00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021 #include "kpixmapcache.h"
00022
00023 #include <QtCore/QString>
00024 #include <QtGui/QPixmap>
00025 #include <QtCore/QFile>
00026 #include <QtCore/QDataStream>
00027 #include <QtCore/QFileInfo>
00028 #include <QtCore/QDateTime>
00029 #include <QtGui/QPixmapCache>
00030 #include <QtCore/QtGlobal>
00031 #include <QtGui/QPainter>
00032 #include <QtCore/QQueue>
00033 #include <QtCore/QThread>
00034 #include <QtCore/QTimer>
00035 #include <QtCore/QMutex>
00036 #include <QtCore/QMutexLocker>
00037 #include <QtCore/QList>
00038
00039 #include <kglobal.h>
00040 #include <kstandarddirs.h>
00041 #include <kdebug.h>
00042 #include <klockfile.h>
00043 #include <ksvgrenderer.h>
00044 #include <kdefakes.h>
00045
00046 #include <config.h>
00047
00048 #include <time.h>
00049 #include <unistd.h>
00050 #include <sys/types.h>
00051 #include <string.h>
00052
00053 #if defined(HAVE_MADVISE)
00054 #include <sys/mman.h>
00055 #endif
00056
00057
00058
00059 #ifdef Q_OS_SOLARIS
00060 #ifndef _XPG_4_2
00061 extern "C" int madvise(caddr_t addr, size_t len, int advice);
00062 #endif
00063 #endif
00064
00065 #define KPIXMAPCACHE_VERSION 0x000208
00066
00067 namespace {
00068
00069 class KPCLockFile
00070 {
00071 public:
00072 KPCLockFile(const QString& filename)
00073 {
00074 mValid = false;
00075 mLockFile = new KLockFile(filename);
00076
00077 KLockFile::LockResult result;
00078 for (int i = 0; i < 5; i++) {
00079 result = mLockFile->lock(KLockFile::NoBlockFlag);
00080 if (result == KLockFile::LockOK) {
00081 mValid = true;
00082 break;
00083 }
00084 usleep(5*1000);
00085 }
00086
00087 if (!mValid) {
00088 kError() << "Failed to lock file" << filename << ", last result =" << result;
00089 }
00090 }
00091 ~KPCLockFile()
00092 {
00093 unlock();
00094 delete mLockFile;
00095 }
00096
00097 void unlock()
00098 {
00099 if (mValid) {
00100 mLockFile->unlock();
00101 mValid = false;
00102 }
00103 }
00104
00105 bool isValid() const { return mValid; }
00106
00107 private:
00108 bool mValid;
00109 KLockFile* mLockFile;
00110 };
00111
00112
00113
00114
00115
00116
00117
00118
00119 static const char KPC_MAGIC[] = "KDE PIXMAP CACHE DEUX";
00120 struct KPixmapCacheDataHeader
00121 {
00122
00123
00124 char magic[sizeof(KPC_MAGIC) - 1];
00125 quint32 cacheVersion;
00126 quint32 size;
00127 };
00128
00129 struct KPixmapCacheIndexHeader
00130 {
00131
00132
00133 char magic[sizeof(KPC_MAGIC) - 1];
00134 quint32 cacheVersion;
00135 quint32 size;
00136
00137
00138 quint32 cacheId;
00139 time_t timestamp;
00140 };
00141
00142 class KPCMemoryDevice : public QIODevice
00143 {
00144 public:
00145 KPCMemoryDevice(char* start, quint32* size, quint32 available);
00146 virtual ~KPCMemoryDevice();
00147
00148 virtual qint64 size() const { return *mSize; }
00149 void setSize(quint32 s) { *mSize = s; }
00150 virtual bool seek(qint64 pos);
00151
00152 protected:
00153 virtual qint64 readData(char* data, qint64 maxSize);
00154 virtual qint64 writeData(const char* data, qint64 maxSize);
00155
00156 private:
00157 char* mMemory;
00158 KPixmapCacheIndexHeader *mHeader;
00159 quint32* mSize;
00160 quint32 mInitialSize;
00161 qint64 mAvailable;
00162 quint32 mPos;
00163 };
00164
00165 KPCMemoryDevice::KPCMemoryDevice(char* start, quint32* size, quint32 available) : QIODevice()
00166 {
00167 mMemory = start;
00168 mHeader = reinterpret_cast<KPixmapCacheIndexHeader *>(start);
00169 mSize = size;
00170 mAvailable = available;
00171 mPos = 0;
00172
00173 open(QIODevice::ReadWrite);
00174
00175
00176 *mSize = mHeader->size;
00177
00178 mInitialSize = *mSize;
00179 }
00180
00181 KPCMemoryDevice::~KPCMemoryDevice()
00182 {
00183 if (*mSize != mInitialSize) {
00184
00185 mHeader->size = *mSize;
00186 }
00187 }
00188
00189 bool KPCMemoryDevice::seek(qint64 pos)
00190 {
00191 if (pos < 0 || pos > *mSize) {
00192 return false;
00193 }
00194 mPos = pos;
00195 return QIODevice::seek(pos);
00196 }
00197
00198 qint64 KPCMemoryDevice::readData(char* data, qint64 len)
00199 {
00200 len = qMin(len, qint64(*mSize) - mPos);
00201 if (len <= 0) {
00202 return 0;
00203 }
00204 memcpy(data, mMemory + mPos, len);
00205 mPos += len;
00206 return len;
00207 }
00208
00209 qint64 KPCMemoryDevice::writeData(const char* data, qint64 len)
00210 {
00211 if (mPos + len > mAvailable) {
00212 kError() << "Overflow of" << mPos+len - mAvailable;
00213 return -1;
00214 }
00215 memcpy(mMemory + mPos, (uchar*)data, len);
00216 mPos += len;
00217 *mSize = qMax(*mSize, mPos);
00218 return len;
00219 }
00220
00221
00222 }
00223
00224 class KPixmapCache::Private
00225 {
00226 public:
00227 Private(KPixmapCache* q);
00228 ~Private();
00229
00230
00231
00232 QIODevice* indexDevice();
00233 QIODevice* dataDevice();
00234
00235
00236
00237 bool mmapFiles();
00238 void unmmapFiles();
00239
00240
00241 void invalidateMmapFiles();
00242
00243
00244 static QList<KPixmapCache::Private *> mCaches;
00245
00246 int findOffset(const QString& key);
00247 int binarySearchKey(QDataStream& stream, const QString& key, int start);
00248 void writeIndexEntry(QDataStream& stream, const QString& key, int dataoffset);
00249
00250 bool checkLockFile();
00251 bool checkFileVersion(const QString& filename);
00252 bool loadIndexHeader();
00253 bool loadDataHeader();
00254
00255 bool removeEntries(int newsize);
00256 bool scheduleRemoveEntries(int newsize);
00257
00258 void init();
00259 bool loadData(int offset, QPixmap& pix);
00260 int writeData(const QString& key, const QPixmap& pix);
00261 void writeIndex(const QString& key, int offset);
00262
00263
00264
00265 QString indexKey(const QString& key);
00266
00267
00268 KPixmapCache* q;
00269
00270 quint32 mHeaderSize;
00271 quint32 mIndexRootOffset;
00272
00273 QString mName;
00274 QString mIndexFile;
00275 QString mDataFile;
00276 QString mLockFileName;
00277 QMutex mMutex;
00278
00279 quint32 mTimestamp;
00280 quint32 mCacheId;
00281 int mCacheLimit;
00282 RemoveStrategy mRemoveStrategy:4;
00283 bool mUseQPixmapCache:4;
00284
00285 bool mInited:8;
00286 bool mEnabled:8;
00287 bool mValid:8;
00288
00289
00290 struct MmapInfo
00291 {
00292 MmapInfo() { file = 0; indexHeader = 0; }
00293 QFile* file;
00294
00295
00296 KPixmapCacheIndexHeader *indexHeader;
00297
00298 quint32 size;
00299 quint32 available;
00300 };
00301 MmapInfo mIndexMmapInfo;
00302 MmapInfo mDataMmapInfo;
00303
00304 bool mmapFile(const QString& filename, MmapInfo* info, int newsize);
00305 void unmmapFile(MmapInfo* info);
00306
00307
00308
00309 class KPixmapCacheEntry
00310 {
00311 public:
00312 KPixmapCacheEntry(int indexoffset_, const QString& key_, int dataoffset_,
00313 int pos_, quint32 timesused_, quint32 lastused_)
00314 {
00315 indexoffset = indexoffset_;
00316 key = key_;
00317 dataoffset = dataoffset_;
00318 pos = pos_;
00319 timesused = timesused_;
00320 lastused = lastused_;
00321 }
00322
00323 int indexoffset;
00324 QString key;
00325 int dataoffset;
00326
00327 int pos;
00328 quint32 timesused;
00329 quint32 lastused;
00330 };
00331
00332
00333 static bool compareEntriesByAge(const KPixmapCacheEntry& a, const KPixmapCacheEntry& b)
00334 {
00335 return a.pos > b.pos;
00336 }
00337 static bool compareEntriesByTimesUsed(const KPixmapCacheEntry& a, const KPixmapCacheEntry& b)
00338 {
00339 return a.timesused > b.timesused;
00340 }
00341 static bool compareEntriesByLastUsed(const KPixmapCacheEntry& a, const KPixmapCacheEntry& b)
00342 {
00343 return a.lastused > b.lastused;
00344 }
00345
00346
00347
00348 class RemovalThread : public QThread
00349 {
00350 public:
00351 RemovalThread(KPixmapCache::Private* _d) : QThread()
00352 {
00353 d = _d;
00354 mRemovalScheduled = false;
00355 }
00356 ~RemovalThread()
00357 {
00358 }
00359
00360 void scheduleRemoval(int newsize)
00361 {
00362 mNewSize = newsize;
00363 if (!mRemovalScheduled) {
00364 QTimer::singleShot(5000, this, SLOT(start()));
00365 mRemovalScheduled = true;
00366 }
00367 }
00368
00369 protected:
00370 virtual void run()
00371 {
00372 mRemovalScheduled = false;
00373 kDebug(264) << "starting";
00374 d->removeEntries(mNewSize);
00375 kDebug(264) << "done";
00376 }
00377
00378 private:
00379 bool mRemovalScheduled;
00380 int mNewSize;
00381 KPixmapCache::Private* d;
00382 };
00383 RemovalThread* mRemovalThread;
00384 };
00385
00386
00387 QList<KPixmapCache::Private *> KPixmapCache::Private::mCaches;
00388
00389 KPixmapCache::Private::Private(KPixmapCache* _q)
00390 {
00391 q = _q;
00392 mCaches.append(this);
00393 mRemovalThread = 0;
00394 }
00395
00396 KPixmapCache::Private::~Private()
00397 {
00398 mCaches.removeAll(this);
00399 }
00400
00401 bool KPixmapCache::Private::mmapFiles()
00402 {
00403 unmmapFiles();
00404 if (!q->isValid()) {
00405 return false;
00406 }
00407
00408
00409 int cacheLimit = mCacheLimit > 0 ? mCacheLimit : 100 * 1024;
00410 if (!mmapFile(mIndexFile, &mIndexMmapInfo, (int)(cacheLimit * 0.4 + 100) * 1024)) {
00411 q->setValid(false);
00412 return false;
00413 }
00414
00415 if (!mmapFile(mDataFile, &mDataMmapInfo, (int)(cacheLimit * 1.5 + 500) * 1024)) {
00416 unmmapFile(&mIndexMmapInfo);
00417 q->setValid(false);
00418 return false;
00419 }
00420
00421 return true;
00422 }
00423
00424 void KPixmapCache::Private::unmmapFiles()
00425 {
00426 unmmapFile(&mIndexMmapInfo);
00427 unmmapFile(&mDataMmapInfo);
00428 }
00429
00430 void KPixmapCache::Private::invalidateMmapFiles()
00431 {
00432 if (!q->isValid())
00433 return;
00434
00435 if (mIndexMmapInfo.file) {
00436 kDebug(264) << "Invalidating cache";
00437 mIndexMmapInfo.indexHeader->cacheId = 0;
00438 }
00439 }
00440
00441 bool KPixmapCache::Private::mmapFile(const QString& filename, MmapInfo* info, int newsize)
00442 {
00443 info->file = new QFile(filename);
00444 if (!info->file->open(QIODevice::ReadWrite)) {
00445 kDebug(264) << "Couldn't open" << filename;
00446 delete info->file;
00447 info->file = 0;
00448 return false;
00449 }
00450
00451 if (!info->size) {
00452 info->size = info->file->size();
00453 }
00454 info->available = newsize;
00455
00456
00457
00458 if (info->file->size() < info->available && ftruncate(info->file->handle(), info->available) < 0) {
00459 kError(264) << "Couldn't resize" << filename << "to" << newsize;
00460 delete info->file;
00461 info->file = 0;
00462 return false;
00463 }
00464
00465
00466 void *indexMem = info->file->map(0, info->available);
00467 if (indexMem == 0) {
00468 kError() << "mmap failed for" << filename;
00469 delete info->file;
00470 info->file = 0;
00471 return false;
00472 }
00473 info->indexHeader = reinterpret_cast<KPixmapCacheIndexHeader *>(indexMem);
00474 #ifdef HAVE_MADVISE
00475 posix_madvise(indexMem, info->size, POSIX_MADV_WILLNEED);
00476 #endif
00477
00478 info->file->close();
00479
00480
00481
00482 if(0 == info->indexHeader->size) {
00483
00484
00485 info->indexHeader->size = mHeaderSize;
00486 info->size = info->indexHeader->size;
00487 }
00488
00489 return true;
00490 }
00491
00492 void KPixmapCache::Private::unmmapFile(MmapInfo* info)
00493 {
00494 if (info->file) {
00495 info->file->unmap(reinterpret_cast<uchar*>(info->indexHeader));
00496 info->indexHeader = 0;
00497 info->available = 0;
00498 info->size = 0;
00499
00500 delete info->file;
00501 info->file = 0;
00502 }
00503 }
00504
00505
00506 QIODevice* KPixmapCache::Private::indexDevice()
00507 {
00508 QIODevice* device = 0;
00509
00510 if (mIndexMmapInfo.file) {
00511
00512 QFileInfo fi(mIndexFile);
00513
00514 if (!fi.exists() || fi.size() != mIndexMmapInfo.available) {
00515 kDebug(264) << "File size has changed, re-initializing.";
00516 q->recreateCacheFiles();
00517 }
00518
00519 fi.refresh();
00520 if(fi.exists() && fi.size() == mIndexMmapInfo.available) {
00521
00522 device = new KPCMemoryDevice(
00523 reinterpret_cast<char*>(mIndexMmapInfo.indexHeader),
00524 &mIndexMmapInfo.size, mIndexMmapInfo.available);
00525 }
00526
00527
00528
00529 if(!q->isValid()) {
00530 delete device;
00531 return 0;
00532 }
00533 }
00534
00535 if (!device) {
00536 QFile* file = new QFile(mIndexFile);
00537 if (!file->exists() || (size_t) file->size() < sizeof(KPixmapCacheIndexHeader)) {
00538 q->recreateCacheFiles();
00539 }
00540
00541 if (!q->isValid() || !file->open(QIODevice::ReadWrite)) {
00542 kDebug(264) << "Couldn't open index file" << mIndexFile;
00543 delete file;
00544 return 0;
00545 }
00546
00547 device = file;
00548 }
00549
00550
00551 KPixmapCacheIndexHeader indexHeader;
00552
00553 int numRead = device->read(reinterpret_cast<char *>(&indexHeader), sizeof indexHeader);
00554 if (sizeof indexHeader != numRead) {
00555 kError(264) << "Unable to read header from pixmap cache index.";
00556 delete device;
00557 return 0;
00558 }
00559
00560 if (indexHeader.cacheId != mCacheId) {
00561 kDebug(264) << "Cache has changed, reloading";
00562 delete device;
00563
00564 init();
00565 if (!q->isValid()) {
00566 return 0;
00567 } else {
00568 return indexDevice();
00569 }
00570 }
00571
00572 return device;
00573 }
00574
00575 QIODevice* KPixmapCache::Private::dataDevice()
00576 {
00577 if (mDataMmapInfo.file) {
00578
00579 QFileInfo fi(mDataFile);
00580
00581 if (!fi.exists() || fi.size() != mDataMmapInfo.available) {
00582 kDebug(264) << "File size has changed, re-initializing.";
00583 q->recreateCacheFiles();
00584
00585
00586
00587 return 0;
00588 }
00589
00590 fi.refresh();
00591 if (fi.exists() && fi.size() == mDataMmapInfo.available) {
00592
00593 return new KPCMemoryDevice(
00594 reinterpret_cast<char*>(mDataMmapInfo.indexHeader),
00595 &mDataMmapInfo.size, mDataMmapInfo.available);
00596 }
00597 else
00598 return 0;
00599 }
00600
00601 QFile* file = new QFile(mDataFile);
00602 if (!file->exists() || (size_t) file->size() < sizeof(KPixmapCacheDataHeader)) {
00603 q->recreateCacheFiles();
00604
00605
00606 delete file;
00607 return 0;
00608 }
00609 if (!file->open(QIODevice::ReadWrite)) {
00610 kDebug(264) << "Couldn't open data file" << mDataFile;
00611 delete file;
00612 return 0;
00613 }
00614 return file;
00615 }
00616
00617 int KPixmapCache::Private::binarySearchKey(QDataStream& stream, const QString& key, int start)
00618 {
00619 stream.device()->seek(start);
00620
00621 QString fkey;
00622 qint32 foffset;
00623 quint32 timesused, lastused;
00624 qint32 leftchild, rightchild;
00625 stream >> fkey >> foffset >> timesused >> lastused >> leftchild >> rightchild;
00626
00627 if (key < fkey) {
00628 if (leftchild) {
00629 return binarySearchKey(stream, key, leftchild);
00630 }
00631 } else if (key == fkey) {
00632 return start;
00633 } else if (rightchild) {
00634 return binarySearchKey(stream, key, rightchild);
00635 }
00636
00637 return start;
00638 }
00639
00640 int KPixmapCache::Private::findOffset(const QString& key)
00641 {
00642
00643 QIODevice* device = indexDevice();
00644 if (!device) {
00645 return -1;
00646 }
00647 device->seek(mIndexRootOffset);
00648 QDataStream stream(device);
00649
00650
00651
00652
00653 if (!stream.atEnd()) {
00654 int nodeoffset = binarySearchKey(stream, key, mIndexRootOffset);
00655
00656
00657 device->seek(nodeoffset);
00658 QString fkey;
00659 stream >> fkey;
00660 if (fkey == key) {
00661
00662 qint32 foffset;
00663 quint32 timesused, lastused;
00664 stream >> foffset >> timesused;
00665
00666 timesused++;
00667 lastused = ::time(0);
00668 stream.device()->seek(stream.device()->pos() - sizeof(quint32));
00669 stream << timesused << lastused;
00670 delete device;
00671 return foffset;
00672 }
00673 }
00674
00675
00676 delete device;
00677 return -1;
00678 }
00679
00680 bool KPixmapCache::Private::checkLockFile()
00681 {
00682
00683 if (QFile::exists(mLockFileName)) {
00684 if (!QFile::remove(mLockFileName)) {
00685 kError() << "Couldn't remove lockfile" << mLockFileName;
00686 return false;
00687 }
00688 }
00689 return true;
00690 }
00691
00692 bool KPixmapCache::Private::checkFileVersion(const QString& filename)
00693 {
00694 if (!mEnabled) {
00695 return false;
00696 }
00697
00698 if (QFile::exists(filename)) {
00699
00700 QFile f(filename);
00701 if (!f.open(QIODevice::ReadOnly)) {
00702 kError() << "Couldn't open file" << filename;
00703 return false;
00704 }
00705
00706
00707
00708 KPixmapCacheIndexHeader indexHeader;
00709
00710
00711 if(sizeof indexHeader != f.read(reinterpret_cast<char*>(&indexHeader), sizeof indexHeader) ||
00712 qstrncmp(indexHeader.magic, KPC_MAGIC, sizeof(indexHeader.magic)) != 0)
00713 {
00714 kDebug(264) << "File" << filename << "is not KPixmapCache file, or is";
00715 kDebug(264) << "version <= 0x000207, will recreate...";
00716 return q->recreateCacheFiles();
00717 }
00718
00719 if(indexHeader.cacheVersion == KPIXMAPCACHE_VERSION)
00720 return true;
00721
00722
00723
00724 if(indexHeader.cacheVersion > KPIXMAPCACHE_VERSION) {
00725 kDebug(264) << "File" << filename << "has newer version, disabling cache";
00726 return false;
00727 }
00728
00729 kDebug(264) << "File" << filename << "is outdated, will recreate...";
00730 }
00731
00732 return q->recreateCacheFiles();
00733 }
00734
00735 bool KPixmapCache::Private::loadDataHeader()
00736 {
00737
00738 QFile file(mDataFile);
00739 if (!file.open(QIODevice::ReadOnly)) {
00740 return false;
00741 }
00742
00743 KPixmapCacheDataHeader dataHeader;
00744 if(sizeof dataHeader != file.read(reinterpret_cast<char*>(&dataHeader), sizeof dataHeader)) {
00745 kDebug(264) << "Unable to read from data file" << mDataFile;
00746 return false;
00747 }
00748
00749 mDataMmapInfo.size = dataHeader.size;
00750 return true;
00751 }
00752
00753 bool KPixmapCache::Private::loadIndexHeader()
00754 {
00755
00756 QFile file(mIndexFile);
00757 if (!file.open(QIODevice::ReadOnly)) {
00758 return false;
00759 }
00760
00761 KPixmapCacheIndexHeader indexHeader;
00762 if(sizeof indexHeader != file.read(reinterpret_cast<char*>(&indexHeader), sizeof indexHeader)) {
00763 kWarning(264) << "Failed to read index file's header";
00764 q->recreateCacheFiles();
00765 return false;
00766 }
00767
00768 mCacheId = indexHeader.cacheId;
00769 mTimestamp = indexHeader.timestamp;
00770 mIndexMmapInfo.size = indexHeader.size;
00771
00772 QDataStream stream(&file);
00773
00774
00775 if (!q->loadCustomIndexHeader(stream)) {
00776 return false;
00777 }
00778
00779 mHeaderSize = file.pos();
00780 mIndexRootOffset = file.pos();
00781
00782 return true;
00783 }
00784
00785 QString KPixmapCache::Private::indexKey(const QString& key)
00786 {
00787 const QByteArray latin1 = key.toLatin1();
00788 return QString("%1%2").arg((ushort)qChecksum(latin1.data(), latin1.size()), 4, 16, QLatin1Char('0')).arg(key);
00789 }
00790
00791 void KPixmapCache::Private::writeIndexEntry(QDataStream& stream, const QString& key, int dataoffset)
00792 {
00793
00794 qint32 offset = stream.device()->size();
00795
00796 int parentoffset = binarySearchKey(stream, key, mIndexRootOffset);
00797 if (parentoffset != stream.device()->size()) {
00798
00799 QString fkey;
00800 stream.device()->seek(parentoffset);
00801 stream >> fkey;
00802 if (key == fkey) {
00803
00804 offset = parentoffset;
00805 }
00806 }
00807
00808 stream.device()->seek(offset);
00809
00810 stream << key << (qint32)dataoffset;
00811
00812 stream << (quint32)1 << (quint32)::time(0);
00813
00814 stream << (qint32)0 << (qint32)0;
00815
00816
00817
00818
00819 if (parentoffset != offset) {
00820 stream.device()->seek(parentoffset);
00821 QString fkey;
00822 qint32 foffset, tmp;
00823 quint32 timesused, lastused;
00824 stream >> fkey >> foffset >> timesused >> lastused;
00825 if (key < fkey) {
00826
00827 stream << offset;
00828 } else {
00829
00830 stream >> tmp;
00831 stream << offset;
00832 }
00833 }
00834 }
00835
00836 bool KPixmapCache::Private::scheduleRemoveEntries(int newsize)
00837 {
00838 if (!mRemovalThread) {
00839 mRemovalThread = new RemovalThread(this);
00840 }
00841 mRemovalThread->scheduleRemoval(newsize);
00842 return true;
00843 }
00844
00845 bool KPixmapCache::Private::removeEntries(int newsize)
00846 {
00847 KPCLockFile lock(mLockFileName);
00848 if (!lock.isValid()) {
00849 kDebug(264) << "Couldn't lock cache" << mName;
00850 return false;
00851 }
00852 QMutexLocker mutexlocker(&mMutex);
00853
00854
00855 QFile indexfile(mIndexFile);
00856 if (!indexfile.open(QIODevice::ReadOnly)) {
00857 kDebug(264) << "Couldn't open old index file";
00858 return false;
00859 }
00860 QDataStream istream(&indexfile);
00861 QFile datafile(mDataFile);
00862 if (!datafile.open(QIODevice::ReadOnly)) {
00863 kDebug(264) << "Couldn't open old data file";
00864 return false;
00865 }
00866 if (datafile.size() <= newsize*1024) {
00867 kDebug(264) << "Cache size is already within limit (" << datafile.size() << " <= " << newsize*1024 << ")";
00868 return true;
00869 }
00870 QDataStream dstream(&datafile);
00871
00872 QFile newindexfile(mIndexFile + ".new");
00873 if (!newindexfile.open(QIODevice::ReadWrite)) {
00874 kDebug(264) << "Couldn't open new index file";
00875 return false;
00876 }
00877 QDataStream newistream(&newindexfile);
00878 QFile newdatafile(mDataFile + ".new");
00879 if (!newdatafile.open(QIODevice::WriteOnly)) {
00880 kDebug(264) << "Couldn't open new data file";
00881 return false;
00882 }
00883 QDataStream newdstream(&newdatafile);
00884
00885
00886 char* header = new char[mHeaderSize];
00887 if (istream.readRawData(header, mHeaderSize) != (int)mHeaderSize) {
00888 kDebug(264) << "Couldn't read index header";
00889 delete [] header;
00890 return false;
00891 }
00892
00893
00894 reinterpret_cast<KPixmapCacheIndexHeader *>(header)->size = 0;
00895 newistream.writeRawData(header, mHeaderSize);
00896
00897
00898 int dataheaderlen = sizeof(KPixmapCacheDataHeader);
00899
00900
00901
00902 if (dstream.readRawData(header, dataheaderlen) != dataheaderlen) {
00903 kDebug(264) << "Couldn't read data header";
00904 delete [] header;
00905 return false;
00906 }
00907
00908
00909 reinterpret_cast<KPixmapCacheDataHeader *>(header)->size = 0;
00910 newdstream.writeRawData(header, dataheaderlen);
00911 delete [] header;
00912
00913
00914 QList<KPixmapCacheEntry> entries;
00915
00916 QQueue<int> open;
00917 open.enqueue(mIndexRootOffset);
00918 while (!open.isEmpty()) {
00919 int indexoffset = open.dequeue();
00920 indexfile.seek(indexoffset);
00921 QString fkey;
00922 qint32 foffset;
00923 quint32 timesused, lastused;
00924 qint32 leftchild, rightchild;
00925 istream >> fkey >> foffset >> timesused >> lastused >> leftchild >> rightchild;
00926 entries.append(KPixmapCacheEntry(indexoffset, fkey, foffset, entries.count(), timesused, lastused));
00927 if (leftchild) {
00928 open.enqueue(leftchild);
00929 }
00930 if (rightchild) {
00931 open.enqueue(rightchild);
00932 }
00933 }
00934
00935
00936
00937 if (q->removeEntryStrategy() == RemoveOldest) {
00938 qSort(entries.begin(), entries.end(), compareEntriesByAge);
00939 } else if (q->removeEntryStrategy() == RemoveSeldomUsed) {
00940 qSort(entries.begin(), entries.end(), compareEntriesByTimesUsed);
00941 } else {
00942 qSort(entries.begin(), entries.end(), compareEntriesByLastUsed);
00943 }
00944
00945
00946 int entrieswritten = 0;
00947 for (entrieswritten = 0; entrieswritten < entries.count(); entrieswritten++) {
00948 const KPixmapCacheEntry& entry = entries[entrieswritten];
00949
00950 datafile.seek(entry.dataoffset);
00951 int entrysize = -datafile.pos();
00952
00953
00954 QString fkey;
00955 dstream >> fkey;
00956 qint32 format, w, h, bpl;
00957 dstream >> format >> w >> h >> bpl;
00958 QByteArray imgdatacompressed;
00959 dstream >> imgdatacompressed;
00960
00961 if (!q->loadCustomData(dstream)) {
00962 return false;
00963 }
00964
00965 entrysize += datafile.pos();
00966
00967
00968 if (newdatafile.size() + entrysize > newsize*1024) {
00969 break;
00970 }
00971
00972
00973 int newdataoffset = newdatafile.pos();
00974 newdstream << fkey;
00975 newdstream << format << w << h << bpl;
00976 newdstream << imgdatacompressed;
00977 q->writeCustomData(newdstream);
00978
00979
00980 writeIndexEntry(newistream, entry.key, newdataoffset);
00981 }
00982
00983
00984 indexfile.remove();
00985 datafile.remove();
00986 newindexfile.rename(mIndexFile);
00987 newdatafile.rename(mDataFile);
00988 invalidateMmapFiles();
00989
00990 kDebug(264) << "Wrote back" << entrieswritten << "of" << entries.count() << "entries";
00991
00992 return true;
00993 }
00994
00995
00996
00997
00998 KPixmapCache::KPixmapCache(const QString& name)
00999 :d(new Private(this))
01000 {
01001 d->mName = name;
01002 d->mUseQPixmapCache = true;
01003 d->mCacheLimit = 3 * 1024;
01004 d->mRemoveStrategy = RemoveLeastRecentlyUsed;
01005
01006
01007
01008 d->mInited = false;
01009 }
01010
01011 KPixmapCache::~KPixmapCache()
01012 {
01013 d->unmmapFiles();
01014 if (d->mRemovalThread) {
01015 d->mRemovalThread->wait();
01016 delete d->mRemovalThread;
01017 }
01018 delete d;
01019 }
01020
01021 void KPixmapCache::Private::init()
01022 {
01023 mInited = true;
01024
01025 #ifdef DISABLE_PIXMAPCACHE
01026 mValid = mEnabled = false;
01027 #else
01028 mValid = false;
01029
01030
01031 mIndexFile = KGlobal::dirs()->locateLocal("cache", "kpc/" + mName + ".index");
01032 mDataFile = KGlobal::dirs()->locateLocal("cache", "kpc/" + mName + ".data");
01033 mLockFileName = KGlobal::dirs()->locateLocal("cache", "kpc/" + mName + ".lock");
01034
01035 mEnabled = true;
01036 mEnabled &= checkLockFile();
01037 mEnabled &= checkFileVersion(mDataFile);
01038 mEnabled &= checkFileVersion(mIndexFile);
01039 if (!mEnabled) {
01040 kDebug(264) << "Pixmap cache" << mName << "is disabled";
01041 } else {
01042
01043 loadDataHeader();
01044 q->setValid(loadIndexHeader());
01045
01046 mmapFiles();
01047 }
01048 #endif
01049 }
01050
01051 void KPixmapCache::ensureInited() const
01052 {
01053 if (!d->mInited) {
01054 const_cast<KPixmapCache*>(this)->d->init();
01055 }
01056 }
01057
01058 bool KPixmapCache::loadCustomIndexHeader(QDataStream&)
01059 {
01060 return true;
01061 }
01062
01063 void KPixmapCache::writeCustomIndexHeader(QDataStream&)
01064 {
01065 }
01066
01067 bool KPixmapCache::isEnabled() const
01068 {
01069 ensureInited();
01070 return d->mEnabled;
01071 }
01072
01073 bool KPixmapCache::isValid() const
01074 {
01075 ensureInited();
01076 return d->mEnabled && d->mValid;
01077 }
01078
01079 void KPixmapCache::setValid(bool valid)
01080 {
01081 ensureInited();
01082 d->mValid = valid;
01083 }
01084
01085 unsigned int KPixmapCache::timestamp() const
01086 {
01087 ensureInited();
01088 return d->mTimestamp;
01089 }
01090
01091 void KPixmapCache::setTimestamp(unsigned int ts)
01092 {
01093 ensureInited();
01094 d->mTimestamp = ts;
01095
01096
01097 KPCLockFile lock(d->mLockFileName);
01098 if (!lock.isValid()) {
01099
01100 return;
01101 }
01102
01103 QIODevice* device = d->indexDevice();
01104 if (!device) {
01105 return;
01106 }
01107
01108 KPixmapCacheIndexHeader header;
01109 device->seek(0);
01110 if(sizeof header != device->read(reinterpret_cast<char*>(&header), sizeof header)) {
01111 delete device;
01112 return;
01113 }
01114
01115 header.timestamp = ts;
01116 device->seek(0);
01117 device->write(reinterpret_cast<char *>(&header), sizeof header);
01118
01119 delete device;
01120 }
01121
01122 int KPixmapCache::size() const
01123 {
01124 ensureInited();
01125 if (d->mDataMmapInfo.file) {
01126 return d->mDataMmapInfo.size / 1024;
01127 }
01128 return QFileInfo(d->mDataFile).size() / 1024;
01129 }
01130
01131 void KPixmapCache::setUseQPixmapCache(bool use)
01132 {
01133 d->mUseQPixmapCache = use;
01134 }
01135
01136 bool KPixmapCache::useQPixmapCache() const
01137 {
01138 return d->mUseQPixmapCache;
01139 }
01140
01141 int KPixmapCache::cacheLimit() const
01142 {
01143 return d->mCacheLimit;
01144 }
01145
01146 void KPixmapCache::setCacheLimit(int kbytes)
01147 {
01148
01149 if (kbytes < 0) {
01150 return;
01151 }
01152
01153 d->mCacheLimit = kbytes;
01154
01155
01156
01157 if (d->mInited && d->mCacheLimit && size() > d->mCacheLimit) {
01158 if (size() > (int)(d->mCacheLimit * 1.2)) {
01159
01160 d->removeEntries(d->mCacheLimit * 0.75);
01161 } else {
01162 d->scheduleRemoveEntries(int(d->mCacheLimit * 0.75));
01163 }
01164 }
01165 }
01166
01167 KPixmapCache::RemoveStrategy KPixmapCache::removeEntryStrategy() const
01168 {
01169 return d->mRemoveStrategy;
01170 }
01171
01172 void KPixmapCache::setRemoveEntryStrategy(KPixmapCache::RemoveStrategy strategy)
01173 {
01174 d->mRemoveStrategy = strategy;
01175 }
01176
01177 bool KPixmapCache::recreateCacheFiles()
01178 {
01179 if (!isEnabled()) {
01180 return false;
01181 }
01182
01183 d->invalidateMmapFiles();
01184 d->unmmapFiles();
01185
01186 d->mEnabled = false;
01187
01188 KPCLockFile lock(d->mLockFileName);
01189
01190
01191
01192 QFile indexfile(d->mIndexFile);
01193 if (!indexfile.open(QIODevice::WriteOnly)) {
01194 kError() << "Couldn't create index file" << d->mIndexFile;
01195 return false;
01196 }
01197
01198 KPixmapCacheIndexHeader indexHeader;
01199 d->mCacheId = ::time(0);
01200 d->mTimestamp = ::time(0);
01201
01202 memcpy(indexHeader.magic, KPC_MAGIC, sizeof(indexHeader.magic));
01203 indexHeader.cacheVersion = KPIXMAPCACHE_VERSION;
01204 indexHeader.timestamp = d->mTimestamp;
01205 indexHeader.cacheId = d->mCacheId;
01206
01207
01208
01209 indexHeader.size = 0;
01210
01211 indexfile.write(reinterpret_cast<char*>(&indexHeader), sizeof indexHeader);
01212
01213
01214 QFile datafile(d->mDataFile);
01215 if (!datafile.open(QIODevice::WriteOnly)) {
01216 kError() << "Couldn't create data file" << d->mDataFile;
01217 return false;
01218 }
01219
01220 KPixmapCacheDataHeader dataHeader;
01221 memcpy(dataHeader.magic, KPC_MAGIC, sizeof(dataHeader.magic));
01222 dataHeader.cacheVersion = KPIXMAPCACHE_VERSION;
01223 dataHeader.size = sizeof dataHeader;
01224
01225 datafile.write(reinterpret_cast<char*>(&dataHeader), sizeof dataHeader);
01226
01227 setValid(true);
01228
01229 QDataStream istream(&indexfile);
01230 writeCustomIndexHeader(istream);
01231 d->mHeaderSize = indexfile.pos();
01232
01233 d->mIndexRootOffset = d->mHeaderSize;
01234
01235
01236 indexfile.close();
01237 datafile.close();
01238 d->mEnabled = true;
01239 d->mmapFiles();
01240 return true;
01241 }
01242
01243 void KPixmapCache::deleteCache(const QString& name)
01244 {
01245 foreach (KPixmapCache::Private *d, Private::mCaches) {
01246 if (d->mName == name && d->mInited) {
01247 d->q->discard();
01248 }
01249 }
01250 }
01251
01252 void KPixmapCache::discard()
01253 {
01254 d->invalidateMmapFiles();
01255 d->unmmapFiles();
01256 d->mInited = false;
01257
01258 if (d->mUseQPixmapCache) {
01259 QPixmapCache::clear();
01260 }
01261
01262 QString indexFile = KGlobal::dirs()->locateLocal("cache", "kpc/" + d->mName + ".index");
01263 QString dataFile = KGlobal::dirs()->locateLocal("cache", "kpc/" + d->mName + ".data");
01264
01265 QFile::remove(indexFile);
01266 QFile::remove(dataFile);
01267
01268
01269 d->init();
01270 }
01271
01272 void KPixmapCache::removeEntries(int newsize)
01273 {
01274 if (!newsize) {
01275 newsize = d->mCacheLimit;
01276
01277 if (!newsize) {
01278
01279 return;
01280 }
01281 }
01282
01283 d->removeEntries(newsize);
01284 }
01285
01286 bool KPixmapCache::find(const QString& key, QPixmap& pix)
01287 {
01288 ensureInited();
01289 if (!isValid()) {
01290 return false;
01291 }
01292
01293
01294
01295 if (d->mUseQPixmapCache && QPixmapCache::find(key, pix)) {
01296
01297 return true;
01298 }
01299
01300 KPCLockFile lock(d->mLockFileName);
01301 if (!lock.isValid()) {
01302 return false;
01303 }
01304
01305
01306 QString indexkey = d->indexKey(key);
01307 int offset = d->findOffset(indexkey);
01308
01309 if (offset == -1) {
01310 return false;
01311 }
01312
01313
01314 bool ret = d->loadData(offset, pix);
01315 if (ret && d->mUseQPixmapCache) {
01316
01317 QPixmapCache::insert(key, pix);
01318 }
01319 return ret;
01320 }
01321
01322 bool KPixmapCache::Private::loadData(int offset, QPixmap& pix)
01323 {
01324
01325 QIODevice* device = dataDevice();
01326 if (!device) {
01327 return false;
01328 }
01329
01330 if (!device->seek(offset)) {
01331 kError() << "Couldn't seek to pos" << offset;
01332 delete device;
01333 return false;
01334 }
01335 QDataStream stream(device);
01336
01337
01338 QString fkey;
01339 stream >> fkey;
01340
01341
01342 qint32 format, w, h, bpl;
01343 stream >> format >> w >> h >> bpl;
01344 QByteArray imgdatacompressed;
01345 stream >> imgdatacompressed;
01346
01347
01348
01349
01350
01351 QByteArray imgdata = qUncompress(imgdatacompressed);
01352 if (!imgdata.isEmpty()) {
01353 QImage img((const uchar*)imgdata.constData(), w, h, bpl, (QImage::Format)format);
01354 img.bits();
01355 pix = QPixmap::fromImage(img);
01356 } else {
01357 pix = QPixmap(w, h);
01358 }
01359
01360 if (!q->loadCustomData(stream)) {
01361 delete device;
01362 return false;
01363 }
01364
01365 delete device;
01366 if (stream.status() != QDataStream::Ok) {
01367 kError() << "stream is bad :-( status=" << stream.status();
01368 return false;
01369 }
01370
01371
01372 return true;
01373 }
01374
01375 bool KPixmapCache::loadCustomData(QDataStream&)
01376 {
01377 return true;
01378 }
01379
01380 void KPixmapCache::insert(const QString& key, const QPixmap& pix)
01381 {
01382 ensureInited();
01383 if (!isValid()) {
01384 return;
01385 }
01386
01387
01388
01389 if (d->mUseQPixmapCache) {
01390 QPixmapCache::insert(key, pix);
01391 }
01392
01393 KPCLockFile lock(d->mLockFileName);
01394 if (!lock.isValid()) {
01395 return;
01396 }
01397
01398
01399 QString indexkey = d->indexKey(key);
01400 int offset = d->writeData(key, pix);
01401
01402 if (offset == -1) {
01403 return;
01404 }
01405
01406 d->writeIndex(indexkey, offset);
01407
01408
01409 if (d->mCacheLimit && size() > d->mCacheLimit) {
01410 lock.unlock();
01411 if (size() > (int)(d->mCacheLimit * 1.2)) {
01412
01413 d->removeEntries(d->mCacheLimit * 0.75);
01414 } else {
01415 d->scheduleRemoveEntries(int(d->mCacheLimit * 0.75));
01416 }
01417 }
01418 }
01419
01420 int KPixmapCache::Private::writeData(const QString& key, const QPixmap& pix)
01421 {
01422
01423 QIODevice* device = dataDevice();
01424 if (!device) {
01425 return -1;
01426 }
01427 int offset = device->size();
01428 device->seek(offset);
01429 QDataStream stream(device);
01430
01431
01432 stream << key;
01433
01434 QImage img = pix.toImage();
01435 QByteArray imgdatacompressed = qCompress(img.bits(), img.numBytes());
01436 stream << (qint32)img.format() << (qint32)img.width() << (qint32)img.height() << (qint32)img.bytesPerLine();
01437 stream << imgdatacompressed;
01438
01439 q->writeCustomData(stream);
01440
01441 delete device;
01442 return offset;
01443 }
01444
01445 bool KPixmapCache::writeCustomData(QDataStream&)
01446 {
01447 return true;
01448 }
01449
01450 void KPixmapCache::Private::writeIndex(const QString& key, int dataoffset)
01451 {
01452
01453 QIODevice* device = indexDevice();
01454 if (!device) {
01455 return;
01456 }
01457 QDataStream stream(device);
01458
01459 writeIndexEntry(stream, key, dataoffset);
01460 delete device;
01461 }
01462
01463 QPixmap KPixmapCache::loadFromFile(const QString& filename)
01464 {
01465 QFileInfo fi(filename);
01466 if (!fi.exists()) {
01467 return QPixmap();
01468 } else if (fi.lastModified().toTime_t() > timestamp()) {
01469
01470 discard();
01471 }
01472
01473 QPixmap pix;
01474 QString key("file:" + filename);
01475 if (!find(key, pix)) {
01476
01477 pix = QPixmap(filename);
01478 if (pix.isNull()) {
01479 return pix;
01480 }
01481
01482 insert(key, pix);
01483 }
01484
01485 return pix;
01486 }
01487
01488 QPixmap KPixmapCache::loadFromSvg(const QString& filename, const QSize& size)
01489 {
01490 QFileInfo fi(filename);
01491 if (!fi.exists()) {
01492 return QPixmap();
01493 } else if (fi.lastModified().toTime_t() > timestamp()) {
01494
01495 discard();
01496 }
01497
01498 QPixmap pix;
01499 QString key = QString("file:%1_%2_%3").arg(filename).arg(size.width()).arg(size.height());
01500 if (!find(key, pix)) {
01501
01502 KSvgRenderer svg;
01503 if (!svg.load(filename)) {
01504 return pix;
01505 } else {
01506 QSize pixSize = size.isValid() ? size : svg.defaultSize();
01507 pix = QPixmap(pixSize);
01508 pix.fill(Qt::transparent);
01509
01510 QPainter p(&pix);
01511 svg.render(&p, QRectF(QPointF(), pixSize));
01512 }
01513
01514
01515 insert(key, pix);
01516 }
01517
01518 return pix;
01519 }
01520