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