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

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
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • KIO
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • Kross
  • KUtils
  • Nepomuk
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.5.4
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