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

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
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.5.7
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