• Skip to content
  • Skip to link menu
KDE 4.3 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_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; indexHeader = 0; }
00293         QFile* file;  // If this is not null, then the file is mmapped
00294 
00295         // This points to the mmap'ed file area.
00296         KPixmapCacheIndexHeader *indexHeader;
00297 
00298         quint32 size;  // Number of currently used bytes
00299         quint32 available;  // Number of available bytes (including those reserved for mmap)
00300     };
00301     MmapInfo mIndexMmapInfo;
00302     MmapInfo mDataMmapInfo;
00303     // Mmaps given file, growing it to newsize bytes.
00304     bool mmapFile(const QString& filename, MmapInfo* info, int newsize);
00305     void unmmapFile(MmapInfo* info);
00306 
00307 
00308     // Used by removeEntries()
00309     class KPixmapCacheEntry
00310     {
00311     public:
00312         KPixmapCacheEntry(int indexoffset_, const QString& key_, int dataoffset_,
00313                           int pos_, quint32 timesused_, quint32 lastused_)
00314         {
00315             indexoffset = indexoffset_;
00316             key = key_;
00317             dataoffset = dataoffset_;
00318             pos = pos_;
00319             timesused = timesused_;
00320             lastused = lastused_;
00321         }
00322 
00323         int indexoffset;
00324         QString key;
00325         int dataoffset;
00326 
00327         int pos;
00328         quint32 timesused;
00329         quint32 lastused;
00330     };
00331 
00332     // Various comparison functions for different removal strategies
00333     static bool compareEntriesByAge(const KPixmapCacheEntry& a, const KPixmapCacheEntry& b)
00334     {
00335         return a.pos > b.pos;
00336     }
00337     static bool compareEntriesByTimesUsed(const KPixmapCacheEntry& a, const KPixmapCacheEntry& b)
00338     {
00339         return a.timesused > b.timesused;
00340     }
00341     static bool compareEntriesByLastUsed(const KPixmapCacheEntry& a, const KPixmapCacheEntry& b)
00342     {
00343         return a.lastused > b.lastused;
00344     }
00345 
00346     // Helper class to run the possibly expensive removeEntries() operation in
00347     //  the background thread
00348     class RemovalThread : public QThread
00349     {
00350     public:
00351         RemovalThread(KPixmapCache::Private* _d) : QThread()
00352         {
00353             d = _d;
00354             mRemovalScheduled = false;
00355         }
00356         ~RemovalThread()
00357         {
00358         }
00359 
00360         void scheduleRemoval(int newsize)
00361         {
00362             mNewSize = newsize;
00363             if (!mRemovalScheduled) {
00364                 QTimer::singleShot(5000, this, SLOT(start()));
00365                 mRemovalScheduled = true;
00366             }
00367         }
00368 
00369     protected:
00370         virtual void run()
00371         {
00372             mRemovalScheduled = false;
00373             kDebug(264) << "starting";
00374             d->removeEntries(mNewSize);
00375             kDebug(264) << "done";
00376         }
00377 
00378     private:
00379         bool mRemovalScheduled;
00380         int mNewSize;
00381         KPixmapCache::Private* d;
00382     };
00383     RemovalThread* mRemovalThread;
00384 };
00385 
00386 // List of KPixmapCache::Private instances.
00387 QList<KPixmapCache::Private *> KPixmapCache::Private::mCaches;
00388 
00389 KPixmapCache::Private::Private(KPixmapCache* _q)
00390 {
00391     q = _q;
00392     mCaches.append(this);
00393     mRemovalThread = 0;
00394 }
00395 
00396 KPixmapCache::Private::~Private()
00397 {
00398     mCaches.removeAll(this);
00399 }
00400 
00401 bool KPixmapCache::Private::mmapFiles()
00402 {
00403     unmmapFiles();  // Noop if nothing has been mmapped
00404     if (!q->isValid()) {
00405         return false;
00406     }
00407 
00408     //TODO: 100MB limit if we have no cache limit, is that sensible?
00409     int cacheLimit = mCacheLimit > 0 ? mCacheLimit : 100 * 1024;
00410     if (!mmapFile(mIndexFile, &mIndexMmapInfo, (int)(cacheLimit * 0.4 + 100) * 1024)) {
00411         q->setValid(false);
00412         return false;
00413     }
00414 
00415     if (!mmapFile(mDataFile, &mDataMmapInfo, (int)(cacheLimit * 1.5 + 500) * 1024)) {
00416         unmmapFile(&mIndexMmapInfo);
00417         q->setValid(false);
00418         return false;
00419     }
00420 
00421     return true;
00422 }
00423 
00424 void KPixmapCache::Private::unmmapFiles()
00425 {
00426     unmmapFile(&mIndexMmapInfo);
00427     unmmapFile(&mDataMmapInfo);
00428 }
00429 
00430 void KPixmapCache::Private::invalidateMmapFiles()
00431 {
00432     if (!q->isValid())
00433         return;
00434     // Set cache id to 0, this will force a reload the next time the files are used
00435     if (mIndexMmapInfo.file) {
00436         kDebug(264) << "Invalidating cache";
00437         mIndexMmapInfo.indexHeader->cacheId = 0;
00438     }
00439 }
00440 
00441 bool KPixmapCache::Private::mmapFile(const QString& filename, MmapInfo* info, int newsize)
00442 {
00443     info->file = new QFile(filename);
00444     if (!info->file->open(QIODevice::ReadWrite)) {
00445         kDebug(264) << "Couldn't open" << filename;
00446         delete info->file;
00447         info->file = 0;
00448         return false;
00449     }
00450 
00451     if (!info->size) {
00452         info->size = info->file->size();
00453     }
00454     info->available = newsize;
00455 
00456     // Only resize if greater than current file size, otherwise we may cause SIGBUS
00457     // errors from mmap().
00458     if (info->file->size() < info->available && ftruncate(info->file->handle(), info->available) < 0) {
00459         kError(264) << "Couldn't resize" << filename << "to" << newsize;
00460         delete info->file;
00461         info->file = 0;
00462         return false;
00463     }
00464 
00465     //void* indexMem = mmap(0, info->available, PROT_READ | PROT_WRITE, MAP_SHARED, info->file->handle(), 0);
00466     void *indexMem = info->file->map(0, info->available);
00467     if (indexMem == 0) {
00468         kError() << "mmap failed for" << filename;
00469         delete info->file;
00470         info->file = 0;
00471         return false;
00472     }
00473     info->indexHeader = reinterpret_cast<KPixmapCacheIndexHeader *>(indexMem);
00474 #ifdef HAVE_MADVISE
00475     posix_madvise(indexMem, info->size, POSIX_MADV_WILLNEED);
00476 #endif
00477 
00478     info->file->close();
00479 
00480     // Update our stored file size.  Other objects that have this mmaped will have to
00481     // invalidate their map if size is different.
00482     if(0 == info->indexHeader->size) {
00483         // This size includes index header and and custom headers tacked on
00484         // by subclasses.
00485         info->indexHeader->size = mHeaderSize;
00486         info->size = info->indexHeader->size;
00487     }
00488 
00489     return true;
00490 }
00491 
00492 void KPixmapCache::Private::unmmapFile(MmapInfo* info)
00493 {
00494     if (info->file) {
00495         info->file->unmap(reinterpret_cast<uchar*>(info->indexHeader));
00496         info->indexHeader = 0;
00497         info->available = 0;
00498         info->size = 0;
00499 
00500         delete info->file;
00501         info->file = 0;
00502     }
00503 }
00504 
00505 
00506 QIODevice* KPixmapCache::Private::indexDevice()
00507 {
00508     QIODevice* device = 0;
00509 
00510     if (mIndexMmapInfo.file) {
00511         // Make sure the file still exists
00512         QFileInfo fi(mIndexFile);
00513 
00514         if (!fi.exists() || fi.size() != mIndexMmapInfo.available) {
00515             kDebug(264) << "File size has changed, re-initializing.";
00516             q->recreateCacheFiles(); // Recreates memory maps as well.
00517         }
00518 
00519         fi.refresh();
00520         if(fi.exists() && fi.size() == mIndexMmapInfo.available) {
00521             // Create the device
00522             device = new KPCMemoryDevice(
00523                              reinterpret_cast<char*>(mIndexMmapInfo.indexHeader),
00524                              &mIndexMmapInfo.size, mIndexMmapInfo.available);
00525         }
00526 
00527         // Is it possible to have a valid cache with no file?  If not it would be easier
00528         // to do return 0 in the else portion of the prior test.
00529         if(!q->isValid()) {
00530             delete device;
00531             return 0;
00532         }
00533     }
00534 
00535     if (!device) {
00536         QFile* file = new QFile(mIndexFile);
00537         if (!file->exists() || (size_t) file->size() < sizeof(KPixmapCacheIndexHeader)) {
00538             q->recreateCacheFiles();
00539         }
00540 
00541         if (!q->isValid() || !file->open(QIODevice::ReadWrite)) {
00542             kDebug(264) << "Couldn't open index file" << mIndexFile;
00543             delete file;
00544             return 0;
00545         }
00546 
00547         device = file;
00548     }
00549 
00550     // Make sure the device is up-to-date
00551     KPixmapCacheIndexHeader indexHeader;
00552 
00553     int numRead = device->read(reinterpret_cast<char *>(&indexHeader), sizeof indexHeader);
00554     if (sizeof indexHeader != numRead) {
00555         kError(264) << "Unable to read header from pixmap cache index.";
00556         delete device;
00557         return 0;
00558     }
00559 
00560     if (indexHeader.cacheId != mCacheId) {
00561         kDebug(264) << "Cache has changed, reloading";
00562         delete device;
00563 
00564         init();
00565         if (!q->isValid()) {
00566             return 0;
00567         } else {
00568             return indexDevice(); // Careful, this is a recursive call.
00569         }
00570     }
00571 
00572     return device;
00573 }
00574 
00575 QIODevice* KPixmapCache::Private::dataDevice()
00576 {
00577     if (mDataMmapInfo.file) {
00578         // Make sure the file still exists
00579         QFileInfo fi(mDataFile);
00580 
00581         if (!fi.exists() || fi.size() != mDataMmapInfo.available) {
00582             kDebug(264) << "File size has changed, re-initializing.";
00583             q->recreateCacheFiles(); // Recreates memory maps as well.
00584 
00585             // Index file has also been recreated so we cannot continue with
00586             //  modifying the data file because it would make things inconsistent.
00587             return 0;
00588         }
00589 
00590         fi.refresh();
00591         if (fi.exists() && fi.size() == mDataMmapInfo.available) {
00592             // Create the device
00593             return new KPCMemoryDevice(
00594                            reinterpret_cast<char*>(mDataMmapInfo.indexHeader),
00595                            &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
  • KPty
  • Kross
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.6.1
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