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
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?";
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;
214 return cacheDirName + baseName;
237 kDebug(7103) <<
"You have witnessed a very improbable hash collision!";
255 if (ok && !readBuf.
isEmpty()) {
274 if (!file.
open(QIODevice::ReadOnly)) {
277 fi->baseName = baseName;
282 kDebug(7113) <<
"read(Text|Binary)Header() returned false, deleting file" << baseName;
289 fi->sizeOnDisk = fileInfo.
size();
298 explicit CacheIndex(
const QString &baseName)
301 const int sz = ba.
size();
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);
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;
390 Q_ASSERT(stream.
atEnd());
399 struct ScoreboardEntry {
407 static const int size = 36;
409 bool operator<(
const MiniCacheFileInfo &other)
const;
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) {
429 MiniCacheFileInfo mcfi;
430 if (readAndValidateMcfi(baRest, entryBasename, &mcfi)) {
431 m_scoreboard.
insert(CacheIndex(baIndex), mcfi);
440 if (!sboard.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
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()) {
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;
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";
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()) {
556 it = m_scoreboard.
erase(it);
562 bool readAndValidateMcfi(
const QByteArray &rawData,
const QString &basename, MiniCacheFileInfo *mcfi)
565 stream >> mcfi->useCount;
567 stream >> mcfi->lastUsedDate;
568 stream >> mcfi->sizeOnDisk;
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) {
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++) {
619 cacheRootDir.
rmdir(dirName);
627 CacheCleaner(
const QDir &cacheDir)
628 : m_totalSizeOnDisk(0)
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()) {
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();
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"));
746 if (args->
isSet(
"clear-all")) {
748 }
else if (args->
isSet(
"file-info")) {
766 fprintf(stderr,
"%s: Could not connect to D-Bus! (%s: %s)\n",
appName,
767 qPrintable(error.
name()), qPrintable(error.
message()));
772 fprintf(stderr,
"%s: Already running!\n",
appName);
783 QDir cacheDir(cacheDirName);
785 fprintf(stderr,
"%s: '%s' does not exist.\n",
appName, qPrintable(cacheDirName));
796 CacheCleaner cleaner(cacheDir);
797 while (!cleaner.processSlice()) { }
805 lServer.
listen(socketFileName);
807 qint64 newBytesCounter = LLONG_MAX;
809 Scoreboard scoreboard;
810 CacheCleaner *cleaner = 0;
827 sock->waitForConnected();
831 for (
int i = 0; i < sockets.
size(); i++) {
833 if (sock->
state() != QLocalSocket::ConnectedState) {
834 if (sock->
state() != QLocalSocket::UnconnectedState) {
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);
virtual bool waitForReadyRead(int msecs)
virtual QLocalSocket * nextPendingConnection()
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())
QString & append(QChar ch)
const Key key(const T &value) const
static const int s_hashedUrlBytes
KCmdLineOptions & add(const QByteArray &name, const KLocalizedString &description=KLocalizedString(), const QByteArray &defaultValue=QByteArray())
static bool timeSizeFits(qint64 intTime)
bool waitForNewConnection(int msec, bool *timedOut)
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
const_iterator constFind(const Key &key) const
QDBusConnection sessionBus()
bool waitForDisconnected(int msecs)
bool listen(const QString &name)
const_iterator insert(const T &value)
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
int readRawData(char *s, int len)
void setTime_t(qint64 seconds)
bool operator==(const KEntry &k1, const KEntry &k2)
int count(const T &value) const
void processEvents(QFlags< QEventLoop::ProcessEventsFlag > flags)
void append(const T &value)
bool rmdir(const QString &dirName) const
QString & insert(int position, QChar ch)
static QString filePath(const QString &baseName)
const_iterator constEnd() const
static const char appName[]
int kdemain(int argc, char **argv)
int removeAll(const T &value)
const char * constData() const
void addData(const char *data, int length)
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const
qint64 read(char *data, qint64 maxSize)
uint qHash(const CacheIndex &ci)
static bool readTextHeader(QFile *file, CacheFileInfo *fi, OperationMode mode)
QDateTime lastModified() const
QByteArray mid(int pos, int len) const
virtual bool open(QFlags< QIODevice::OpenModeFlag > mode)
static CacheCleanerCommand readCommand(const QByteArray &cmd, CacheFileInfo *fi)
iterator erase(iterator pos)
const T value(const Key &key) const
static void removeOldFiles()
static bool readBinaryHeader(const QByteArray &d, CacheFileInfo *fi)
bool contains(const T &value) const
const_iterator constBegin() const
static const int s_hashedUrlBits
QByteArray toLatin1() const
static QString locateLocal(const char *type, const QString &filename, const KComponentData &cData=KGlobal::mainComponent())
QStringList entryList(QFlags< QDir::Filter > filters, QFlags< QDir::SortFlag > sort) const
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))
QByteArray result() const
QString fromLatin1(const char *str, int size)
QString getOption(const QByteArray &option) const
static QString dateString(qint64 date)
static bool readLineChecked(QIODevice *dev, QByteArray *line)
LocalSocketState state() const
bool endsWith(const QByteArray &ba) const
qint64 readLine(char *data, qint64 maxSize)
static const int s_hashedUrlNibbles
static const char appFullName[]