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

KIOSlave

  • kioslave
  • http
http_cache_cleaner.cpp
Go to the documentation of this file.
1 /*
2 This file is part of KDE
3 
4  Copyright (C) 1999-2000 Waldo Bastian (bastian@kde.org)
5  Copyright (C) 2009 Andreas Hartmetz (ahartmetz@gmail.com)
6 
7 Permission is hereby granted, free of charge, to any person obtaining a copy
8 of this software and associated documentation files (the "Software"), to deal
9 in the Software without restriction, including without limitation the rights
10 to use, copy, modify, merge, publish, distribute, and/or sell
11 copies of the Software, and to permit persons to whom the Software is
12 furnished to do so, subject to the following conditions:
13 
14 The above copyright notice and this permission notice shall be included in
15 all copies or substantial portions of the Software.
16 
17 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
21 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 */
24 //----------------------------------------------------------------------------
25 //
26 // KDE HTTP Cache cleanup tool
27 
28 #include <cstring>
29 #include <time.h>
30 #include <stdlib.h>
31 #include <zlib.h>
32 
33 #include <QtCore/QDir>
34 #include <QtCore/QString>
35 #include <QtCore/QTime>
36 #include <QtDBus/QtDBus>
37 #include <QtNetwork/QLocalServer>
38 #include <QtNetwork/QLocalSocket>
39 
40 #include <kcmdlineargs.h>
41 #include <kcomponentdata.h>
42 #include <kdatetime.h>
43 #include <kdebug.h>
44 #include <kglobal.h>
45 #include <klocale.h>
46 #include <kprotocolmanager.h>
47 #include <kstandarddirs.h>
48 
49 #include <unistd.h>
50 
51 time_t g_currentDate;
52 int g_maxCacheAge;
53 qint64 g_maxCacheSize;
54 
55 static const char appFullName[] = "org.kde.kio_http_cache_cleaner";
56 static const char appName[] = "kio_http_cache_cleaner";
57 
58 // !START OF SYNC!
59 // Keep the following in sync with the cache code in http.cpp
60 
61 static const int s_hashedUrlBits = 160; // this number should always be divisible by eight
62 static const int s_hashedUrlNibbles = s_hashedUrlBits / 4;
63 static const int s_hashedUrlBytes = s_hashedUrlBits / 8;
64 
65 static const char version[] = "A\n";
66 
67 // never instantiated, on-disk / wire format only
68 struct SerializedCacheFileInfo {
69 // from http.cpp
70  quint8 version[2];
71  quint8 compression; // for now fixed to 0
72  quint8 reserved; // for now; also alignment
73  static const int useCountOffset = 4;
74  qint32 useCount;
75  qint64 servedDate;
76  qint64 lastModifiedDate;
77  qint64 expireDate;
78  qint32 bytesCached;
79  static const int size = 36;
80 
81  QString url;
82  QString etag;
83  QString mimeType;
84  QStringList responseHeaders; // including status response like "HTTP 200 OK"
85 };
86 
87 static QString dateString(qint64 date)
88 {
89  KDateTime dt;
90  dt.setTime_t(date);
91  return dt.toString(KDateTime::ISODate);
92 }
93 
94 struct MiniCacheFileInfo {
95 // data from cache entry file, or from scoreboard file
96  qint32 useCount;
97 // from filesystem
98  qint64 lastUsedDate;
99  qint32 sizeOnDisk;
100  // we want to delete the least "useful" files and we'll have to sort a list for that...
101  bool operator<(const MiniCacheFileInfo &other) const;
102  void debugPrint() const
103  {
104  kDebug(7113) << "useCount:" << useCount
105  << "\nlastUsedDate:" << lastUsedDate
106  << "\nsizeOnDisk:" << sizeOnDisk << '\n';
107  }
108 };
109 
110 struct CacheFileInfo : MiniCacheFileInfo {
111  quint8 version[2];
112  quint8 compression; // for now fixed to 0
113  quint8 reserved; // for now; also alignment
114 
115 
116  qint64 servedDate;
117  qint64 lastModifiedDate;
118  qint64 expireDate;
119  qint32 bytesCached;
120 
121  QString baseName;
122  QString url;
123  QString etag;
124  QString mimeType;
125  QStringList responseHeaders; // including status response like "HTTP 200 OK"
126 
127  void prettyPrint() const
128  {
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) {
140  out << h << '\n';
141  }
142  }
143 };
144 
145 
146 bool MiniCacheFileInfo::operator<(const MiniCacheFileInfo &other) const
147 {
148  const int thisUseful = useCount / qMax(g_currentDate - lastUsedDate, qint64(1));
149  const int otherUseful = other.useCount / qMax(g_currentDate - other.lastUsedDate, qint64(1));
150  return thisUseful < otherUseful;
151 }
152 
153 bool CacheFileInfoPtrLessThan(const CacheFileInfo *cf1, const CacheFileInfo *cf2)
154 {
155  return *cf1 < *cf2;
156 }
157 
158 enum OperationMode {
159  CleanCache = 0,
160  DeleteCache,
161  FileInfo
162 };
163 
164 static bool timeSizeFits(qint64 intTime)
165 {
166  time_t tTime = static_cast<time_t>(intTime);
167  qint64 check = static_cast<qint64>(tTime);
168  return check == intTime;
169 }
170 
171 static bool readBinaryHeader(const QByteArray &d, CacheFileInfo *fi)
172 {
173  if (d.size() < SerializedCacheFileInfo::size) {
174  kDebug(7113) << "readBinaryHeader(): file too small?";
175  return false;
176  }
177  QDataStream stream(d);
178  stream.setVersion(QDataStream::Qt_4_5);
179 
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";
184  return false;
185  }
186  stream >> fi->compression;
187  stream >> fi->reserved;
188 
189  stream >> fi->useCount;
190 
191  stream >> fi->servedDate;
192  stream >> fi->lastModifiedDate;
193  stream >> fi->expireDate;
194  bool timeSizeOk = timeSizeFits(fi->servedDate) && timeSizeFits(fi->lastModifiedDate) &&
195  timeSizeFits(fi->expireDate);
196 
197  stream >> fi->bytesCached;
198  return timeSizeOk;
199 }
200 
201 static QString filenameFromUrl(const QByteArray &url)
202 {
203  QCryptographicHash hash(QCryptographicHash::Sha1);
204  hash.addData(url);
205  return QString::fromLatin1(hash.result().toHex());
206 }
207 
208 static QString filePath(const QString &baseName)
209 {
210  QString cacheDirName = KGlobal::dirs()->saveLocation("cache", "http");
211  if (!cacheDirName.endsWith('/')) {
212  cacheDirName.append('/');
213  }
214  return cacheDirName + baseName;
215 }
216 
217 static bool readLineChecked(QIODevice *dev, QByteArray *line)
218 {
219  *line = dev->readLine(8192);
220  // if nothing read or the line didn't fit into 8192 bytes(!)
221  if (line->isEmpty() || !line->endsWith('\n')) {
222  return false;
223  }
224  // we don't actually want the newline!
225  line->chop(1);
226  return true;
227 }
228 
229 static bool readTextHeader(QFile *file, CacheFileInfo *fi, OperationMode mode)
230 {
231  bool ok = true;
232  QByteArray readBuf;
233 
234  ok = ok && readLineChecked(file, &readBuf);
235  fi->url = QString::fromLatin1(readBuf);
236  if (filenameFromUrl(readBuf) != QFileInfo(*file).baseName()) {
237  kDebug(7103) << "You have witnessed a very improbable hash collision!";
238  return false;
239  }
240 
241  // only read the necessary info for cache cleaning. Saves time and (more importantly) memory.
242  if (mode != FileInfo) {
243  return true;
244  }
245 
246  ok = ok && readLineChecked(file, &readBuf);
247  fi->etag = QString::fromLatin1(readBuf);
248 
249  ok = ok && readLineChecked(file, &readBuf);
250  fi->mimeType = QString::fromLatin1(readBuf);
251 
252  // read as long as no error and no empty line found
253  while (true) {
254  ok = ok && readLineChecked(file, &readBuf);
255  if (ok && !readBuf.isEmpty()) {
256  fi->responseHeaders.append(QString::fromLatin1(readBuf));
257  } else {
258  break;
259  }
260  }
261  return ok; // it may still be false ;)
262 }
263 
264 // TODO common include file with http.cpp?
265 enum CacheCleanerCommand {
266  InvalidCommand = 0,
267  CreateFileNotificationCommand,
268  UpdateFileCommand
269 };
270 
271 static bool readCacheFile(const QString &baseName, CacheFileInfo *fi, OperationMode mode)
272 {
273  QFile file(filePath(baseName));
274  file.open(QIODevice::ReadOnly);
275  if (file.openMode() != QIODevice::ReadOnly) {
276  return false;
277  }
278  fi->baseName = baseName;
279 
280  QByteArray header = file.read(SerializedCacheFileInfo::size);
281  // do *not* modify/delete the file if we're in file info mode.
282  if (!(readBinaryHeader(header, fi) && readTextHeader(&file, fi, mode)) && mode != FileInfo) {
283  kDebug(7113) << "read(Text|Binary)Header() returned false, deleting file" << baseName;
284  file.remove();
285  return false;
286  }
287  // get meta-information from the filesystem
288  QFileInfo fileInfo(file);
289  fi->lastUsedDate = fileInfo.lastModified().toTime_t();
290  fi->sizeOnDisk = fileInfo.size();
291  return true;
292 }
293 
294 class Scoreboard;
295 
296 class CacheIndex
297 {
298 public:
299  explicit CacheIndex(const QString &baseName)
300  {
301  QByteArray ba = baseName.toLatin1();
302  const int sz = ba.size();
303  const char *input = ba.constData();
304  Q_ASSERT(sz == s_hashedUrlNibbles);
305 
306  int translated = 0;
307  for (int i = 0; i < sz; i++) {
308  int c = input[i];
309 
310  if (c >= '0' && c <= '9') {
311  translated |= c - '0';
312  } else if (c >= 'a' && c <= 'f') {
313  translated |= c - 'a' + 10;
314  } else {
315  Q_ASSERT(false);
316  }
317 
318  if (i & 1) {
319  // odd index
320  m_index[i >> 1] = translated;
321  translated = 0;
322  } else {
323  translated = translated << 4;
324  }
325  }
326 
327  computeHash();
328  }
329 
330  bool operator==(const CacheIndex &other) const
331  {
332  const bool isEqual = memcmp(m_index, other.m_index, s_hashedUrlBytes) == 0;
333  if (isEqual) {
334  Q_ASSERT(m_hash == other.m_hash);
335  }
336  return isEqual;
337  }
338 
339 private:
340  explicit CacheIndex(const QByteArray &index)
341  {
342  Q_ASSERT(index.length() >= s_hashedUrlBytes);
343  memcpy(m_index, index.constData(), s_hashedUrlBytes);
344  computeHash();
345  }
346 
347  void computeHash()
348  {
349  uint hash = 0;
350  const int ints = s_hashedUrlBytes / sizeof(uint);
351  for (int i = 0; i < ints; i++) {
352  hash ^= reinterpret_cast<uint *>(&m_index[0])[i];
353  }
354  if (const int bytesLeft = s_hashedUrlBytes % sizeof(uint)) {
355  // dead code until a new url hash algorithm or architecture with sizeof(uint) != 4 appears.
356  // we have the luxury of ignoring endianness because the hash is never written to disk.
357  // just merge the bits into the the hash in some way.
358  const int offset = ints * sizeof(uint);
359  for (int i = 0; i < bytesLeft; i++) {
360  hash ^= static_cast<uint>(m_index[offset + i]) << (i * 8);
361  }
362  }
363  m_hash = hash;
364  }
365 
366  friend uint qHash(const CacheIndex &);
367  friend class Scoreboard;
368 
369  quint8 m_index[s_hashedUrlBytes]; // packed binary version of the hexadecimal name
370  uint m_hash;
371 };
372 
373 uint qHash(const CacheIndex &ci)
374 {
375  return ci.m_hash;
376 }
377 
378 
379 static CacheCleanerCommand readCommand(const QByteArray &cmd, CacheFileInfo *fi)
380 {
381  readBinaryHeader(cmd, fi);
382  QDataStream stream(cmd);
383  stream.skipRawData(SerializedCacheFileInfo::size);
384 
385  quint32 ret;
386  stream >> ret;
387 
388  QByteArray baseName;
389  baseName.resize(s_hashedUrlNibbles);
390  stream.readRawData(baseName.data(), s_hashedUrlNibbles);
391  Q_ASSERT(stream.atEnd());
392  fi->baseName = QString::fromLatin1(baseName);
393 
394  Q_ASSERT(ret == CreateFileNotificationCommand || ret == UpdateFileCommand);
395  return static_cast<CacheCleanerCommand>(ret);
396 }
397 
398 
399 // never istantiated, on-disk format only
400 struct ScoreboardEntry {
401 // from scoreboard file
402  quint8 index[s_hashedUrlBytes];
403  static const int indexSize = s_hashedUrlBytes;
404  qint32 useCount;
405 // from scoreboard file, but compared with filesystem to see if scoreboard has current data
406  qint64 lastUsedDate;
407  qint32 sizeOnDisk;
408  static const int size = 36;
409  // we want to delete the least "useful" files and we'll have to sort a list for that...
410  bool operator<(const MiniCacheFileInfo &other) const;
411 };
412 
413 
414 class Scoreboard
415 {
416 public:
417  Scoreboard()
418  {
419  // read in the scoreboard...
420  QFile sboard(filePath(QLatin1String("scoreboard")));
421  sboard.open(QIODevice::ReadOnly);
422  while (true) {
423  QByteArray baIndex = sboard.read(ScoreboardEntry::indexSize);
424  QByteArray baRest = sboard.read(ScoreboardEntry::size - ScoreboardEntry::indexSize);
425  if (baIndex.size() + baRest.size() != ScoreboardEntry::size) {
426  break;
427  }
428 
429  const QString entryBasename = QString::fromLatin1(baIndex.toHex());
430  MiniCacheFileInfo mcfi;
431  if (readAndValidateMcfi(baRest, entryBasename, &mcfi)) {
432  m_scoreboard.insert(CacheIndex(baIndex), mcfi);
433  }
434  }
435  }
436 
437  void writeOut()
438  {
439  // write out the scoreboard
440  QFile sboard(filePath(QLatin1String("scoreboard")));
441  sboard.open(QIODevice::WriteOnly | QIODevice::Truncate);
442  QDataStream stream(&sboard);
443 
444  QHash<CacheIndex, MiniCacheFileInfo>::ConstIterator it = m_scoreboard.constBegin();
445  for (; it != m_scoreboard.constEnd(); ++it) {
446  const char *indexData = reinterpret_cast<const char *>(it.key().m_index);
447  stream.writeRawData(indexData, s_hashedUrlBytes);
448 
449  stream << it.value().useCount;
450  stream << it.value().lastUsedDate;
451  stream << it.value().sizeOnDisk;
452  }
453  }
454 
455  bool fillInfo(const QString &baseName, MiniCacheFileInfo *mcfi)
456  {
457  QHash<CacheIndex, MiniCacheFileInfo>::ConstIterator it =
458  m_scoreboard.constFind(CacheIndex(baseName));
459  if (it == m_scoreboard.constEnd()) {
460  return false;
461  }
462  *mcfi = it.value();
463  return true;
464  }
465 
466  int runCommand(const QByteArray &cmd)
467  {
468  // execute the command; return number of bytes if a new file was created, zero otherwise.
469  Q_ASSERT(cmd.size() == 80);
470  CacheFileInfo fi;
471  const CacheCleanerCommand ccc = readCommand(cmd, &fi);
472  QString fileName = filePath(fi.baseName);
473 
474  switch (ccc) {
475  case CreateFileNotificationCommand:
476  kDebug(7113) << "CreateNotificationCommand for" << fi.baseName;
477  if (!readBinaryHeader(cmd, &fi)) {
478  return 0;
479  }
480  break;
481 
482  case UpdateFileCommand: {
483  kDebug(7113) << "UpdateFileCommand for" << fi.baseName;
484  QFile file(fileName);
485  file.open(QIODevice::ReadWrite);
486 
487  CacheFileInfo fiFromDisk;
488  QByteArray header = file.read(SerializedCacheFileInfo::size);
489  if (!readBinaryHeader(header, &fiFromDisk) || fiFromDisk.bytesCached != fi.bytesCached) {
490  return 0;
491  }
492 
493  // adjust the use count, to make sure that we actually count up. (slaves read the file
494  // asynchronously...)
495  const quint32 newUseCount = fiFromDisk.useCount + 1;
496  QByteArray newHeader = cmd.mid(0, SerializedCacheFileInfo::size);
497  {
498  QDataStream stream(&newHeader, QIODevice::WriteOnly);
499  stream.skipRawData(SerializedCacheFileInfo::useCountOffset);
500  stream << newUseCount;
501  }
502 
503  file.seek(0);
504  file.write(newHeader);
505  file.close();
506 
507  if (!readBinaryHeader(newHeader, &fi)) {
508  return 0;
509  }
510  break;
511  }
512 
513  default:
514  kDebug(7113) << "received invalid command";
515  return 0;
516  }
517 
518  QFileInfo fileInfo(fileName);
519  fi.lastUsedDate = fileInfo.lastModified().toTime_t();
520  fi.sizeOnDisk = fileInfo.size();
521  fi.debugPrint();
522  // a CacheFileInfo is-a MiniCacheFileInfo which enables the following assignment...
523  add(fi);
524  // finally, return cache dir growth (only relevant if a file was actually created!)
525  return ccc == CreateFileNotificationCommand ? fi.sizeOnDisk : 0;
526  }
527 
528  void add(const CacheFileInfo &fi)
529  {
530  m_scoreboard[CacheIndex(fi.baseName)] = fi;
531  }
532 
533  void remove(const QString &basename)
534  {
535  m_scoreboard.remove(CacheIndex(basename));
536  }
537 
538  // keep memory usage reasonably low - otherwise entries of nonexistent files don't hurt.
539  void maybeRemoveStaleEntries(const QList<CacheFileInfo *> &fiList)
540  {
541  // don't bother when there are a few bogus entries
542  if (m_scoreboard.count() < fiList.count() + 100) {
543  return;
544  }
545  kDebug(7113) << "we have too many fake/stale entries, cleaning up...";
546  QSet<CacheIndex> realFiles;
547  Q_FOREACH (CacheFileInfo *fi, fiList) {
548  realFiles.insert(CacheIndex(fi->baseName));
549  }
550  QHash<CacheIndex, MiniCacheFileInfo>::Iterator it = m_scoreboard.begin();
551  while (it != m_scoreboard.end()) {
552  if (realFiles.contains(it.key())) {
553  ++it;
554  } else {
555  it = m_scoreboard.erase(it);
556  }
557  }
558  }
559 
560 private:
561  bool readAndValidateMcfi(const QByteArray &rawData, const QString &basename, MiniCacheFileInfo *mcfi)
562  {
563  QDataStream stream(rawData);
564  stream >> mcfi->useCount;
565  // check those against filesystem
566  stream >> mcfi->lastUsedDate;
567  stream >> mcfi->sizeOnDisk;
568 
569  QFileInfo fileInfo(filePath(basename));
570  if (!fileInfo.exists()) {
571  return false;
572  }
573  bool ok = true;
574  ok = ok && fileInfo.lastModified().toTime_t() == mcfi->lastUsedDate;
575  ok = ok && fileInfo.size() == mcfi->sizeOnDisk;
576  if (!ok) {
577  // size or last-modified date not consistent with entry file; reload useCount
578  // note that avoiding to open the file is the whole purpose of the scoreboard - we only
579  // open the file if we really have to.
580  QFile entryFile(fileInfo.absoluteFilePath());
581  entryFile.open(QIODevice::ReadOnly);
582  if (entryFile.size() < SerializedCacheFileInfo::size) {
583  return false;
584  }
585  QDataStream stream(&entryFile);
586  stream.skipRawData(SerializedCacheFileInfo::useCountOffset);
587 
588  stream >> mcfi->useCount;
589  mcfi->lastUsedDate = fileInfo.lastModified().toTime_t();
590  mcfi->sizeOnDisk = fileInfo.size();
591  ok = true;
592  }
593  return ok;
594  }
595 
596  QHash<CacheIndex, MiniCacheFileInfo> m_scoreboard;
597 };
598 
599 
600 // Keep the above in sync with the cache code in http.cpp
601 // !END OF SYNC!
602 
603 // remove files and directories used by earlier versions of the HTTP cache.
604 static void removeOldFiles()
605 {
606  const char *oldDirs = "0abcdefghijklmnopqrstuvwxyz";
607  const int n = strlen(oldDirs);
608  QDir cacheRootDir(filePath(QString()));
609  for (int i = 0; i < n; i++) {
610  QString dirName = QString::fromLatin1(&oldDirs[i], 1);
611  // delete files in directory...
612  Q_FOREACH (const QString &baseName, QDir(filePath(dirName)).entryList()) {
613  QFile::remove(filePath(dirName + '/' + baseName));
614  }
615  // delete the (now hopefully empty!) directory itself
616  cacheRootDir.rmdir(dirName);
617  }
618  QFile::remove(filePath(QLatin1String("cleaned")));
619 }
620 
621 class CacheCleaner
622 {
623 public:
624  CacheCleaner(const QDir &cacheDir)
625  : m_totalSizeOnDisk(0)
626  {
627  kDebug(7113);
628  m_fileNameList = cacheDir.entryList();
629  }
630 
631  // Delete some of the files that need to be deleted. Return true when done, false otherwise.
632  // This makes interleaved cleaning / serving ioslaves possible.
633  bool processSlice(Scoreboard *scoreboard = 0)
634  {
635  QTime t;
636  t.start();
637  // phase one: gather information about cache files
638  if (!m_fileNameList.isEmpty()) {
639  while (t.elapsed() < 100 && !m_fileNameList.isEmpty()) {
640  QString baseName = m_fileNameList.takeFirst();
641  // check if the filename is of the $s_hashedUrlNibbles letters, 0...f type
642  if (baseName.length() < s_hashedUrlNibbles) {
643  continue;
644  }
645  bool nameOk = true;
646  for (int i = 0; i < s_hashedUrlNibbles && nameOk; i++) {
647  QChar c = baseName[i];
648  nameOk = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f');
649  }
650  if (!nameOk) {
651  continue;
652  }
653  if (baseName.length() > s_hashedUrlNibbles) {
654  if (g_currentDate - QFileInfo(filePath(baseName)).lastModified().toTime_t() > 15*60) {
655  // it looks like a temporary file that hasn't been touched in > 15 minutes...
656  QFile::remove(filePath(baseName));
657  }
658  // the temporary file might still be written to, leave it alone
659  continue;
660  }
661 
662  CacheFileInfo *fi = new CacheFileInfo();
663  fi->baseName = baseName;
664 
665  bool gotInfo = false;
666  if (scoreboard) {
667  gotInfo = scoreboard->fillInfo(baseName, fi);
668  }
669  if (!gotInfo) {
670  gotInfo = readCacheFile(baseName, fi, CleanCache);
671  if (gotInfo && scoreboard) {
672  scoreboard->add(*fi);
673  }
674  }
675  if (gotInfo) {
676  m_fiList.append(fi);
677  m_totalSizeOnDisk += fi->sizeOnDisk;
678  } else {
679  delete fi;
680  }
681  }
682  kDebug(7113) << "total size of cache files is" << m_totalSizeOnDisk;
683 
684  if (m_fileNameList.isEmpty()) {
685  // final step of phase one
686  qSort(m_fiList.begin(), m_fiList.end(), CacheFileInfoPtrLessThan);
687  }
688  return false;
689  }
690 
691  // phase two: delete files until cache is under maximum allowed size
692 
693  // TODO: delete files larger than allowed for a single file
694  while (t.elapsed() < 100) {
695  if (m_totalSizeOnDisk <= g_maxCacheSize || m_fiList.isEmpty()) {
696  kDebug(7113) << "total size of cache files after cleaning is" << m_totalSizeOnDisk;
697  if (scoreboard) {
698  scoreboard->maybeRemoveStaleEntries(m_fiList);
699  scoreboard->writeOut();
700  }
701  qDeleteAll(m_fiList);
702  m_fiList.clear();
703  return true;
704  }
705  CacheFileInfo *fi = m_fiList.takeFirst();
706  QString filename = filePath(fi->baseName);
707  if (QFile::remove(filename)) {
708  m_totalSizeOnDisk -= fi->sizeOnDisk;
709  if (scoreboard) {
710  scoreboard->remove(fi->baseName);
711  }
712  }
713  delete fi;
714  }
715  return false;
716  }
717 
718 private:
719  QStringList m_fileNameList;
720  QList<CacheFileInfo *> m_fiList;
721  qint64 m_totalSizeOnDisk;
722 };
723 
724 
725 extern "C" KDE_EXPORT int kdemain(int argc, char **argv)
726 {
727  KCmdLineArgs::init(argc, argv, appName, "kdelibs4",
728  ki18n("KDE HTTP cache maintenance tool"), version,
729  ki18n("KDE HTTP cache maintenance tool"), KCmdLineArgs::CmdLineArgNone);
730 
731  KCmdLineOptions options;
732  options.add("clear-all", ki18n("Empty the cache"));
733  options.add("file-info <filename>", ki18n("Display information about cache file"));
734 
735  KCmdLineArgs::addCmdLineOptions( options );
736  KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
737  KComponentData componentData(appName);
738 
739  // we need a QCoreApplication so QCoreApplication::processEvents() works as intended
740  QCoreApplication app(argc, argv);
741 
742  OperationMode mode = CleanCache;
743  if (args->isSet("clear-all")) {
744  mode = DeleteCache;
745  } else if (args->isSet("file-info")) {
746  mode = FileInfo;
747  }
748 
749  // file info mode: no scanning of directories, just output info and exit.
750  if (mode == FileInfo) {
751  CacheFileInfo fi;
752  if (!readCacheFile(args->getOption("file-info"), &fi, mode)) {
753  return 1;
754  }
755  fi.prettyPrint();
756  return 0;
757  }
758 
759  // make sure we're the only running instance of the cleaner service
760  if (mode == CleanCache) {
761  if (!QDBusConnection::sessionBus().isConnected()) {
762  QDBusError error(QDBusConnection::sessionBus().lastError());
763  fprintf(stderr, "%s: Could not connect to D-Bus! (%s: %s)\n", appName,
764  qPrintable(error.name()), qPrintable(error.message()));
765  return 1;
766  }
767 
768  if (!QDBusConnection::sessionBus().registerService(appFullName)) {
769  fprintf(stderr, "%s: Already running!\n", appName);
770  return 0;
771  }
772  }
773 
774 
775  g_currentDate = time(0);
776  g_maxCacheAge = KProtocolManager::maxCacheAge();
777  g_maxCacheSize = mode == DeleteCache ? -1 : KProtocolManager::maxCacheSize() * 1024;
778 
779  QString cacheDirName = KGlobal::dirs()->saveLocation("cache", "http");
780  QDir cacheDir(cacheDirName);
781  if (!cacheDir.exists()) {
782  fprintf(stderr, "%s: '%s' does not exist.\n", appName, qPrintable(cacheDirName));
783  return 0;
784  }
785 
786  removeOldFiles();
787 
788  if (mode == DeleteCache) {
789  QTime t;
790  t.start();
791  cacheDir.refresh();
792  //qDebug() << "time to refresh the cacheDir QDir:" << t.elapsed();
793  CacheCleaner cleaner(cacheDir);
794  while (!cleaner.processSlice()) { }
795  return 0;
796  }
797 
798  QLocalServer lServer;
799  QString socketFileName = KStandardDirs::locateLocal("socket", "kio_http_cache_cleaner");
800  // we need to create the file by opening the socket, otherwise it won't work
801  QFile::remove(socketFileName);
802  lServer.listen(socketFileName);
803  QList<QLocalSocket *> sockets;
804  int newBytesCounter = INT_MAX; // force cleaner run on startup
805 
806  Scoreboard scoreboard;
807  CacheCleaner *cleaner = 0;
808  while (true) {
809  g_currentDate = time(0);
810  if (cleaner) {
811  QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
812  } else {
813  // We will not immediately know when a socket was disconnected. Causes:
814  // - WaitForMoreEvents does not make processEvents() return when a socket disconnects
815  // - WaitForMoreEvents *and* a timeout is not possible.
816  QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents);
817  }
818  if (!lServer.isListening()) {
819  return 1;
820  }
821  lServer.waitForNewConnection(1);
822 
823  while (QLocalSocket *sock = lServer.nextPendingConnection()) {
824  sock->waitForConnected();
825  sockets.append(sock);
826  }
827 
828  for (int i = 0; i < sockets.size(); i++) {
829  QLocalSocket *sock = sockets[i];
830  if (sock->state() != QLocalSocket::ConnectedState) {
831  if (sock->state() != QLocalSocket::UnconnectedState) {
832  sock->waitForDisconnected();
833  }
834  delete sock;
835  sockets.removeAll(sock);
836  i--;
837  continue;
838  }
839  sock->waitForReadyRead(0);
840  while (true) {
841  QByteArray recv = sock->read(80);
842  if (recv.isEmpty()) {
843  break;
844  }
845  Q_ASSERT(recv.size() == 80);
846  newBytesCounter += scoreboard.runCommand(recv);
847  }
848  }
849 
850  // interleave cleaning with serving ioslaves to reduce "garbage collection pauses"
851  if (cleaner) {
852  if (cleaner->processSlice(&scoreboard)) {
853  // that was the last slice, done
854  delete cleaner;
855  cleaner = 0;
856  }
857  } else if (newBytesCounter > g_maxCacheSize / 8) {
858  cacheDir.refresh();
859  cleaner = new CacheCleaner(cacheDir);
860  newBytesCounter = 0;
861  }
862  }
863  return 0;
864 }
This file is part of the KDE documentation.
Documentation copyright © 1996-2013 The KDE developers.
Generated on Thu Feb 21 2013 11:13:57 by doxygen 1.8.1 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KIOSlave

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

kdelibs-4.8.5 API Reference

Skip menu "kdelibs-4.8.5 API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • 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
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal