33 #include <QtCore/QDir>
34 #include <QtCore/QString>
35 #include <QtCore/QTime>
36 #include <QtDBus/QtDBus>
37 #include <QtNetwork/QLocalServer>
38 #include <QtNetwork/QLocalSocket>
55 static const char appFullName[] =
"org.kde.kio_http_cache_cleaner";
56 static const char appName[] =
"kio_http_cache_cleaner";
68 struct SerializedCacheFileInfo {
73 static const int useCountOffset = 4;
79 static const int size = 36;
94 struct MiniCacheFileInfo {
101 bool operator<(
const MiniCacheFileInfo &other)
const;
102 void debugPrint()
const
104 kDebug(7113) <<
"useCount:" << useCount
105 <<
"\nlastUsedDate:" << lastUsedDate
106 <<
"\nsizeOnDisk:" << sizeOnDisk <<
'\n';
110 struct CacheFileInfo : MiniCacheFileInfo {
127 void prettyPrint()
const
129 QTextStream out(stdout, QIODevice::WriteOnly);
130 out <<
"File " << baseName <<
" version " <<
version[0] <<
version[1];
131 out <<
"\n cached bytes " << bytesCached <<
" useCount " << useCount;
132 out <<
"\n servedDate " <<
dateString(servedDate);
133 out <<
"\n lastModifiedDate " <<
dateString(lastModifiedDate);
134 out <<
"\n expireDate " <<
dateString(expireDate);
135 out <<
"\n entity tag " << etag;
136 out <<
"\n encoded URL " << url;
137 out <<
"\n mimetype " << mimeType;
138 out <<
"\nResponse headers follow...\n";
139 Q_FOREACH (
const QString &h, responseHeaders) {
146 bool MiniCacheFileInfo::operator<(
const MiniCacheFileInfo &other)
const
149 const int otherUseful = other.useCount / qMax(
g_currentDate - other.lastUsedDate,
qint64(1));
150 return thisUseful < otherUseful;
166 time_t tTime =
static_cast<time_t
>(intTime);
168 return check == intTime;
173 if (d.size() < SerializedCacheFileInfo::size) {
174 kDebug(7113) <<
"readBinaryHeader(): file too small?";
177 QDataStream stream(d);
178 stream.setVersion(QDataStream::Qt_4_5);
180 stream >> fi->version[0];
181 stream >> fi->version[1];
182 if (fi->version[0] !=
version[0] || fi->version[1] !=
version[1]) {
183 kDebug(7113) <<
"readBinaryHeader(): wrong magic bytes";
186 stream >> fi->compression;
187 stream >> fi->reserved;
189 stream >> fi->useCount;
191 stream >> fi->servedDate;
192 stream >> fi->lastModifiedDate;
193 stream >> fi->expireDate;
197 stream >> fi->bytesCached;
203 QCryptographicHash hash(QCryptographicHash::Sha1);
205 return QString::fromLatin1(hash.result().toHex());
211 if (!cacheDirName.endsWith(
'/')) {
212 cacheDirName.append(
'/');
214 return cacheDirName + baseName;
219 *line = dev->readLine(8192);
221 if (line->isEmpty() || !line->endsWith(
'\n')) {
235 fi->url = QString::fromLatin1(readBuf);
237 kDebug(7103) <<
"You have witnessed a very improbable hash collision!";
247 fi->etag = QString::fromLatin1(readBuf);
250 fi->mimeType = QString::fromLatin1(readBuf);
255 if (ok && !readBuf.isEmpty()) {
256 fi->responseHeaders.append(QString::fromLatin1(readBuf));
274 if (!file.open(QIODevice::ReadOnly)) {
277 fi->baseName = baseName;
279 QByteArray
header = file.read(SerializedCacheFileInfo::size);
282 kDebug(7113) <<
"read(Text|Binary)Header() returned false, deleting file" << baseName;
287 QFileInfo fileInfo(file);
288 fi->lastUsedDate = fileInfo.lastModified().toTime_t();
289 fi->sizeOnDisk = fileInfo.size();
298 explicit CacheIndex(
const QString &baseName)
300 QByteArray ba = baseName.toLatin1();
301 const int sz = ba.size();
302 const char *input = ba.constData();
306 for (
int i = 0; i < sz; i++) {
309 if (c >=
'0' && c <=
'9') {
310 translated |= c -
'0';
311 }
else if (c >=
'a' && c <=
'f') {
312 translated |= c -
'a' + 10;
319 m_index[i >> 1] = translated;
322 translated = translated << 4;
329 bool operator==(
const CacheIndex &other)
const
333 Q_ASSERT(m_hash == other.m_hash);
339 explicit CacheIndex(
const QByteArray &index)
350 for (
int i = 0; i < ints; i++) {
351 hash ^=
reinterpret_cast<uint *
>(&m_index[0])[i];
357 const int offset = ints *
sizeof(uint);
358 for (
int i = 0; i < bytesLeft; i++) {
359 hash ^=
static_cast<uint
>(m_index[offset + i]) << (i * 8);
365 friend uint
qHash(
const CacheIndex &);
366 friend class Scoreboard;
381 QDataStream stream(cmd);
382 stream.skipRawData(SerializedCacheFileInfo::size);
390 Q_ASSERT(stream.atEnd());
391 fi->baseName = QString::fromLatin1(baseName);
399 struct ScoreboardEntry {
407 static const int size = 36;
409 bool operator<(
const MiniCacheFileInfo &other)
const;
419 QFile sboard(
filePath(QLatin1String(
"scoreboard")));
420 sboard.open(QIODevice::ReadOnly);
422 QByteArray baIndex = sboard.read(ScoreboardEntry::indexSize);
423 QByteArray baRest = sboard.read(ScoreboardEntry::size - ScoreboardEntry::indexSize);
424 if (baIndex.size() + baRest.size() != ScoreboardEntry::size) {
428 const QString entryBasename = QString::fromLatin1(baIndex.toHex());
429 MiniCacheFileInfo mcfi;
430 if (readAndValidateMcfi(baRest, entryBasename, &mcfi)) {
431 m_scoreboard.insert(CacheIndex(baIndex), mcfi);
439 QFile sboard(
filePath(QLatin1String(
"scoreboard")));
440 if (!sboard.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
443 QDataStream stream(&sboard);
446 for (; it != m_scoreboard.constEnd(); ++it) {
447 const char *indexData =
reinterpret_cast<const char *
>(it.key().m_index);
450 stream << it.value().useCount;
451 stream << it.value().lastUsedDate;
452 stream << it.value().sizeOnDisk;
456 bool fillInfo(
const QString &baseName, MiniCacheFileInfo *mcfi)
459 m_scoreboard.constFind(CacheIndex(baseName));
460 if (it == m_scoreboard.constEnd()) {
467 qint64 runCommand(
const QByteArray &cmd)
470 Q_ASSERT(cmd.size() == 80);
477 kDebug(7113) <<
"CreateNotificationCommand for" << fi.baseName;
484 kDebug(7113) <<
"UpdateFileCommand for" << fi.baseName;
485 QFile file(fileName);
486 file.open(QIODevice::ReadWrite);
488 CacheFileInfo fiFromDisk;
489 QByteArray
header = file.read(SerializedCacheFileInfo::size);
490 if (!
readBinaryHeader(header, &fiFromDisk) || fiFromDisk.bytesCached != fi.bytesCached) {
496 const quint32 newUseCount = fiFromDisk.useCount + 1;
497 QByteArray newHeader = cmd.mid(0, SerializedCacheFileInfo::size);
499 QDataStream stream(&newHeader, QIODevice::WriteOnly);
500 stream.skipRawData(SerializedCacheFileInfo::useCountOffset);
501 stream << newUseCount;
505 file.write(newHeader);
515 kDebug(7113) <<
"received invalid command";
519 QFileInfo fileInfo(fileName);
520 fi.lastUsedDate = fileInfo.lastModified().toTime_t();
521 fi.sizeOnDisk = fileInfo.size();
529 void add(
const CacheFileInfo &fi)
531 m_scoreboard[CacheIndex(fi.baseName)] = fi;
534 void remove(
const QString &basename)
536 m_scoreboard.remove(CacheIndex(basename));
543 if (m_scoreboard.count() < fiList.count() + 100) {
546 kDebug(7113) <<
"we have too many fake/stale entries, cleaning up...";
548 Q_FOREACH (CacheFileInfo *fi, fiList) {
549 realFiles.insert(CacheIndex(fi->baseName));
552 while (it != m_scoreboard.end()) {
553 if (realFiles.contains(it.key())) {
556 it = m_scoreboard.erase(it);
562 bool readAndValidateMcfi(
const QByteArray &rawData,
const QString &basename, MiniCacheFileInfo *mcfi)
564 QDataStream stream(rawData);
565 stream >> mcfi->useCount;
567 stream >> mcfi->lastUsedDate;
568 stream >> mcfi->sizeOnDisk;
570 QFileInfo fileInfo(
filePath(basename));
571 if (!fileInfo.exists()) {
575 ok = ok && fileInfo.lastModified().toTime_t() == mcfi->lastUsedDate;
576 ok = ok && fileInfo.size() == mcfi->sizeOnDisk;
581 QFile entryFile(fileInfo.absoluteFilePath());
582 if (!entryFile.open(QIODevice::ReadOnly)) {
585 if (entryFile.size() < SerializedCacheFileInfo::size) {
588 QDataStream stream(&entryFile);
589 stream.skipRawData(SerializedCacheFileInfo::useCountOffset);
591 stream >> mcfi->useCount;
592 mcfi->lastUsedDate = fileInfo.lastModified().toTime_t();
593 mcfi->sizeOnDisk = fileInfo.size();
609 const char *oldDirs =
"0abcdefghijklmnopqrstuvwxyz";
610 const int n = strlen(oldDirs);
612 for (
int i = 0; i < n; i++) {
613 QString dirName = QString::fromLatin1(&oldDirs[i], 1);
615 Q_FOREACH (
const QString &baseName, QDir(
filePath(dirName)).entryList()) {
616 QFile::remove(
filePath(dirName +
'/' + baseName));
619 cacheRootDir.rmdir(dirName);
621 QFile::remove(
filePath(QLatin1String(
"cleaned")));
627 CacheCleaner(
const QDir &cacheDir)
628 : m_totalSizeOnDisk(0)
631 m_fileNameList = cacheDir.entryList();
636 bool processSlice(Scoreboard *scoreboard = 0)
641 if (!m_fileNameList.isEmpty()) {
642 while (t.elapsed() < 100 && !m_fileNameList.isEmpty()) {
643 QString baseName = m_fileNameList.takeFirst();
650 QChar c = baseName[i];
651 nameOk = (c >=
'0' && c <= '9') || (c >=
'a' && c <=
'f');
665 CacheFileInfo *fi =
new CacheFileInfo();
666 fi->baseName = baseName;
668 bool gotInfo =
false;
670 gotInfo = scoreboard->fillInfo(baseName, fi);
674 if (gotInfo && scoreboard) {
675 scoreboard->add(*fi);
680 m_totalSizeOnDisk += fi->sizeOnDisk;
685 kDebug(7113) <<
"total size of cache files is" << m_totalSizeOnDisk;
687 if (m_fileNameList.isEmpty()) {
697 while (t.elapsed() < 100) {
698 if (m_totalSizeOnDisk <= g_maxCacheSize || m_fiList.isEmpty()) {
699 kDebug(7113) <<
"total size of cache files after cleaning is" << m_totalSizeOnDisk;
701 scoreboard->maybeRemoveStaleEntries(m_fiList);
702 scoreboard->writeOut();
704 qDeleteAll(m_fiList);
708 CacheFileInfo *fi = m_fiList.takeFirst();
710 if (QFile::remove(filename)) {
711 m_totalSizeOnDisk -= fi->sizeOnDisk;
713 scoreboard->remove(fi->baseName);
735 options.
add(
"clear-all",
ki18n(
"Empty the cache"));
736 options.
add(
"file-info <filename>",
ki18n(
"Display information about cache file"));
743 QCoreApplication app(argc, argv);
746 if (args->
isSet(
"clear-all")) {
748 }
else if (args->
isSet(
"file-info")) {
764 if (!QDBusConnection::sessionBus().isConnected()) {
765 QDBusError error(QDBusConnection::sessionBus().lastError());
766 fprintf(stderr,
"%s: Could not connect to D-Bus! (%s: %s)\n",
appName,
767 qPrintable(error.name()), qPrintable(error.message()));
771 if (!QDBusConnection::sessionBus().registerService(
appFullName)) {
772 fprintf(stderr,
"%s: Already running!\n",
appName);
783 QDir cacheDir(cacheDirName);
784 if (!cacheDir.exists()) {
785 fprintf(stderr,
"%s: '%s' does not exist.\n",
appName, qPrintable(cacheDirName));
796 CacheCleaner cleaner(cacheDir);
797 while (!cleaner.processSlice()) { }
801 QLocalServer lServer;
804 QFile::remove(socketFileName);
805 lServer.listen(socketFileName);
807 qint64 newBytesCounter = LLONG_MAX;
809 Scoreboard scoreboard;
810 CacheCleaner *cleaner = 0;
814 QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
819 QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents);
821 if (!lServer.isListening()) {
824 lServer.waitForNewConnection(1);
826 while (QLocalSocket *sock = lServer.nextPendingConnection()) {
827 sock->waitForConnected();
828 sockets.append(sock);
831 for (
int i = 0; i < sockets.size(); i++) {
832 QLocalSocket *sock = sockets[i];
833 if (sock->state() != QLocalSocket::ConnectedState) {
834 if (sock->state() != QLocalSocket::UnconnectedState) {
835 sock->waitForDisconnected();
838 sockets.removeAll(sock);
842 sock->waitForReadyRead(0);
844 QByteArray recv = sock->read(80);
845 if (recv.isEmpty()) {
848 Q_ASSERT(recv.size() == 80);
849 newBytesCounter += scoreboard.runCommand(recv);
855 if (cleaner->processSlice(&scoreboard)) {
860 }
else if (newBytesCounter > (g_maxCacheSize / 8)) {
862 cleaner =
new CacheCleaner(cacheDir);
QString saveLocation(const char *type, const QString &suffix=QString(), bool create=true) const
bool CacheFileInfoPtrLessThan(const CacheFileInfo *cf1, const CacheFileInfo *cf2)
static void addCmdLineOptions(const KCmdLineOptions &options, const KLocalizedString &name=KLocalizedString(), const QByteArray &id=QByteArray(), const QByteArray &afterId=QByteArray())
static const int s_hashedUrlBytes
KCmdLineOptions & add(const QByteArray &name, const KLocalizedString &description=KLocalizedString(), const QByteArray &defaultValue=QByteArray())
static bool timeSizeFits(qint64 intTime)
KLocalizedString ki18n(const char *msg)
static QString filenameFromUrl(const QByteArray &url)
static KCmdLineArgs * parsedArgs(const QByteArray &id=QByteArray())
bool operator<(const KEntryKey &k1, const KEntryKey &k2)
static int maxCacheSize()
void add(const QString &fileClass, const QString &directory)
QString toString(const QString &format) const
static QDebug kDebug(bool cond, int area=KDE_DEFAULT_DEBUG_AREA)
static bool readCacheFile(const QString &baseName, CacheFileInfo *fi, OperationMode mode)
bool isSet(const QByteArray &option) const
void setTime_t(qint64 seconds)
bool operator==(const KEntry &k1, const KEntry &k2)
static QString filePath(const QString &baseName)
static const char appName[]
int kdemain(int argc, char **argv)
uint qHash(const CacheIndex &ci)
static bool readTextHeader(QFile *file, CacheFileInfo *fi, OperationMode mode)
static CacheCleanerCommand readCommand(const QByteArray &cmd, CacheFileInfo *fi)
static void removeOldFiles()
static bool readBinaryHeader(const QByteArray &d, CacheFileInfo *fi)
static const int s_hashedUrlBits
static QString locateLocal(const char *type, const QString &filename, const KComponentData &cData=KGlobal::mainComponent())
static const char version[]
static void init(int argc, char **argv, const QByteArray &appname, const QByteArray &catalog, const KLocalizedString &programName, const QByteArray &version, const KLocalizedString &description=KLocalizedString(), StdCmdLineArgs stdargs=StdCmdLineArgs(CmdLineArgQt|CmdLineArgKDE))
QString getOption(const QByteArray &option) const
static QString dateString(qint64 date)
static bool readLineChecked(QIODevice *dev, QByteArray *line)
static const int s_hashedUrlNibbles
static const char appFullName[]