• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • kdelibs
  • KDE Home
  • Contact Us
 

KIOSlave

http_cache_cleaner.cpp

Go to the documentation of this file.
00001 /*
00002 This file is part of KDE
00003 
00004  Copyright (C) 1999-2000 Waldo Bastian (bastian@kde.org)
00005  Copyright (C) 2009 Andreas Hartmetz (ahartmetz@gmail.com)
00006 
00007 Permission is hereby granted, free of charge, to any person obtaining a copy
00008 of this software and associated documentation files (the "Software"), to deal
00009 in the Software without restriction, including without limitation the rights
00010 to use, copy, modify, merge, publish, distribute, and/or sell
00011 copies of the Software, and to permit persons to whom the Software is
00012 furnished to do so, subject to the following conditions:
00013 
00014 The above copyright notice and this permission notice shall be included in
00015 all copies or substantial portions of the Software.
00016 
00017 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
00018 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
00019 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
00020 AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
00021 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
00022 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
00023 */
00024 //----------------------------------------------------------------------------
00025 //
00026 // KDE HTTP Cache cleanup tool
00027 
00028 #include <cstring>
00029 #include <time.h>
00030 #include <stdlib.h>
00031 #include <zlib.h>
00032 
00033 #include <QtCore/QDir>
00034 #include <QtCore/QString>
00035 #include <QtCore/QTime>
00036 #include <QtDBus/QtDBus>
00037 #include <QtNetwork/QLocalServer>
00038 #include <QtNetwork/QLocalSocket>
00039 
00040 #include <kcmdlineargs.h>
00041 #include <kcomponentdata.h>
00042 #include <kdatetime.h>
00043 #include <kdebug.h>
00044 #include <kglobal.h>
00045 #include <klocale.h>
00046 #include <kprotocolmanager.h>
00047 #include <kstandarddirs.h>
00048 
00049 #include <unistd.h>
00050 
00051 time_t g_currentDate;
00052 int g_maxCacheAge;
00053 qint64 g_maxCacheSize;
00054 
00055 static const char appFullName[] = "org.kde.kio_http_cache_cleaner";
00056 static const char appName[] = "kio_http_cache_cleaner";
00057 
00058 // !START OF SYNC!
00059 // Keep the following in sync with the cache code in http.cpp
00060 
00061 static const int s_hashedUrlBits = 160;   // this number should always be divisible by eight
00062 static const int s_hashedUrlNibbles = s_hashedUrlBits / 4;
00063 static const int s_hashedUrlBytes = s_hashedUrlBits / 8;
00064 
00065 static const char version[] = "A\n";
00066 
00067 // never instantiated, on-disk / wire format only
00068 struct SerializedCacheFileInfo {
00069 // from http.cpp
00070     quint8 version[2];
00071     quint8 compression; // for now fixed to 0
00072     quint8 reserved;    // for now; also alignment
00073     static const int useCountOffset = 4;
00074     qint32 useCount;
00075     qint64 servedDate;
00076     qint64 lastModifiedDate;
00077     qint64 expireDate;
00078     qint32 bytesCached;
00079     static const int size = 36;
00080 
00081     QString url;
00082     QString etag;
00083     QString mimeType;
00084     QStringList responseHeaders; // including status response like "HTTP 200 OK"
00085 };
00086 
00087 static QString dateString(qint64 date)
00088 {
00089     KDateTime dt;
00090     dt.setTime_t(date);
00091     return dt.toString(KDateTime::ISODate);
00092 }
00093 
00094 struct MiniCacheFileInfo {
00095 // data from cache entry file, or from scoreboard file
00096     qint32 useCount;
00097 // from filesystem
00098     qint64 lastUsedDate;
00099     qint32 sizeOnDisk;
00100     // we want to delete the least "useful" files and we'll have to sort a list for that...
00101     bool operator<(const MiniCacheFileInfo &other) const;
00102     void debugPrint() const
00103     {
00104         kDebug(7113) << "useCount:" << useCount
00105                      << "\nlastUsedDate:" << lastUsedDate
00106                      << "\nsizeOnDisk:" << sizeOnDisk << '\n';
00107     }
00108 };
00109 
00110 struct CacheFileInfo : MiniCacheFileInfo {
00111     quint8 version[2];
00112     quint8 compression; // for now fixed to 0
00113     quint8 reserved;    // for now; also alignment
00114 
00115 
00116     qint64 servedDate;
00117     qint64 lastModifiedDate;
00118     qint64 expireDate;
00119     qint32 bytesCached;
00120 
00121     QString baseName;
00122     QString url;
00123     QString etag;
00124     QString mimeType;
00125     QStringList responseHeaders; // including status response like "HTTP 200 OK"
00126 
00127     void prettyPrint() const
00128     {
00129         QTextStream out(stdout, QIODevice::WriteOnly);
00130         out << "File " << baseName << " version " << version[0] << version[1];
00131         out << "\n cached bytes     " << bytesCached << " useCount " << useCount;
00132         out << "\n servedDate       " << dateString(servedDate);
00133         out << "\n lastModifiedDate " << dateString(lastModifiedDate);
00134         out << "\n expireDate       " << dateString(expireDate);
00135         out << "\n entity tag       " << etag;
00136         out << "\n encoded URL      " << url;
00137         out << "\n mimetype         " << mimeType;
00138         out << "\nResponse headers follow...\n";
00139         Q_FOREACH (const QString &h, responseHeaders) {
00140             out << h << '\n';
00141         }
00142     }
00143 };
00144 
00145 
00146 bool MiniCacheFileInfo::operator<(const MiniCacheFileInfo &other) const
00147 {
00148     const int thisUseful = useCount / qMax(g_currentDate - lastUsedDate, qint64(1));
00149     const int otherUseful = other.useCount / qMax(g_currentDate - other.lastUsedDate, qint64(1));
00150     return thisUseful < otherUseful;
00151 }
00152 
00153 bool CacheFileInfoPtrLessThan(const CacheFileInfo *cf1, const CacheFileInfo *cf2)
00154 {
00155     return *cf1 < *cf2;
00156 }
00157 
00158 enum OperationMode {
00159     CleanCache = 0,
00160     DeleteCache,
00161     FileInfo
00162 };
00163 
00164 static bool timeSizeFits(qint64 intTime)
00165 {
00166     time_t tTime = static_cast<time_t>(intTime);
00167     qint64 check = static_cast<qint64>(tTime);
00168     return check == intTime;
00169 }
00170 
00171 static bool readBinaryHeader(const QByteArray &d, CacheFileInfo *fi)
00172 {
00173     if (d.size() < SerializedCacheFileInfo::size) {
00174         kDebug(7113) << "readBinaryHeader(): file too small?";
00175         return false;
00176     }
00177     QDataStream stream(d);
00178     stream.setVersion(QDataStream::Qt_4_5);
00179 
00180     stream >> fi->version[0];
00181     stream >> fi->version[1];
00182     if (fi->version[0] != version[0] || fi->version[1] != version[1]) {
00183         kDebug(7113) << "readBinaryHeader(): wrong magic bytes";
00184         return false;
00185     }
00186     stream >> fi->compression;
00187     stream >> fi->reserved;
00188 
00189     stream >> fi->useCount;
00190 
00191     stream >> fi->servedDate;
00192     stream >> fi->lastModifiedDate;
00193     stream >> fi->expireDate;
00194     bool timeSizeOk = timeSizeFits(fi->servedDate) && timeSizeFits(fi->lastModifiedDate) &&
00195                       timeSizeFits(fi->expireDate);
00196 
00197     stream >> fi->bytesCached;
00198     return timeSizeOk;
00199 }
00200 
00201 static QString filenameFromUrl(const QByteArray &url)
00202 {
00203     QCryptographicHash hash(QCryptographicHash::Sha1);
00204     hash.addData(url);
00205     return QString::fromLatin1(hash.result().toHex());
00206 }
00207 
00208 static QString filePath(const QString &baseName)
00209 {
00210     QString cacheDirName = KGlobal::dirs()->saveLocation("cache", "http");
00211     if (!cacheDirName.endsWith('/')) {
00212         cacheDirName.append('/');
00213     }
00214     return cacheDirName + baseName;
00215 }
00216 
00217 static bool readLineChecked(QIODevice *dev, QByteArray *line)
00218 {
00219     *line = dev->readLine(8192);
00220     // if nothing read or the line didn't fit into 8192 bytes(!)
00221     if (line->isEmpty() || !line->endsWith('\n')) {
00222         return false;
00223     }
00224     // we don't actually want the newline!
00225     line->chop(1);
00226     return true;
00227 }
00228 
00229 static bool readTextHeader(QFile *file, CacheFileInfo *fi, OperationMode mode)
00230 {
00231     bool ok = true;
00232     QByteArray readBuf;
00233 
00234     ok = ok && readLineChecked(file, &readBuf);
00235     fi->url = QString::fromLatin1(readBuf);
00236     if (filenameFromUrl(readBuf) != QFileInfo(*file).baseName()) {
00237         kDebug(7103) << "You have witnessed a very improbable hash collision!";
00238         return false;
00239     }
00240 
00241     // only read the necessary info for cache cleaning. Saves time and (more importantly) memory.
00242     if (mode != FileInfo) {
00243         return true;
00244     }
00245 
00246     ok = ok && readLineChecked(file, &readBuf);
00247     fi->etag = QString::fromLatin1(readBuf);
00248 
00249     ok = ok && readLineChecked(file, &readBuf);
00250     fi->mimeType = QString::fromLatin1(readBuf);
00251 
00252     // read as long as no error and no empty line found
00253     while (true) {
00254         ok = ok && readLineChecked(file, &readBuf);
00255         if (ok && !readBuf.isEmpty()) {
00256             fi->responseHeaders.append(QString::fromLatin1(readBuf));
00257         } else {
00258             break;
00259         }
00260     }
00261     return ok; // it may still be false ;)
00262 }
00263 
00264 // TODO common include file with http.cpp?
00265 enum CacheCleanerCommand {
00266     InvalidCommand = 0,
00267     CreateFileNotificationCommand,
00268     UpdateFileCommand
00269 };
00270 
00271 static bool readCacheFile(const QString &baseName, CacheFileInfo *fi, OperationMode mode)
00272 {
00273     QFile file(filePath(baseName));
00274     file.open(QIODevice::ReadOnly);
00275     if (file.openMode() != QIODevice::ReadOnly) {
00276         return false;
00277     }
00278     fi->baseName = baseName;
00279 
00280     QByteArray header = file.read(SerializedCacheFileInfo::size);
00281     // do *not* modify/delete the file if we're in file info mode.
00282     if (!(readBinaryHeader(header, fi) && readTextHeader(&file, fi, mode)) && mode != FileInfo) {
00283         kDebug(7113) << "read(Text|Binary)Header() returned false, deleting file" << baseName;
00284         file.remove();
00285         return false;
00286     }
00287     // get meta-information from the filesystem
00288     QFileInfo fileInfo(file);
00289     fi->lastUsedDate = fileInfo.lastModified().toTime_t();
00290     fi->sizeOnDisk = fileInfo.size();
00291     return true;
00292 }
00293 
00294 class Scoreboard;
00295 
00296 class CacheIndex
00297 {
00298 public:
00299     explicit CacheIndex(const QString &baseName)
00300     {
00301         QByteArray ba = baseName.toLatin1();
00302         const int sz = ba.size();
00303         const char *input = ba.constData();
00304         Q_ASSERT(sz == s_hashedUrlNibbles);
00305 
00306         int translated = 0;
00307         for (int i = 0; i < sz; i++) {
00308             int c = input[i];
00309 
00310             if (c >= '0' && c <= '9') {
00311                 translated |= c - '0';
00312             } else if (c >= 'a' && c <= 'f') {
00313                 translated |= c - 'a' + 10;
00314             } else {
00315                 Q_ASSERT(false);
00316             }
00317 
00318             if (i & 1) {
00319                 // odd index
00320                 m_index[i >> 1] = translated;
00321                 translated = 0;
00322             } else  {
00323                 translated = translated << 4;
00324             }
00325         }
00326 
00327         computeHash();
00328     }
00329 
00330     bool operator==(const CacheIndex &other) const
00331     {
00332         const bool isEqual = memcmp(m_index, other.m_index, s_hashedUrlBytes) == 0;
00333         if (isEqual) {
00334             Q_ASSERT(m_hash == other.m_hash);
00335         }
00336         return isEqual;
00337     }
00338 
00339 private:
00340     explicit CacheIndex(const QByteArray &index)
00341     {
00342         Q_ASSERT(index.length() >= s_hashedUrlBytes);
00343         memcpy(m_index, index.constData(), s_hashedUrlBytes);
00344         computeHash();
00345     }
00346 
00347     void computeHash()
00348     {
00349         uint hash = 0;
00350         const int ints = s_hashedUrlBytes / sizeof(uint);
00351         for (int i = 0; i < ints; i++) {
00352             hash ^= reinterpret_cast<uint *>(&m_index[0])[i];
00353         }
00354         if (const int bytesLeft = s_hashedUrlBytes % sizeof(uint)) {
00355             // dead code until a new url hash algorithm or architecture with sizeof(uint) != 4 appears.
00356             // we have the luxury of ignoring endianness because the hash is never written to disk.
00357             // just merge the bits into the the hash in some way.
00358             const int offset = ints * sizeof(uint);
00359             for (int i = 0; i < bytesLeft; i++) {
00360                 hash ^= static_cast<uint>(m_index[offset + i]) << (i * 8);
00361             }
00362         }
00363         m_hash = hash;
00364     }
00365 
00366     friend uint qHash(const CacheIndex &);
00367     friend class Scoreboard;
00368 
00369     quint8 m_index[s_hashedUrlBytes]; // packed binary version of the hexadecimal name
00370     uint m_hash;
00371 };
00372 
00373 uint qHash(const CacheIndex &ci)
00374 {
00375     return ci.m_hash;
00376 }
00377 
00378 
00379 static CacheCleanerCommand readCommand(const QByteArray &cmd, CacheFileInfo *fi)
00380 {
00381     readBinaryHeader(cmd, fi);
00382     QDataStream stream(cmd);
00383     stream.skipRawData(SerializedCacheFileInfo::size);
00384 
00385     quint32 ret;
00386     stream >> ret;
00387 
00388     QByteArray baseName;
00389     baseName.resize(s_hashedUrlNibbles);
00390     stream.readRawData(baseName.data(), s_hashedUrlNibbles);
00391     Q_ASSERT(stream.atEnd());
00392     fi->baseName = QString::fromLatin1(baseName);
00393 
00394     Q_ASSERT(ret == CreateFileNotificationCommand || ret == UpdateFileCommand);
00395     return static_cast<CacheCleanerCommand>(ret);
00396 }
00397 
00398 
00399 // never istantiated, on-disk format only
00400 struct ScoreboardEntry {
00401 // from scoreboard file
00402     quint8 index[s_hashedUrlBytes];
00403     static const int indexSize = s_hashedUrlBytes;
00404     qint32 useCount;
00405 // from scoreboard file, but compared with filesystem to see if scoreboard has current data
00406     qint64 lastUsedDate;
00407     qint32 sizeOnDisk;
00408     static const int size = 36;
00409     // we want to delete the least "useful" files and we'll have to sort a list for that...
00410     bool operator<(const MiniCacheFileInfo &other) const;
00411 };
00412 
00413 
00414 class Scoreboard
00415 {
00416 public:
00417     Scoreboard()
00418     {
00419         // read in the scoreboard...
00420         QFile sboard(filePath(QLatin1String("scoreboard")));
00421         sboard.open(QIODevice::ReadOnly);
00422         while (true) {
00423             QByteArray baIndex = sboard.read(ScoreboardEntry::indexSize);
00424             QByteArray baRest = sboard.read(ScoreboardEntry::size - ScoreboardEntry::indexSize);
00425             if (baIndex.size() + baRest.size() != ScoreboardEntry::size) {
00426                 break;
00427             }
00428 
00429             const QString entryBasename = QString::fromLatin1(baIndex.toHex());
00430             MiniCacheFileInfo mcfi;
00431             if (readAndValidateMcfi(baRest, entryBasename, &mcfi)) {
00432                 m_scoreboard.insert(CacheIndex(baIndex), mcfi);
00433             }
00434         }
00435     }
00436 
00437     void writeOut()
00438     {
00439         // write out the scoreboard
00440         QFile sboard(filePath(QLatin1String("scoreboard")));
00441         sboard.open(QIODevice::WriteOnly | QIODevice::Truncate);
00442         QDataStream stream(&sboard);
00443 
00444         QHash<CacheIndex, MiniCacheFileInfo>::ConstIterator it = m_scoreboard.constBegin();
00445         for (; it != m_scoreboard.constEnd(); ++it) {
00446             const char *indexData = reinterpret_cast<const char *>(it.key().m_index);
00447             stream.writeRawData(indexData, s_hashedUrlBytes);
00448 
00449             stream << it.value().useCount;
00450             stream << it.value().lastUsedDate;
00451             stream << it.value().sizeOnDisk;
00452         }
00453     }
00454 
00455     bool fillInfo(const QString &baseName, MiniCacheFileInfo *mcfi)
00456     {
00457         QHash<CacheIndex, MiniCacheFileInfo>::ConstIterator it =
00458                                        m_scoreboard.constFind(CacheIndex(baseName));
00459         if (it == m_scoreboard.constEnd()) {
00460             return false;
00461         }
00462         *mcfi = it.value();
00463         return true;
00464     }
00465 
00466     int runCommand(const QByteArray &cmd)
00467     {
00468         // execute the command; return number of bytes if a new file was created, zero otherwise.
00469         Q_ASSERT(cmd.size() == 80);
00470         CacheFileInfo fi;
00471         const CacheCleanerCommand ccc = readCommand(cmd, &fi);
00472         QString fileName = filePath(fi.baseName);
00473 
00474         switch (ccc) {
00475         case CreateFileNotificationCommand:
00476             kDebug(7113) << "CreateNotificationCommand for" << fi.baseName;
00477             if (!readBinaryHeader(cmd, &fi)) {
00478                 return 0;
00479             }
00480             break;
00481 
00482         case UpdateFileCommand: {
00483             kDebug(7113) << "UpdateFileCommand for" << fi.baseName;
00484             QFile file(fileName);
00485             file.open(QIODevice::ReadWrite);
00486 
00487             CacheFileInfo fiFromDisk;
00488             QByteArray header = file.read(SerializedCacheFileInfo::size);
00489             if (!readBinaryHeader(header, &fiFromDisk) || fiFromDisk.bytesCached != fi.bytesCached) {
00490                 return 0;
00491             }
00492 
00493             // adjust the use count, to make sure that we actually count up. (slaves read the file
00494             // asynchronously...)
00495             const quint32 newUseCount = fiFromDisk.useCount + 1;
00496             QByteArray newHeader = cmd.mid(0, SerializedCacheFileInfo::size);
00497             {
00498                 QDataStream stream(&newHeader, QIODevice::WriteOnly);
00499                 stream.skipRawData(SerializedCacheFileInfo::useCountOffset);
00500                 stream << newUseCount;
00501             }
00502 
00503             file.seek(0);
00504             file.write(newHeader);
00505             file.close();
00506 
00507             if (!readBinaryHeader(newHeader, &fi)) {
00508                 return 0;
00509             }
00510             break;
00511         }
00512 
00513         default:
00514             kDebug(7113) << "received invalid command";
00515             return 0;
00516         }
00517 
00518         QFileInfo fileInfo(fileName);
00519         fi.lastUsedDate = fileInfo.lastModified().toTime_t();
00520         fi.sizeOnDisk = fileInfo.size();
00521         fi.debugPrint();
00522         // a CacheFileInfo is-a MiniCacheFileInfo which enables the following assignment...
00523         add(fi);
00524         // finally, return cache dir growth (only relevant if a file was actually created!)
00525         return ccc == CreateFileNotificationCommand ? fi.sizeOnDisk : 0;
00526     }
00527 
00528     void add(const CacheFileInfo &fi)
00529     {
00530         m_scoreboard[CacheIndex(fi.baseName)] = fi;
00531     }
00532 
00533     void remove(const QString &basename)
00534     {
00535         m_scoreboard.remove(CacheIndex(basename));
00536     }
00537 
00538     // keep memory usage reasonably low - otherwise entries of nonexistent files don't hurt.
00539     void maybeRemoveStaleEntries(const QList<CacheFileInfo *> &fiList)
00540     {
00541         // don't bother when there are a few bogus entries
00542         if (m_scoreboard.count() < fiList.count() + 100) {
00543             return;
00544         }
00545         kDebug(7113) << "we have too many fake/stale entries, cleaning up...";
00546         QSet<CacheIndex> realFiles;
00547         Q_FOREACH (CacheFileInfo *fi, fiList) {
00548             realFiles.insert(CacheIndex(fi->baseName));
00549         }
00550         QHash<CacheIndex, MiniCacheFileInfo>::Iterator it = m_scoreboard.begin();
00551         while (it != m_scoreboard.end()) {
00552             if (realFiles.contains(it.key())) {
00553                 ++it;
00554             } else {
00555                 it = m_scoreboard.erase(it);
00556             }
00557         }
00558     }
00559 
00560 private:
00561     bool readAndValidateMcfi(const QByteArray &rawData, const QString &basename, MiniCacheFileInfo *mcfi)
00562     {
00563         QDataStream stream(rawData);
00564         stream >> mcfi->useCount;
00565         // check those against filesystem
00566         stream >> mcfi->lastUsedDate;
00567         stream >> mcfi->sizeOnDisk;
00568 
00569         QFileInfo fileInfo(filePath(basename));
00570         if (!fileInfo.exists()) {
00571             return false;
00572         }
00573         bool ok = true;
00574         ok = ok && fileInfo.lastModified().toTime_t() == mcfi->lastUsedDate;
00575         ok = ok && fileInfo.size() == mcfi->sizeOnDisk;
00576         if (!ok) {
00577             // size or last-modified date not consistent with entry file; reload useCount
00578             // note that avoiding to open the file is the whole purpose of the scoreboard - we only
00579             // open the file if we really have to.
00580             QFile entryFile(fileInfo.absoluteFilePath());
00581             entryFile.open(QIODevice::ReadOnly);
00582             if (entryFile.size() < SerializedCacheFileInfo::size) {
00583                 return false;
00584             }
00585             QDataStream stream(&entryFile);
00586             stream.skipRawData(SerializedCacheFileInfo::useCountOffset);
00587 
00588             stream >> mcfi->useCount;
00589             mcfi->lastUsedDate = fileInfo.lastModified().toTime_t();
00590             mcfi->sizeOnDisk = fileInfo.size();
00591             ok = true;
00592         }
00593         return ok;
00594     }
00595 
00596     QHash<CacheIndex, MiniCacheFileInfo> m_scoreboard;
00597 };
00598 
00599 
00600 // Keep the above in sync with the cache code in http.cpp
00601 // !END OF SYNC!
00602 
00603 // remove files and directories used by earlier versions of the HTTP cache.
00604 static void removeOldFiles()
00605 {
00606     const char *oldDirs = "0abcdefghijklmnopqrstuvwxyz";
00607     const int n = strlen(oldDirs);
00608     QDir cacheRootDir(filePath(QString()));
00609     for (int i = 0; i < n; i++) {
00610         QString dirName = QString::fromLatin1(&oldDirs[i], 1);
00611         // delete files in directory...
00612         Q_FOREACH (const QString &baseName, QDir(filePath(dirName)).entryList()) {
00613             QFile::remove(filePath(dirName + '/' + baseName));
00614         }
00615         // delete the (now hopefully empty!) directory itself
00616         cacheRootDir.rmdir(dirName);
00617     }
00618     QFile::remove(filePath(QLatin1String("cleaned")));
00619 }
00620 
00621 class CacheCleaner
00622 {
00623 public:
00624     CacheCleaner(const QDir &cacheDir)
00625      : m_totalSizeOnDisk(0)
00626     {
00627         kDebug(7113);
00628         m_fileNameList = cacheDir.entryList();
00629     }
00630 
00631     // Delete some of the files that need to be deleted. Return true when done, false otherwise.
00632     // This makes interleaved cleaning / serving ioslaves possible.
00633     bool processSlice(Scoreboard *scoreboard = 0)
00634     {
00635         QTime t;
00636         t.start();
00637         // phase one: gather information about cache files
00638         if (!m_fileNameList.isEmpty()) {
00639             while (t.elapsed() < 100 && !m_fileNameList.isEmpty()) {
00640                 QString baseName = m_fileNameList.takeFirst();
00641                 // check if the filename is of the $s_hashedUrlNibbles letters, 0...f type
00642                 if (baseName.length() < s_hashedUrlNibbles) {
00643                     continue;
00644                 }
00645                 bool nameOk = true;
00646                 for (int i = 0; i < s_hashedUrlNibbles && nameOk; i++) {
00647                     QChar c = baseName[i];
00648                     nameOk = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f');
00649                 }
00650                 if (!nameOk) {
00651                     continue;
00652                 }
00653                 if (baseName.length() > s_hashedUrlNibbles) {
00654                     if (g_currentDate - QFileInfo(filePath(baseName)).lastModified().toTime_t() > 15*60) {
00655                         // it looks like a temporary file that hasn't been touched in > 15 minutes...
00656                         QFile::remove(filePath(baseName));
00657                     }
00658                     // the temporary file might still be written to, leave it alone
00659                     continue;
00660                 }
00661 
00662                 CacheFileInfo *fi = new CacheFileInfo();
00663                 fi->baseName = baseName;
00664 
00665                 bool gotInfo = false;
00666                 if (scoreboard) {
00667                     gotInfo = scoreboard->fillInfo(baseName, fi);
00668                 }
00669                 if (!gotInfo) {
00670                     gotInfo = readCacheFile(baseName, fi, CleanCache);
00671                     if (gotInfo && scoreboard) {
00672                         scoreboard->add(*fi);
00673                     }
00674                 }
00675                 if (gotInfo) {
00676                     m_fiList.append(fi);
00677                     m_totalSizeOnDisk += fi->sizeOnDisk;
00678                 } else {
00679                     delete fi;
00680                 }
00681             }
00682             kDebug(7113) << "total size of cache files is" << m_totalSizeOnDisk;
00683 
00684             if (m_fileNameList.isEmpty()) {
00685                 // final step of phase one
00686                 qSort(m_fiList.begin(), m_fiList.end(), CacheFileInfoPtrLessThan);
00687             }
00688             return false;
00689         }
00690 
00691         // phase two: delete files until cache is under maximum allowed size
00692 
00693         // TODO: delete files larger than allowed for a single file
00694         while (t.elapsed() < 100) {
00695             if (m_totalSizeOnDisk <= g_maxCacheSize || m_fiList.isEmpty()) {
00696                 kDebug(7113) << "total size of cache files after cleaning is" << m_totalSizeOnDisk;
00697                 if (scoreboard) {
00698                     scoreboard->maybeRemoveStaleEntries(m_fiList);
00699                     scoreboard->writeOut();
00700                 }
00701                 qDeleteAll(m_fiList);
00702                 m_fiList.clear();
00703                 return true;
00704             }
00705             CacheFileInfo *fi = m_fiList.takeFirst();
00706             QString filename = filePath(fi->baseName);
00707             if (QFile::remove(filename)) {
00708                 m_totalSizeOnDisk -= fi->sizeOnDisk;
00709                 if (scoreboard) {
00710                     scoreboard->remove(fi->baseName);
00711                 }
00712             }
00713             delete fi;
00714         }
00715         return false;
00716     }
00717 
00718 private:
00719     QStringList m_fileNameList;
00720     QList<CacheFileInfo *> m_fiList;
00721     qint64 m_totalSizeOnDisk;
00722 };
00723 
00724 
00725 extern "C" KDE_EXPORT int kdemain(int argc, char **argv)
00726 {
00727     KCmdLineArgs::init(argc, argv, appName, "kdelibs4",
00728                        ki18n("KDE HTTP cache maintenance tool"), version,
00729                        ki18n("KDE HTTP cache maintenance tool"), KCmdLineArgs::CmdLineArgNone);
00730 
00731     KCmdLineOptions options;
00732     options.add("clear-all", ki18n("Empty the cache"));
00733     options.add("file-info <filename>", ki18n("Display information about cache file"));
00734 
00735     KCmdLineArgs::addCmdLineOptions( options );
00736     KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
00737     KComponentData componentData(appName);
00738 
00739     // we need a QCoreApplication so QCoreApplication::processEvents() works as intended
00740     QCoreApplication app(argc, argv);
00741 
00742     OperationMode mode = CleanCache;
00743     if (args->isSet("clear-all")) {
00744         mode = DeleteCache;
00745     } else if (args->isSet("file-info")) {
00746         mode = FileInfo;
00747     }
00748 
00749     // file info mode: no scanning of directories, just output info and exit.
00750     if (mode == FileInfo) {
00751         CacheFileInfo fi;
00752         if (!readCacheFile(args->getOption("file-info"), &fi, mode)) {
00753             return 1;
00754         }
00755         fi.prettyPrint();
00756         return 0;
00757     }
00758 
00759     // make sure we're the only running instance of the cleaner service
00760     if (mode == CleanCache) {
00761         if (!QDBusConnection::sessionBus().isConnected()) {
00762             QDBusError error(QDBusConnection::sessionBus().lastError());
00763             fprintf(stderr, "%s: Could not connect to D-Bus! (%s: %s)\n", appName,
00764                     qPrintable(error.name()), qPrintable(error.message()));
00765             return 1;
00766         }
00767 
00768         if (!QDBusConnection::sessionBus().registerService(appFullName)) {
00769             fprintf(stderr, "%s: Already running!\n", appName);
00770             return 0;
00771         }
00772     }
00773 
00774 
00775     g_currentDate = time(0);
00776     g_maxCacheAge = KProtocolManager::maxCacheAge();
00777     g_maxCacheSize = mode == DeleteCache ? -1 : KProtocolManager::maxCacheSize() * 1024;
00778 
00779     QString cacheDirName = KGlobal::dirs()->saveLocation("cache", "http");
00780     QDir cacheDir(cacheDirName);
00781     if (!cacheDir.exists()) {
00782         fprintf(stderr, "%s: '%s' does not exist.\n", appName, qPrintable(cacheDirName));
00783         return 0;
00784     }
00785 
00786     removeOldFiles();
00787 
00788     if (mode == DeleteCache) {
00789         QTime t;
00790         t.start();
00791         cacheDir.refresh();
00792         //qDebug() << "time to refresh the cacheDir QDir:" << t.elapsed();
00793         CacheCleaner cleaner(cacheDir);
00794         while (!cleaner.processSlice()) { }
00795         return 0;
00796     }
00797 
00798     QLocalServer lServer;
00799     QString socketFileName = KStandardDirs::locateLocal("socket", "kio_http_cache_cleaner");
00800     // we need to create the file by opening the socket, otherwise it won't work
00801     QFile::remove(socketFileName);
00802     lServer.listen(socketFileName);
00803     QList<QLocalSocket *> sockets;
00804     int newBytesCounter = INT_MAX;  // force cleaner run on startup
00805 
00806     Scoreboard scoreboard;
00807     CacheCleaner *cleaner = 0;
00808     while (true) {
00809         g_currentDate = time(0);
00810         if (cleaner) {
00811             QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
00812         } else {
00813             // We will not immediately know when a socket was disconnected. Causes:
00814             // - WaitForMoreEvents does not make processEvents() return when a socket disconnects
00815             // - WaitForMoreEvents *and* a timeout is not possible.
00816             QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents);
00817         }
00818         if (!lServer.isListening()) {
00819             return 1;
00820         }
00821         lServer.waitForNewConnection(1);
00822 
00823         while (QLocalSocket *sock = lServer.nextPendingConnection()) {
00824             sock->waitForConnected();
00825             sockets.append(sock);
00826         }
00827 
00828         for (int i = 0; i < sockets.size(); i++) {
00829             QLocalSocket *sock = sockets[i];
00830             if (sock->state() != QLocalSocket::ConnectedState) {
00831                 if (sock->state() != QLocalSocket::UnconnectedState) {
00832                     sock->waitForDisconnected();
00833                 }
00834                 delete sock;
00835                 sockets.removeAll(sock);
00836                 i--;
00837                 continue;
00838             }
00839             sock->waitForReadyRead(0);
00840             while (true) {
00841                 QByteArray recv = sock->read(80);
00842                 if (recv.isEmpty()) {
00843                     break;
00844                 }
00845                 Q_ASSERT(recv.size() == 80);
00846                 newBytesCounter += scoreboard.runCommand(recv);
00847             }
00848         }
00849 
00850         // interleave cleaning with serving ioslaves to reduce "garbage collection pauses"
00851         if (cleaner) {
00852             if (cleaner->processSlice(&scoreboard)) {
00853                 // that was the last slice, done
00854                 delete cleaner;
00855                 cleaner = 0;
00856             }
00857         } else if (newBytesCounter > g_maxCacheSize / 8) {
00858             cacheDir.refresh();
00859             cleaner = new CacheCleaner(cacheDir);
00860             newBytesCounter = 0;
00861         }
00862     }
00863     return 0;
00864 }

KIOSlave

Skip menu "KIOSlave"
  • Main Page
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.3
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal