• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • kdelibs
  • KDE Home
  • Contact Us
 

KDEUI

kpixmapcache.cpp

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

KDEUI

Skip menu "KDEUI"
  • Main Page
  • Modules
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.3
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal