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