File indexing completed on 2024-04-14 14:20:23
0001 /* 0002 * 0003 * This file is part of the KDE project. 0004 * Copyright (C) 2007 Rivo Laks <rivolaks@hot.ee> 0005 * 0006 * This library is free software; you can redistribute it and/or 0007 * modify it under the terms of the GNU Library General Public 0008 * License version 2 as published by the Free Software Foundation. 0009 * 0010 * This library is distributed in the hope that it will be useful, 0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0013 * Library General Public License for more details. 0014 * 0015 * You should have received a copy of the GNU Library General Public License 0016 * along with this library; see the file COPYING.LIB. If not, write to 0017 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0018 * Boston, MA 02110-1301, USA. 0019 */ 0020 0021 #include "kpixmapcache.h" 0022 0023 #include <QString> 0024 #include <QPixmap> 0025 #include <QFile> 0026 #include <QDataStream> 0027 #include <QFileInfo> 0028 #include <QPixmapCache> 0029 #include <QtGlobal> 0030 #include <QPainter> 0031 #include <QQueue> 0032 #include <QTimer> 0033 #include <QMutex> 0034 #include <QMutexLocker> 0035 #include <qdir.h> 0036 #include <qstandardpaths.h> 0037 #include <qlockfile.h> 0038 0039 #include <qsavefile.h> 0040 #ifndef _WIN32_WCE 0041 #include <QtSvg/QSvgRenderer> 0042 #endif 0043 #include <kdebug.h> 0044 0045 #include <config-kdelibs4support.h> 0046 0047 #include <unistd.h> 0048 #include <sys/types.h> 0049 #include <string.h> 0050 #include <time.h> 0051 0052 #if HAVE_MADVISE || HAVE_MMAP 0053 #include <sys/mman.h> 0054 #endif 0055 0056 //#define DISABLE_PIXMAPCACHE 0057 0058 #ifdef Q_OS_SOLARIS 0059 #ifndef _XPG_4_2 0060 extern "C" int madvise(caddr_t addr, size_t len, int advice); 0061 #endif 0062 #endif 0063 0064 #define KPIXMAPCACHE_VERSION 0x000209 0065 0066 namespace 0067 { 0068 0069 class KPCLockFile 0070 { 0071 public: 0072 KPCLockFile(const QString &filename) 0073 { 0074 mValid = false; 0075 mLockFile = new QLockFile(filename); 0076 mValid = mLockFile->tryLock(200); 0077 if (!mValid) { 0078 kError() << "Failed to lock file" << filename; 0079 } 0080 } 0081 ~KPCLockFile() 0082 { 0083 unlock(); 0084 delete mLockFile; 0085 } 0086 0087 void unlock() 0088 { 0089 if (mValid) { 0090 mLockFile->unlock(); 0091 mValid = false; 0092 } 0093 } 0094 0095 bool isValid() const 0096 { 0097 return mValid; 0098 } 0099 0100 private: 0101 bool mValid; 0102 QLockFile *mLockFile; 0103 }; 0104 0105 // Contained in the header so we will know if we created this or not. Older 0106 // versions of kdelibs had the version on the byte right after "CACHE ". 0107 // "DEUX" will be read as a quint32 by such versions, and will always be 0108 // greater than the last such version (0x000207), whether a big-endian or 0109 // little-endian system is used. Therefore older systems will correctly 0110 // recognize that this is from a newer kdelibs. (This is an issue since old 0111 // and new kdelibs do not read the version from the exact same spot.) 0112 static const char KPC_MAGIC[] = "KDE PIXMAP CACHE DEUX"; 0113 struct KPixmapCacheDataHeader { 0114 // -1 from sizeof so we don't write out the trailing null. If you change 0115 // the list of members change them in the KPixmapCacheIndexHeader as well! 0116 char magic[sizeof(KPC_MAGIC) - 1]; 0117 quint32 cacheVersion; 0118 quint32 size; 0119 }; 0120 0121 struct KPixmapCacheIndexHeader { 0122 // -1 from sizeof so we don't write out the trailing null. 0123 // The follow are also in KPixmapCacheDataHeader 0124 char magic[sizeof(KPC_MAGIC) - 1]; 0125 quint32 cacheVersion; 0126 quint32 size; 0127 0128 // These belong only to this header type. 0129 quint32 cacheId; 0130 time_t timestamp; 0131 }; 0132 0133 class KPCMemoryDevice : public QIODevice 0134 { 0135 public: 0136 KPCMemoryDevice(char *start, quint32 *size, quint32 available); 0137 ~KPCMemoryDevice() override; 0138 0139 qint64 size() const override 0140 { 0141 return *mSize; 0142 } 0143 void setSize(quint32 s) 0144 { 0145 *mSize = s; 0146 } 0147 bool seek(qint64 pos) override; 0148 0149 protected: 0150 qint64 readData(char *data, qint64 maxSize) override; 0151 qint64 writeData(const char *data, qint64 maxSize) override; 0152 0153 private: 0154 char *mMemory; 0155 KPixmapCacheIndexHeader *mHeader; // alias of mMemory 0156 quint32 *mSize; 0157 quint32 mInitialSize; 0158 qint64 mAvailable; 0159 quint32 mPos; 0160 }; 0161 0162 KPCMemoryDevice::KPCMemoryDevice(char *start, quint32 *size, quint32 available) : QIODevice() 0163 { 0164 mMemory = start; 0165 mHeader = reinterpret_cast<KPixmapCacheIndexHeader *>(start); 0166 mSize = size; 0167 mAvailable = available; 0168 mPos = 0; 0169 0170 this->open(QIODevice::ReadWrite); 0171 0172 // Load up-to-date size from the memory 0173 *mSize = mHeader->size; 0174 0175 mInitialSize = *mSize; 0176 } 0177 0178 KPCMemoryDevice::~KPCMemoryDevice() 0179 { 0180 if (*mSize != mInitialSize) { 0181 // Update file size 0182 mHeader->size = *mSize; 0183 } 0184 } 0185 0186 bool KPCMemoryDevice::seek(qint64 pos) 0187 { 0188 if (pos < 0 || pos > *mSize) { 0189 return false; 0190 } 0191 mPos = pos; 0192 return QIODevice::seek(pos); 0193 } 0194 0195 qint64 KPCMemoryDevice::readData(char *data, qint64 len) 0196 { 0197 len = qMin(len, qint64(*mSize) - mPos); 0198 if (len <= 0) { 0199 return 0; 0200 } 0201 memcpy(data, mMemory + mPos, len); 0202 mPos += len; 0203 return len; 0204 } 0205 0206 qint64 KPCMemoryDevice::writeData(const char *data, qint64 len) 0207 { 0208 if (mPos + len > mAvailable) { 0209 kError() << "Overflow of" << mPos + len - mAvailable; 0210 return -1; 0211 } 0212 memcpy(mMemory + mPos, (uchar *)data, len); 0213 mPos += len; 0214 *mSize = qMax(*mSize, mPos); 0215 return len; 0216 } 0217 0218 } // namespace 0219 0220 class Q_DECL_HIDDEN KPixmapCache::Private 0221 { 0222 public: 0223 Private(KPixmapCache *q); 0224 ~Private(); 0225 0226 // Return device used to read from index or data file. The device is either 0227 // QFile or KPCMemoryDevice (if mmap is used) 0228 QIODevice *indexDevice(); 0229 QIODevice *dataDevice(); 0230 0231 // Unmmaps any currently mmapped files and then tries to (re)mmap the cache 0232 // files. If mmapping is disabled then it does nothing 0233 bool mmapFiles(); 0234 void unmmapFiles(); 0235 // Marks the shared mmapped files as invalid so that all processes will 0236 // reload the files 0237 void invalidateMmapFiles(); 0238 0239 // List of all KPixmapCache::Private instances in this process. 0240 static QList<KPixmapCache::Private *> mCaches; 0241 0242 static unsigned kpcNumber; // Used to setup for qpcKey 0243 0244 int findOffset(const QString &key); 0245 int binarySearchKey(QDataStream &stream, const QString &key, int start); 0246 void writeIndexEntry(QDataStream &stream, const QString &key, int dataoffset); 0247 0248 bool checkFileVersion(const QString &filename); 0249 bool loadIndexHeader(); 0250 bool loadDataHeader(); 0251 0252 bool removeEntries(int newsize); 0253 bool scheduleRemoveEntries(int newsize); 0254 0255 void init(); 0256 bool loadData(int offset, QPixmap &pix); 0257 int writeData(const QString &key, const QPixmap &pix); 0258 void writeIndex(const QString &key, int offset); 0259 0260 // Prepends key's hash to the key. This makes comparisons and key 0261 // lookups faster as the beginnings of the keys are more random 0262 QString indexKey(const QString &key); 0263 0264 // Returns a QString suitable for use in the static QPixmapCache, which 0265 // differentiates each KPC object in the process. 0266 QString qpcKey(const QString &key) const; 0267 0268 KPixmapCache *q; 0269 0270 QString mThisString; // Used by qpcKey 0271 quint32 mHeaderSize; // full size of the index header, including custom (subclass') header data 0272 quint32 mIndexRootOffset; // offset of the first entry in index file 0273 0274 QString mName; 0275 QString mIndexFile; 0276 QString mDataFile; 0277 QString mLockFileName; 0278 QMutex mMutex; 0279 0280 quint32 mTimestamp; 0281 quint32 mCacheId; // Unique id, will change when cache is recreated 0282 int mCacheLimit; 0283 RemoveStrategy mRemoveStrategy: 4; 0284 bool mUseQPixmapCache: 4; 0285 0286 bool mInited: 8; // Whether init() has been called (it's called on-demand) 0287 bool mEnabled: 8; // whether it's possible to use the cache 0288 bool mValid: 8; // whether cache has been inited and is ready to be used 0289 0290 // Holds info about mmapped file 0291 struct MmapInfo { 0292 MmapInfo() 0293 { 0294 file = nullptr; 0295 indexHeader = nullptr; 0296 } 0297 QFile *file; // If this is not null, then the file is mmapped 0298 0299 // This points to the mmap'ed file area. 0300 KPixmapCacheIndexHeader *indexHeader; 0301 0302 quint32 size; // Number of currently used bytes 0303 quint32 available; // Number of available bytes (including those reserved for mmap) 0304 }; 0305 MmapInfo mIndexMmapInfo; 0306 MmapInfo mDataMmapInfo; 0307 // Mmaps given file, growing it to newsize bytes. 0308 bool mmapFile(const QString &filename, MmapInfo *info, int newsize); 0309 void unmmapFile(MmapInfo *info); 0310 0311 // Used by removeEntries() 0312 class KPixmapCacheEntry 0313 { 0314 public: 0315 KPixmapCacheEntry(int indexoffset_, const QString &key_, int dataoffset_, 0316 int pos_, quint32 timesused_, quint32 lastused_) 0317 : indexoffset(indexoffset_), 0318 key(key_), 0319 dataoffset(dataoffset_), 0320 pos(pos_), 0321 timesused(timesused_), 0322 lastused(lastused_) 0323 { 0324 } 0325 0326 int indexoffset; 0327 QString key; 0328 int dataoffset; 0329 0330 int pos; 0331 quint32 timesused; 0332 quint32 lastused; 0333 }; 0334 0335 // Various comparison functions for different removal strategies 0336 static bool compareEntriesByAge(const KPixmapCacheEntry &a, const KPixmapCacheEntry &b) 0337 { 0338 return a.pos > b.pos; 0339 } 0340 static bool compareEntriesByTimesUsed(const KPixmapCacheEntry &a, const KPixmapCacheEntry &b) 0341 { 0342 return a.timesused > b.timesused; 0343 } 0344 static bool compareEntriesByLastUsed(const KPixmapCacheEntry &a, const KPixmapCacheEntry &b) 0345 { 0346 return a.lastused > b.lastused; 0347 } 0348 }; 0349 0350 // List of KPixmapCache::Private instances. 0351 QList<KPixmapCache::Private *> KPixmapCache::Private::mCaches; 0352 0353 unsigned KPixmapCache::Private::kpcNumber = 0; 0354 0355 KPixmapCache::Private::Private(KPixmapCache *_q) 0356 { 0357 q = _q; 0358 mCaches.append(this); 0359 mThisString = QString("%1").arg(kpcNumber++); 0360 } 0361 0362 KPixmapCache::Private::~Private() 0363 { 0364 mCaches.removeAll(this); 0365 } 0366 0367 bool KPixmapCache::Private::mmapFiles() 0368 { 0369 unmmapFiles(); // Noop if nothing has been mmapped 0370 if (!q->isValid()) { 0371 return false; 0372 } 0373 0374 //TODO: 100MB limit if we have no cache limit, is that sensible? 0375 int cacheLimit = mCacheLimit > 0 ? mCacheLimit : 100 * 1024; 0376 if (!mmapFile(mIndexFile, &mIndexMmapInfo, (int)(cacheLimit * 0.4 + 100) * 1024)) { 0377 q->setValid(false); 0378 return false; 0379 } 0380 0381 if (!mmapFile(mDataFile, &mDataMmapInfo, (int)(cacheLimit * 1.5 + 500) * 1024)) { 0382 unmmapFile(&mIndexMmapInfo); 0383 q->setValid(false); 0384 return false; 0385 } 0386 0387 return true; 0388 } 0389 0390 void KPixmapCache::Private::unmmapFiles() 0391 { 0392 unmmapFile(&mIndexMmapInfo); 0393 unmmapFile(&mDataMmapInfo); 0394 } 0395 0396 void KPixmapCache::Private::invalidateMmapFiles() 0397 { 0398 if (!q->isValid()) { 0399 return; 0400 } 0401 // Set cache id to 0, this will force a reload the next time the files are used 0402 if (mIndexMmapInfo.file) { 0403 kDebug(264) << "Invalidating cache"; 0404 mIndexMmapInfo.indexHeader->cacheId = 0; 0405 } 0406 } 0407 0408 bool KPixmapCache::Private::mmapFile(const QString &filename, MmapInfo *info, int newsize) 0409 { 0410 info->file = new QFile(filename); 0411 if (!info->file->open(QIODevice::ReadWrite)) { 0412 kDebug(264) << "Couldn't open" << filename; 0413 delete info->file; 0414 info->file = nullptr; 0415 return false; 0416 } 0417 0418 if (!info->size) { 0419 info->size = info->file->size(); 0420 } 0421 info->available = newsize; 0422 0423 // Only resize if greater than current file size, otherwise we may cause SIGBUS 0424 // errors from mmap(). 0425 if (info->file->size() < info->available && !info->file->resize(info->available)) { 0426 kError(264) << "Couldn't resize" << filename << "to" << newsize; 0427 delete info->file; 0428 info->file = nullptr; 0429 return false; 0430 } 0431 0432 //void* indexMem = mmap(0, info->available, PROT_READ | PROT_WRITE, MAP_SHARED, info->file->handle(), 0); 0433 void *indexMem = info->file->map(0, info->available); 0434 if (indexMem == nullptr) { 0435 kError() << "mmap failed for" << filename; 0436 delete info->file; 0437 info->file = nullptr; 0438 return false; 0439 } 0440 info->indexHeader = reinterpret_cast<KPixmapCacheIndexHeader *>(indexMem); 0441 #if HAVE_MADVISE 0442 posix_madvise(indexMem, info->size, POSIX_MADV_WILLNEED); 0443 #endif 0444 0445 info->file->close(); 0446 0447 // Update our stored file size. Other objects that have this mmaped will have to 0448 // invalidate their map if size is different. 0449 if (0 == info->indexHeader->size) { 0450 // This size includes index header and and custom headers tacked on 0451 // by subclasses. 0452 info->indexHeader->size = mHeaderSize; 0453 info->size = info->indexHeader->size; 0454 } 0455 0456 return true; 0457 } 0458 0459 void KPixmapCache::Private::unmmapFile(MmapInfo *info) 0460 { 0461 if (info->file) { 0462 info->file->unmap(reinterpret_cast<uchar *>(info->indexHeader)); 0463 info->indexHeader = nullptr; 0464 info->available = 0; 0465 info->size = 0; 0466 0467 delete info->file; 0468 info->file = nullptr; 0469 } 0470 } 0471 0472 QIODevice *KPixmapCache::Private::indexDevice() 0473 { 0474 QIODevice *device = nullptr; 0475 0476 if (mIndexMmapInfo.file) { 0477 // Make sure the file still exists 0478 QFileInfo fi(mIndexFile); 0479 0480 if (!fi.exists() || fi.size() != mIndexMmapInfo.available) { 0481 kDebug(264) << "File size has changed, re-initializing."; 0482 q->recreateCacheFiles(); // Recreates memory maps as well. 0483 } 0484 0485 fi.refresh(); 0486 if (fi.exists() && fi.size() == mIndexMmapInfo.available) { 0487 // Create the device 0488 device = new KPCMemoryDevice( 0489 reinterpret_cast<char *>(mIndexMmapInfo.indexHeader), 0490 &mIndexMmapInfo.size, mIndexMmapInfo.available); 0491 } 0492 0493 // Is it possible to have a valid cache with no file? If not it would be easier 0494 // to do return 0 in the else portion of the prior test. 0495 if (!q->isValid()) { 0496 delete device; 0497 return nullptr; 0498 } 0499 } 0500 0501 if (!device) { 0502 QFile *file = new QFile(mIndexFile); 0503 if (!file->exists() || (size_t) file->size() < sizeof(KPixmapCacheIndexHeader)) { 0504 q->recreateCacheFiles(); 0505 } 0506 0507 if (!q->isValid() || !file->open(QIODevice::ReadWrite)) { 0508 kDebug(264) << "Couldn't open index file" << mIndexFile; 0509 delete file; 0510 return nullptr; 0511 } 0512 0513 device = file; 0514 } 0515 0516 // Make sure the device is up-to-date 0517 KPixmapCacheIndexHeader indexHeader; 0518 0519 int numRead = device->read(reinterpret_cast<char *>(&indexHeader), sizeof indexHeader); 0520 if (sizeof indexHeader != numRead) { 0521 kError(264) << "Unable to read header from pixmap cache index."; 0522 delete device; 0523 return nullptr; 0524 } 0525 0526 if (indexHeader.cacheId != mCacheId) { 0527 kDebug(264) << "Cache has changed, reloading"; 0528 delete device; 0529 0530 init(); 0531 if (!q->isValid()) { 0532 return nullptr; 0533 } else { 0534 return indexDevice(); // Careful, this is a recursive call. 0535 } 0536 } 0537 0538 return device; 0539 } 0540 0541 QIODevice *KPixmapCache::Private::dataDevice() 0542 { 0543 if (mDataMmapInfo.file) { 0544 // Make sure the file still exists 0545 QFileInfo fi(mDataFile); 0546 0547 if (!fi.exists() || fi.size() != mDataMmapInfo.available) { 0548 kDebug(264) << "File size has changed, re-initializing."; 0549 q->recreateCacheFiles(); // Recreates memory maps as well. 0550 0551 // Index file has also been recreated so we cannot continue with 0552 // modifying the data file because it would make things inconsistent. 0553 return nullptr; 0554 } 0555 0556 fi.refresh(); 0557 if (fi.exists() && fi.size() == mDataMmapInfo.available) { 0558 // Create the device 0559 return new KPCMemoryDevice( 0560 reinterpret_cast<char *>(mDataMmapInfo.indexHeader), 0561 &mDataMmapInfo.size, mDataMmapInfo.available); 0562 } else { 0563 return nullptr; 0564 } 0565 } 0566 0567 QFile *file = new QFile(mDataFile); 0568 if (!file->exists() || (size_t) file->size() < sizeof(KPixmapCacheDataHeader)) { 0569 q->recreateCacheFiles(); 0570 // Index file has also been recreated so we cannot continue with 0571 // modifying the data file because it would make things inconsistent. 0572 delete file; 0573 return nullptr; 0574 } 0575 if (!file->open(QIODevice::ReadWrite)) { 0576 kDebug(264) << "Couldn't open data file" << mDataFile; 0577 delete file; 0578 return nullptr; 0579 } 0580 return file; 0581 } 0582 0583 int KPixmapCache::Private::binarySearchKey(QDataStream &stream, const QString &key, int start) 0584 { 0585 stream.device()->seek(start); 0586 0587 QString fkey; 0588 qint32 foffset; 0589 quint32 timesused, lastused; 0590 qint32 leftchild, rightchild; 0591 stream >> fkey >> foffset >> timesused >> lastused >> leftchild >> rightchild; 0592 0593 if (fkey.isEmpty()) { 0594 return start; 0595 } 0596 0597 if (key < fkey) { 0598 if (leftchild) { 0599 return binarySearchKey(stream, key, leftchild); 0600 } 0601 } else if (key == fkey) { 0602 return start; 0603 } else if (rightchild) { 0604 return binarySearchKey(stream, key, rightchild); 0605 } 0606 0607 return start; 0608 } 0609 0610 int KPixmapCache::Private::findOffset(const QString &key) 0611 { 0612 // Open device and datastream on it 0613 QIODevice *device = indexDevice(); 0614 if (!device) { 0615 return -1; 0616 } 0617 device->seek(mIndexRootOffset); 0618 QDataStream stream(device); 0619 0620 // If we're already at the end of the stream then the root node doesn't 0621 // exist yet and there are no entries. Otherwise, do a binary search 0622 // starting from the root node. 0623 if (!stream.atEnd()) { 0624 // One exception is that the root node may exist but be invalid, 0625 // which can happen when the cache data is discarded. This is 0626 // represented by an empty fkey 0627 QString fkey; 0628 stream >> fkey; 0629 0630 if (fkey.isEmpty()) { 0631 delete device; 0632 return -1; 0633 } 0634 0635 int nodeoffset = binarySearchKey(stream, key, mIndexRootOffset); 0636 0637 // Load the found entry and check if it's the one we're looking for. 0638 device->seek(nodeoffset); 0639 stream >> fkey; 0640 0641 if (fkey == key) { 0642 // Read offset and statistics 0643 qint32 foffset; 0644 quint32 timesused, lastused; 0645 stream >> foffset >> timesused; 0646 // Update statistics 0647 timesused++; 0648 lastused = ::time(nullptr); 0649 stream.device()->seek(stream.device()->pos() - sizeof(quint32)); 0650 stream << timesused << lastused; 0651 delete device; 0652 return foffset; 0653 } 0654 } 0655 0656 // Nothing was found 0657 delete device; 0658 return -1; 0659 } 0660 0661 bool KPixmapCache::Private::checkFileVersion(const QString &filename) 0662 { 0663 if (!mEnabled) { 0664 return false; 0665 } 0666 0667 if (QFile::exists(filename)) { 0668 // File already exists, make sure it can be opened 0669 QFile f(filename); 0670 if (!f.open(QIODevice::ReadOnly)) { 0671 kError() << "Couldn't open file" << filename; 0672 return false; 0673 } 0674 0675 // The index header is the same as the beginning of the data header (on purpose), 0676 // so use index header for either one. 0677 KPixmapCacheIndexHeader indexHeader; 0678 0679 // Ensure we have a valid cache. 0680 if (sizeof indexHeader != f.read(reinterpret_cast<char *>(&indexHeader), sizeof indexHeader) || 0681 qstrncmp(indexHeader.magic, KPC_MAGIC, sizeof(indexHeader.magic)) != 0) { 0682 kDebug(264) << "File" << filename << "is not KPixmapCache file, or is"; 0683 kDebug(264) << "version <= 0x000207, will recreate..."; 0684 return q->recreateCacheFiles(); 0685 } 0686 0687 if (indexHeader.cacheVersion == KPIXMAPCACHE_VERSION) { 0688 return true; 0689 } 0690 0691 // Don't recreate the cache if it has newer version to avoid 0692 // problems when upgrading kdelibs. 0693 if (indexHeader.cacheVersion > KPIXMAPCACHE_VERSION) { 0694 kDebug(264) << "File" << filename << "has newer version, disabling cache"; 0695 return false; 0696 } 0697 0698 kDebug(264) << "File" << filename << "is outdated, will recreate..."; 0699 } 0700 0701 return q->recreateCacheFiles(); 0702 } 0703 0704 bool KPixmapCache::Private::loadDataHeader() 0705 { 0706 // Open file and datastream on it 0707 QFile file(mDataFile); 0708 if (!file.open(QIODevice::ReadOnly)) { 0709 return false; 0710 } 0711 0712 KPixmapCacheDataHeader dataHeader; 0713 if (sizeof dataHeader != file.read(reinterpret_cast<char *>(&dataHeader), sizeof dataHeader)) { 0714 kDebug(264) << "Unable to read from data file" << mDataFile; 0715 return false; 0716 } 0717 0718 mDataMmapInfo.size = dataHeader.size; 0719 return true; 0720 } 0721 0722 bool KPixmapCache::Private::loadIndexHeader() 0723 { 0724 // Open file and datastream on it 0725 QFile file(mIndexFile); 0726 if (!file.open(QIODevice::ReadOnly)) { 0727 return false; 0728 } 0729 0730 KPixmapCacheIndexHeader indexHeader; 0731 if (sizeof indexHeader != file.read(reinterpret_cast<char *>(&indexHeader), sizeof indexHeader)) { 0732 kWarning(264) << "Failed to read index file's header"; 0733 q->recreateCacheFiles(); 0734 return false; 0735 } 0736 0737 mCacheId = indexHeader.cacheId; 0738 mTimestamp = indexHeader.timestamp; 0739 mIndexMmapInfo.size = indexHeader.size; 0740 0741 QDataStream stream(&file); 0742 0743 // Give custom implementations chance to load their headers 0744 if (!q->loadCustomIndexHeader(stream)) { 0745 return false; 0746 } 0747 0748 mHeaderSize = file.pos(); 0749 mIndexRootOffset = file.pos(); 0750 0751 return true; 0752 } 0753 0754 QString KPixmapCache::Private::indexKey(const QString &key) 0755 { 0756 const QByteArray latin1 = key.toLatin1(); 0757 return QString("%1%2").arg((ushort)qChecksum(latin1.data(), latin1.size()), 4, 16, QLatin1Char('0')).arg(key); 0758 } 0759 0760 QString KPixmapCache::Private::qpcKey(const QString &key) const 0761 { 0762 return mThisString + key; 0763 } 0764 0765 void KPixmapCache::Private::writeIndexEntry(QDataStream &stream, const QString &key, int dataoffset) 0766 { 0767 // New entry will be written to the end of the file 0768 qint32 offset = stream.device()->size(); 0769 // Find parent index node for this node. 0770 int parentoffset = binarySearchKey(stream, key, mIndexRootOffset); 0771 if (parentoffset != stream.device()->size()) { 0772 // Check if this entry has the same key 0773 QString fkey; 0774 stream.device()->seek(parentoffset); 0775 stream >> fkey; 0776 0777 // The key would be empty if the cache had been discarded. 0778 if (key == fkey || fkey.isEmpty()) { 0779 // We're overwriting an existing entry 0780 offset = parentoffset; 0781 } 0782 } 0783 0784 stream.device()->seek(offset); 0785 // Write the data 0786 stream << key << (qint32)dataoffset; 0787 // Statistics (# of uses and last used timestamp) 0788 stream << (quint32)1 << (quint32)::time(nullptr); 0789 // Write (empty) children offsets 0790 stream << (qint32)0 << (qint32)0; 0791 0792 // If we created the root node or overwrote existing entry then the two 0793 // offsets are equal and we're done. Otherwise set parent's child offset 0794 // to correct value. 0795 if (parentoffset != offset) { 0796 stream.device()->seek(parentoffset); 0797 QString fkey; 0798 qint32 foffset, tmp; 0799 quint32 timesused, lastused; 0800 stream >> fkey >> foffset >> timesused >> lastused; 0801 if (key < fkey) { 0802 // New entry will be parent's left child 0803 stream << offset; 0804 } else { 0805 // New entry will be parent's right child 0806 stream >> tmp; 0807 stream << offset; 0808 } 0809 } 0810 } 0811 0812 bool KPixmapCache::Private::removeEntries(int newsize) 0813 { 0814 KPCLockFile lock(mLockFileName); 0815 if (!lock.isValid()) { 0816 kDebug(264) << "Couldn't lock cache" << mName; 0817 return false; 0818 } 0819 QMutexLocker mutexlocker(&mMutex); 0820 0821 // Open old (current) files 0822 QFile indexfile(mIndexFile); 0823 if (!indexfile.open(QIODevice::ReadOnly)) { 0824 kDebug(264) << "Couldn't open old index file"; 0825 return false; 0826 } 0827 QDataStream istream(&indexfile); 0828 QFile datafile(mDataFile); 0829 if (!datafile.open(QIODevice::ReadOnly)) { 0830 kDebug(264) << "Couldn't open old data file"; 0831 return false; 0832 } 0833 if (datafile.size() <= newsize * 1024) { 0834 kDebug(264) << "Cache size is already within limit (" << datafile.size() << " <= " << newsize * 1024 << ")"; 0835 return true; 0836 } 0837 QDataStream dstream(&datafile); 0838 // Open new files 0839 QFile newindexfile(mIndexFile + ".new"); 0840 if (!newindexfile.open(QIODevice::ReadWrite)) { 0841 kDebug(264) << "Couldn't open new index file"; 0842 return false; 0843 } 0844 QDataStream newistream(&newindexfile); 0845 QFile newdatafile(mDataFile + ".new"); 0846 if (!newdatafile.open(QIODevice::WriteOnly)) { 0847 kDebug(264) << "Couldn't open new data file"; 0848 return false; 0849 } 0850 QDataStream newdstream(&newdatafile); 0851 0852 // Copy index file header 0853 char *header = new char[mHeaderSize]; 0854 if (istream.readRawData(header, mHeaderSize) != (int)mHeaderSize) { 0855 kDebug(264) << "Couldn't read index header"; 0856 delete [] header; 0857 return false; 0858 } 0859 0860 // Set file size to 0 for mmap stuff 0861 reinterpret_cast<KPixmapCacheIndexHeader *>(header)->size = 0; 0862 newistream.writeRawData(header, mHeaderSize); 0863 0864 // Copy data file header 0865 int dataheaderlen = sizeof(KPixmapCacheDataHeader); 0866 0867 // mHeaderSize is always bigger than dataheaderlen, so we needn't create 0868 // new buffer 0869 if (dstream.readRawData(header, dataheaderlen) != dataheaderlen) { 0870 kDebug(264) << "Couldn't read data header"; 0871 delete [] header; 0872 return false; 0873 } 0874 0875 // Set file size to 0 for mmap stuff 0876 reinterpret_cast<KPixmapCacheDataHeader *>(header)->size = 0; 0877 newdstream.writeRawData(header, dataheaderlen); 0878 delete [] header; 0879 0880 // Load all entries 0881 QList<KPixmapCacheEntry> entries; 0882 // Do BFS to find all entries 0883 QQueue<int> open; 0884 open.enqueue(mIndexRootOffset); 0885 while (!open.isEmpty()) { 0886 int indexoffset = open.dequeue(); 0887 indexfile.seek(indexoffset); 0888 QString fkey; 0889 qint32 foffset; 0890 quint32 timesused, lastused; 0891 qint32 leftchild, rightchild; 0892 istream >> fkey >> foffset >> timesused >> lastused >> leftchild >> rightchild; 0893 entries.append(KPixmapCacheEntry(indexoffset, fkey, foffset, entries.count(), timesused, lastused)); 0894 if (leftchild) { 0895 open.enqueue(leftchild); 0896 } 0897 if (rightchild) { 0898 open.enqueue(rightchild); 0899 } 0900 } 0901 0902 // Sort the entries according to RemoveStrategy. This moves the best 0903 // entries to the beginning of the list 0904 if (q->removeEntryStrategy() == RemoveOldest) { 0905 std::sort(entries.begin(), entries.end(), compareEntriesByAge); 0906 } else if (q->removeEntryStrategy() == RemoveSeldomUsed) { 0907 std::sort(entries.begin(), entries.end(), compareEntriesByTimesUsed); 0908 } else { 0909 std::sort(entries.begin(), entries.end(), compareEntriesByLastUsed); 0910 } 0911 0912 // Write some entries to the new files 0913 int entrieswritten = 0; 0914 for (entrieswritten = 0; entrieswritten < entries.count(); entrieswritten++) { 0915 const KPixmapCacheEntry &entry = entries[entrieswritten]; 0916 // Load data 0917 datafile.seek(entry.dataoffset); 0918 int entrysize = -datafile.pos(); 0919 // We have some duplication here but this way we avoid uncompressing 0920 // the data and constructing QPixmap which we don't really need. 0921 QString fkey; 0922 dstream >> fkey; 0923 qint32 format, w, h, bpl; 0924 dstream >> format >> w >> h >> bpl; 0925 QByteArray imgdatacompressed; 0926 dstream >> imgdatacompressed; 0927 // Load custom data as well. This will be stored by the subclass itself. 0928 if (!q->loadCustomData(dstream)) { 0929 return false; 0930 } 0931 // Find out size of this entry 0932 entrysize += datafile.pos(); 0933 0934 // Make sure we'll stay within size limit 0935 if (newdatafile.size() + entrysize > newsize * 1024) { 0936 break; 0937 } 0938 0939 // Now write the same data to the new file 0940 int newdataoffset = newdatafile.pos(); 0941 newdstream << fkey; 0942 newdstream << format << w << h << bpl; 0943 newdstream << imgdatacompressed; 0944 q->writeCustomData(newdstream); 0945 0946 // Finally, add the index entry 0947 writeIndexEntry(newistream, entry.key, newdataoffset); 0948 } 0949 0950 // Remove old files and rename the new ones 0951 indexfile.remove(); 0952 datafile.remove(); 0953 newindexfile.rename(mIndexFile); 0954 newdatafile.rename(mDataFile); 0955 invalidateMmapFiles(); 0956 0957 kDebug(264) << "Wrote back" << entrieswritten << "of" << entries.count() << "entries"; 0958 0959 return true; 0960 } 0961 0962 KPixmapCache::KPixmapCache(const QString &name) 0963 : d(new Private(this)) 0964 { 0965 d->mName = name; 0966 d->mUseQPixmapCache = true; 0967 d->mCacheLimit = 3 * 1024; 0968 d->mRemoveStrategy = RemoveLeastRecentlyUsed; 0969 0970 // We cannot call init() here because the subclasses haven't been 0971 // constructed yet and so their virtual methods cannot be used. 0972 d->mInited = false; 0973 } 0974 0975 KPixmapCache::~KPixmapCache() 0976 { 0977 d->unmmapFiles(); 0978 delete d; 0979 } 0980 0981 void KPixmapCache::Private::init() 0982 { 0983 mInited = true; 0984 0985 #ifdef DISABLE_PIXMAPCACHE 0986 mValid = mEnabled = false; 0987 #else 0988 mValid = false; 0989 0990 // Find locations of the files 0991 const QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation); 0992 mEnabled = QDir().mkpath(cacheDir + "/kpc"); 0993 mIndexFile = cacheDir + "/kpc/" + mName + ".index"; 0994 mDataFile = cacheDir + "/kpc/" + mName + ".data"; 0995 mLockFileName = cacheDir + "/kpc/" + mName + ".lock"; 0996 0997 mEnabled = mEnabled && checkFileVersion(mDataFile); 0998 mEnabled = mEnabled && checkFileVersion(mIndexFile); 0999 if (!mEnabled) { 1000 kDebug(264) << "Pixmap cache" << mName << "is disabled"; 1001 } else { 1002 // Cache is enabled, but check if it's ready for use 1003 loadDataHeader(); 1004 q->setValid(loadIndexHeader()); 1005 // Init mmap stuff if mmap is used 1006 mmapFiles(); 1007 } 1008 #endif 1009 } 1010 1011 void KPixmapCache::ensureInited() const 1012 { 1013 if (!d->mInited) { 1014 const_cast<KPixmapCache *>(this)->d->init(); 1015 } 1016 } 1017 1018 bool KPixmapCache::loadCustomIndexHeader(QDataStream &) 1019 { 1020 return true; 1021 } 1022 1023 void KPixmapCache::writeCustomIndexHeader(QDataStream &) 1024 { 1025 } 1026 1027 bool KPixmapCache::isEnabled() const 1028 { 1029 ensureInited(); 1030 return d->mEnabled; 1031 } 1032 1033 bool KPixmapCache::isValid() const 1034 { 1035 ensureInited(); 1036 return d->mEnabled && d->mValid; 1037 } 1038 1039 void KPixmapCache::setValid(bool valid) 1040 { 1041 ensureInited(); 1042 d->mValid = valid; 1043 } 1044 1045 QDateTime KPixmapCache::timestamp() const 1046 { 1047 ensureInited(); 1048 return QDateTime::fromTime_t(d->mTimestamp); 1049 } 1050 1051 void KPixmapCache::setTimestamp(const QDateTime &ts) 1052 { 1053 ensureInited(); 1054 d->mTimestamp = ts.toTime_t(); 1055 1056 // Write to file 1057 KPCLockFile lock(d->mLockFileName); 1058 if (!lock.isValid()) { 1059 // FIXME: now what? 1060 return; 1061 } 1062 1063 QIODevice *device = d->indexDevice(); 1064 if (!device) { 1065 return; 1066 } 1067 1068 KPixmapCacheIndexHeader header; 1069 device->seek(0); 1070 if (sizeof header != device->read(reinterpret_cast<char *>(&header), sizeof header)) { 1071 delete device; 1072 return; 1073 } 1074 1075 header.timestamp = ts.toTime_t(); 1076 device->seek(0); 1077 device->write(reinterpret_cast<char *>(&header), sizeof header); 1078 1079 delete device; 1080 } 1081 1082 int KPixmapCache::size() const 1083 { 1084 ensureInited(); 1085 if (d->mDataMmapInfo.file) { 1086 return d->mDataMmapInfo.size / 1024; 1087 } 1088 return QFileInfo(d->mDataFile).size() / 1024; 1089 } 1090 1091 void KPixmapCache::setUseQPixmapCache(bool use) 1092 { 1093 d->mUseQPixmapCache = use; 1094 } 1095 1096 bool KPixmapCache::useQPixmapCache() const 1097 { 1098 return d->mUseQPixmapCache; 1099 } 1100 1101 int KPixmapCache::cacheLimit() const 1102 { 1103 return d->mCacheLimit; 1104 } 1105 1106 void KPixmapCache::setCacheLimit(int kbytes) 1107 { 1108 //FIXME: KDE5: this should be uint! 1109 if (kbytes < 0) { 1110 return; 1111 } 1112 1113 d->mCacheLimit = kbytes; 1114 1115 // if we are initialized, let's make sure that we are actually within 1116 // our limits. 1117 if (d->mInited && d->mCacheLimit && size() > d->mCacheLimit) { 1118 if (size() > (int)(d->mCacheLimit)) { 1119 // Can't wait any longer, do it immediately 1120 d->removeEntries(d->mCacheLimit * 0.65); 1121 } 1122 } 1123 } 1124 1125 KPixmapCache::RemoveStrategy KPixmapCache::removeEntryStrategy() const 1126 { 1127 return d->mRemoveStrategy; 1128 } 1129 1130 void KPixmapCache::setRemoveEntryStrategy(KPixmapCache::RemoveStrategy strategy) 1131 { 1132 d->mRemoveStrategy = strategy; 1133 } 1134 1135 bool KPixmapCache::recreateCacheFiles() 1136 { 1137 if (!isEnabled()) { 1138 return false; 1139 } 1140 1141 KPCLockFile lock(d->mLockFileName); 1142 // Hope we got the lock... 1143 1144 d->invalidateMmapFiles(); 1145 d->mEnabled = false; 1146 1147 // Create index file 1148 QSaveFile indexfile(d->mIndexFile); 1149 if (!indexfile.open(QIODevice::WriteOnly)) { 1150 kError() << "Couldn't create index file" << d->mIndexFile; 1151 return false; 1152 } 1153 1154 d->mCacheId = QDateTime::currentDateTime().toTime_t(); 1155 d->mTimestamp = QDateTime::currentDateTime().toTime_t(); 1156 1157 // We can't know the full size until custom headers written. 1158 // mmapFiles() will take care of correcting the size. 1159 KPixmapCacheIndexHeader indexHeader = { {static_cast<time_t>(0)}, KPIXMAPCACHE_VERSION, 0, d->mCacheId, static_cast<time_t>(d->mTimestamp) }; 1160 memcpy(indexHeader.magic, KPC_MAGIC, sizeof(indexHeader.magic)); 1161 1162 indexfile.write(reinterpret_cast<char *>(&indexHeader), sizeof indexHeader); 1163 1164 // Create data file 1165 QSaveFile datafile(d->mDataFile); 1166 if (!datafile.open(QIODevice::WriteOnly)) { 1167 kError() << "Couldn't create data file" << d->mDataFile; 1168 return false; 1169 } 1170 1171 KPixmapCacheDataHeader dataHeader = { {0}, KPIXMAPCACHE_VERSION, sizeof dataHeader }; 1172 memcpy(dataHeader.magic, KPC_MAGIC, sizeof(dataHeader.magic)); 1173 1174 datafile.write(reinterpret_cast<char *>(&dataHeader), sizeof dataHeader); 1175 1176 setValid(true); 1177 1178 QDataStream istream(&indexfile); 1179 writeCustomIndexHeader(istream); 1180 d->mHeaderSize = indexfile.pos(); 1181 1182 d->mIndexRootOffset = d->mHeaderSize; 1183 1184 // Close the files and mmap them (if mmapping is used) 1185 if (!indexfile.commit() || !datafile.commit()) { 1186 return false; 1187 } 1188 1189 d->mEnabled = true; 1190 d->mmapFiles(); 1191 1192 return true; 1193 } 1194 1195 void KPixmapCache::deleteCache(const QString &name) 1196 { 1197 const QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation); 1198 const QString indexFile = cacheDir + "/kpc/" + name + ".index"; 1199 const QString dataFile = cacheDir + "/kpc/" + name + ".data"; 1200 1201 QFile::remove(indexFile); 1202 QFile::remove(dataFile); 1203 } 1204 1205 void KPixmapCache::discard() 1206 { 1207 // To "discard" the cache we simply have to make sure that every that 1208 // was in there before is no longer present when we search for them. 1209 // Easiest way to do *that* is to simply delete the index. 1210 1211 KPCLockFile lock(d->mLockFileName); 1212 if (!lock.isValid()) { 1213 kError(264) << "Unable to lock pixmap cache when trying to discard it"; 1214 return; 1215 } 1216 1217 QIODevice *device = d->indexDevice(); 1218 if (!device) { 1219 kError(264) << "Unable to access index when trying to discard cache"; 1220 return; 1221 } 1222 1223 device->seek(d->mIndexRootOffset); 1224 QDataStream stream(device); 1225 1226 // Stream an empty QString as the hash key to signify that the cache 1227 // has been discarded. 1228 stream << QString(); 1229 1230 if (d->mUseQPixmapCache) { 1231 // TODO: This is broken, it removes every cached QPixmap in the whole 1232 // process, not just this KPixmapCache. 1233 QPixmapCache::clear(); 1234 } 1235 } 1236 1237 void KPixmapCache::removeEntries(int newsize) 1238 { 1239 if (!newsize) { 1240 newsize = d->mCacheLimit; 1241 1242 if (!newsize) { 1243 // nothing to do! 1244 return; 1245 } 1246 } 1247 1248 d->removeEntries(newsize); 1249 } 1250 1251 bool KPixmapCache::find(const QString &key, QPixmap &pix) 1252 { 1253 ensureInited(); 1254 if (!isValid()) { 1255 return false; 1256 } 1257 1258 //kDebug(264) << "key:" << key << ", use QPC:" << d->mUseQPixmapCache; 1259 // First try the QPixmapCache 1260 if (d->mUseQPixmapCache && QPixmapCache::find(d->qpcKey(key), &pix)) { 1261 //kDebug(264) << "Found from QPC"; 1262 return true; 1263 } 1264 1265 KPCLockFile lock(d->mLockFileName); 1266 if (!lock.isValid()) { 1267 return false; 1268 } 1269 1270 // Try to find the offset 1271 QString indexkey = d->indexKey(key); 1272 int offset = d->findOffset(indexkey); 1273 //kDebug(264) << "found offset" << offset; 1274 if (offset == -1) { 1275 return false; 1276 } 1277 1278 // Load the data 1279 bool ret = d->loadData(offset, pix); 1280 if (ret && d->mUseQPixmapCache) { 1281 // This pixmap wasn't in QPC, put it there 1282 QPixmapCache::insert(d->qpcKey(key), pix); 1283 } 1284 return ret; 1285 } 1286 1287 bool KPixmapCache::Private::loadData(int offset, QPixmap &pix) 1288 { 1289 // Open device and datastream on it 1290 QIODevice *device = dataDevice(); 1291 if (!device) { 1292 return false; 1293 } 1294 //kDebug(264) << "Seeking to pos" << offset << "/" << file.size(); 1295 if (!device->seek(offset)) { 1296 kError() << "Couldn't seek to pos" << offset; 1297 delete device; 1298 return false; 1299 } 1300 QDataStream stream(device); 1301 1302 // Load 1303 QString fkey; 1304 stream >> fkey; 1305 1306 // Load image info and compressed data 1307 qint32 format, w, h, bpl; 1308 stream >> format >> w >> h >> bpl; 1309 QByteArray imgdatacompressed; 1310 stream >> imgdatacompressed; 1311 1312 // Uncompress the data and create the image 1313 // TODO: make sure this actually works. QImage ctor we use here seems to 1314 // want 32-bit aligned data. QByteArray uses malloc() to allocate it's 1315 // data, which _probably_ returns 32-bit aligned data. 1316 QByteArray imgdata = qUncompress(imgdatacompressed); 1317 if (!imgdata.isEmpty()) { 1318 QImage img((const uchar *)imgdata.constData(), w, h, bpl, (QImage::Format)format); 1319 img.bits(); // make deep copy since we don't want to keep imgdata around 1320 pix = QPixmap::fromImage(img); 1321 } else { 1322 pix = QPixmap(w, h); 1323 } 1324 1325 if (!q->loadCustomData(stream)) { 1326 delete device; 1327 return false; 1328 } 1329 1330 delete device; 1331 if (stream.status() != QDataStream::Ok) { 1332 kError() << "stream is bad :-( status=" << stream.status(); 1333 return false; 1334 } 1335 1336 //kDebug(264) << "pixmap successfully loaded"; 1337 return true; 1338 } 1339 1340 bool KPixmapCache::loadCustomData(QDataStream &) 1341 { 1342 return true; 1343 } 1344 1345 void KPixmapCache::insert(const QString &key, const QPixmap &pix) 1346 { 1347 ensureInited(); 1348 if (!isValid()) { 1349 return; 1350 } 1351 1352 //kDebug(264) << "key:" << key << ", size:" << pix.width() << "x" << pix.height(); 1353 // Insert to QPixmapCache as well 1354 if (d->mUseQPixmapCache) { 1355 QPixmapCache::insert(d->qpcKey(key), pix); 1356 } 1357 1358 KPCLockFile lock(d->mLockFileName); 1359 if (!lock.isValid()) { 1360 return; 1361 } 1362 1363 // Insert to cache 1364 QString indexkey = d->indexKey(key); 1365 int offset = d->writeData(key, pix); 1366 //kDebug(264) << "data is at offset" << offset; 1367 if (offset == -1) { 1368 return; 1369 } 1370 1371 d->writeIndex(indexkey, offset); 1372 1373 // Make sure the cache size stays within limits 1374 if (d->mCacheLimit && size() > d->mCacheLimit) { 1375 lock.unlock(); 1376 if (size() > (int)(d->mCacheLimit)) { 1377 // Can't wait any longer, do it immediately 1378 d->removeEntries(d->mCacheLimit * 0.65); 1379 } 1380 } 1381 } 1382 1383 int KPixmapCache::Private::writeData(const QString &key, const QPixmap &pix) 1384 { 1385 // Open device and datastream on it 1386 QIODevice *device = dataDevice(); 1387 if (!device) { 1388 return -1; 1389 } 1390 int offset = device->size(); 1391 device->seek(offset); 1392 QDataStream stream(device); 1393 1394 // Write the data 1395 stream << key; 1396 // Write image info and compressed data 1397 QImage img = pix.toImage(); 1398 QByteArray imgdatacompressed = qCompress(img.bits(), img.byteCount()); 1399 stream << (qint32)img.format() << (qint32)img.width() << (qint32)img.height() << (qint32)img.bytesPerLine(); 1400 stream << imgdatacompressed; 1401 1402 q->writeCustomData(stream); 1403 1404 delete device; 1405 return offset; 1406 } 1407 1408 bool KPixmapCache::writeCustomData(QDataStream &) 1409 { 1410 return true; 1411 } 1412 1413 void KPixmapCache::Private::writeIndex(const QString &key, int dataoffset) 1414 { 1415 // Open device and datastream on it 1416 QIODevice *device = indexDevice(); 1417 if (!device) { 1418 return; 1419 } 1420 QDataStream stream(device); 1421 1422 writeIndexEntry(stream, key, dataoffset); 1423 delete device; 1424 } 1425 1426 QPixmap KPixmapCache::loadFromFile(const QString &filename) 1427 { 1428 QFileInfo fi(filename); 1429 if (!fi.exists()) { 1430 return QPixmap(); 1431 } else if (fi.lastModified() > timestamp()) { 1432 // Cache is obsolete, will be regenerated 1433 discard(); 1434 } 1435 1436 QPixmap pix; 1437 QString key("file:" + filename); 1438 if (!find(key, pix)) { 1439 // It wasn't in the cache, so load it... 1440 pix = QPixmap(filename); 1441 if (pix.isNull()) { 1442 return pix; 1443 } 1444 // ... and put it there 1445 insert(key, pix); 1446 } 1447 1448 return pix; 1449 } 1450 1451 #ifndef _WIN32_WCE 1452 QPixmap KPixmapCache::loadFromSvg(const QString &filename, const QSize &size) 1453 { 1454 QFileInfo fi(filename); 1455 if (!fi.exists()) { 1456 return QPixmap(); 1457 } else if (fi.lastModified() > timestamp()) { 1458 // Cache is obsolete, will be regenerated 1459 discard(); 1460 } 1461 1462 QPixmap pix; 1463 QString key = QString("file:%1_%2_%3").arg(filename).arg(size.width()).arg(size.height()); 1464 if (!find(key, pix)) { 1465 // It wasn't in the cache, so load it... 1466 QSvgRenderer svg; 1467 if (!svg.load(filename)) { 1468 return pix; // null pixmap 1469 } else { 1470 QSize pixSize = size.isValid() ? size : svg.defaultSize(); 1471 pix = QPixmap(pixSize); 1472 pix.fill(Qt::transparent); 1473 1474 QPainter p(&pix); 1475 svg.render(&p, QRectF(QPointF(), pixSize)); 1476 } 1477 1478 // ... and put it there 1479 insert(key, pix); 1480 } 1481 1482 return pix; 1483 } 1484 #endif 1485