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