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