23 #include <QtCore/QString>
24 #include <QtGui/QPixmap>
25 #include <QtCore/QFile>
26 #include <QtCore/QDataStream>
27 #include <QtCore/QFileInfo>
28 #include <QtCore/QDateTime>
29 #include <QtGui/QPixmapCache>
30 #include <QtCore/QtGlobal>
31 #include <QtGui/QPainter>
32 #include <QtCore/QQueue>
33 #include <QtCore/QTimer>
34 #include <QtCore/QMutex>
35 #include <QtCore/QMutexLocker>
36 #include <QtCore/QList>
52 #include <sys/types.h>
55 #if defined(HAVE_MADVISE)
63 extern "C" int madvise(caddr_t addr,
size_t len,
int advice);
67 #define KPIXMAPCACHE_VERSION 0x000208
74 KPCLockFile(
const QString& filename)
80 for (
int i = 0; i < 5; i++) {
90 kError() <<
"Failed to lock file" << filename <<
", last result =" << result;
107 bool isValid()
const {
return mValid; }
121 static const char KPC_MAGIC[] =
"KDE PIXMAP CACHE DEUX";
122 struct KPixmapCacheDataHeader
126 char magic[
sizeof(KPC_MAGIC) - 1];
131 struct KPixmapCacheIndexHeader
135 char magic[
sizeof(KPC_MAGIC) - 1];
148 virtual ~KPCMemoryDevice();
150 virtual qint64 size()
const {
return *mSize; }
151 void setSize(
quint32 s) { *mSize = s; }
152 virtual bool seek(
qint64 pos);
156 virtual qint64 writeData(
const char* data,
qint64 maxSize);
160 KPixmapCacheIndexHeader *mHeader;
170 mHeader =
reinterpret_cast<KPixmapCacheIndexHeader *
>(start);
172 mAvailable = available;
175 this->
open(QIODevice::ReadWrite);
178 *mSize = mHeader->size;
180 mInitialSize = *mSize;
183 KPCMemoryDevice::~KPCMemoryDevice()
185 if (*mSize != mInitialSize) {
187 mHeader->size = *mSize;
191 bool KPCMemoryDevice::seek(
qint64 pos)
193 if (pos < 0 || pos > *mSize) {
197 return QIODevice::seek(pos);
200 qint64 KPCMemoryDevice::readData(
char* data,
qint64 len)
202 len = qMin(len,
qint64(*mSize) - mPos);
206 memcpy(data, mMemory + mPos, len);
211 qint64 KPCMemoryDevice::writeData(
const char* data,
qint64 len)
213 if (mPos + len > mAvailable) {
214 kError() <<
"Overflow of" << mPos+len - mAvailable;
217 memcpy(mMemory + mPos, (uchar*)data, len);
219 *mSize = qMax(*mSize, mPos);
243 void invalidateMmapFiles();
246 static QList<KPixmapCache::Private *> mCaches;
248 static unsigned kpcNumber;
250 int findOffset(
const QString& key);
251 int binarySearchKey(QDataStream& stream,
const QString& key,
int start);
252 void writeIndexEntry(QDataStream& stream,
const QString& key,
int dataoffset);
254 bool checkLockFile();
255 bool checkFileVersion(
const QString& filename);
256 bool loadIndexHeader();
257 bool loadDataHeader();
260 bool scheduleRemoveEntries(
int newsize);
263 bool loadData(
int offset, QPixmap& pix);
264 int writeData(
const QString& key,
const QPixmap& pix);
265 void writeIndex(
const QString& key,
int offset);
291 bool mUseQPixmapCache:4;
300 MmapInfo() { file = 0; indexHeader = 0; }
304 KPixmapCacheIndexHeader *indexHeader;
309 MmapInfo mIndexMmapInfo;
310 MmapInfo mDataMmapInfo;
312 bool mmapFile(
const QString& filename, MmapInfo* info,
int newsize);
313 void unmmapFile(MmapInfo* info);
317 class KPixmapCacheEntry
320 KPixmapCacheEntry(
int indexoffset_,
const QString& key_,
int dataoffset_,
322 : indexoffset(indexoffset_),
324 dataoffset(dataoffset_),
326 timesused(timesused_),
341 static bool compareEntriesByAge(
const KPixmapCacheEntry& a,
const KPixmapCacheEntry& b)
343 return a.pos > b.pos;
345 static bool compareEntriesByTimesUsed(
const KPixmapCacheEntry& a,
const KPixmapCacheEntry& b)
347 return a.timesused > b.timesused;
349 static bool compareEntriesByLastUsed(
const KPixmapCacheEntry& a,
const KPixmapCacheEntry& b)
351 return a.lastused > b.lastused;
356 QList<KPixmapCache::Private *> KPixmapCache::Private::mCaches;
358 unsigned KPixmapCache::Private::kpcNumber = 0;
363 mCaches.append(
this);
364 mThisString =
QString(
"%1").arg(kpcNumber++);
367 KPixmapCache::Private::~Private()
369 mCaches.removeAll(
this);
372 bool KPixmapCache::Private::mmapFiles()
380 int cacheLimit = mCacheLimit > 0 ? mCacheLimit : 100 * 1024;
381 if (!mmapFile(mIndexFile, &mIndexMmapInfo, (
int)(cacheLimit * 0.4 + 100) * 1024)) {
386 if (!mmapFile(mDataFile, &mDataMmapInfo, (
int)(cacheLimit * 1.5 + 500) * 1024)) {
387 unmmapFile(&mIndexMmapInfo);
395 void KPixmapCache::Private::unmmapFiles()
397 unmmapFile(&mIndexMmapInfo);
398 unmmapFile(&mDataMmapInfo);
401 void KPixmapCache::Private::invalidateMmapFiles()
406 if (mIndexMmapInfo.file) {
407 kDebug(264) <<
"Invalidating cache";
408 mIndexMmapInfo.indexHeader->cacheId = 0;
412 bool KPixmapCache::Private::mmapFile(
const QString& filename, MmapInfo* info,
int newsize)
414 info->file =
new QFile(filename);
415 if (!info->file->open(QIODevice::ReadWrite)) {
416 kDebug(264) <<
"Couldn't open" << filename;
423 info->size = info->file->size();
425 info->available = newsize;
429 if (info->file->size() < info->available && !info->file->resize(info->available)) {
430 kError(264) <<
"Couldn't resize" << filename <<
"to" << newsize;
437 void *indexMem = info->file->map(0, info->available);
439 kError() <<
"mmap failed for" << filename;
444 info->indexHeader =
reinterpret_cast<KPixmapCacheIndexHeader *
>(indexMem);
446 posix_madvise(indexMem, info->size, POSIX_MADV_WILLNEED);
453 if(0 == info->indexHeader->size) {
456 info->indexHeader->size = mHeaderSize;
457 info->size = info->indexHeader->size;
463 void KPixmapCache::Private::unmmapFile(MmapInfo* info)
466 info->file->unmap(reinterpret_cast<uchar*>(info->indexHeader));
467 info->indexHeader = 0;
477 QIODevice* KPixmapCache::Private::indexDevice()
481 if (mIndexMmapInfo.file) {
483 QFileInfo fi(mIndexFile);
485 if (!fi.exists() || fi.size() != mIndexMmapInfo.available) {
486 kDebug(264) <<
"File size has changed, re-initializing.";
487 q->recreateCacheFiles();
491 if(fi.exists() && fi.size() == mIndexMmapInfo.available) {
493 device =
new KPCMemoryDevice(
494 reinterpret_cast<char*>(mIndexMmapInfo.indexHeader),
495 &mIndexMmapInfo.size, mIndexMmapInfo.available);
507 QFile* file =
new QFile(mIndexFile);
508 if (!file->exists() || (size_t) file->size() <
sizeof(KPixmapCacheIndexHeader)) {
509 q->recreateCacheFiles();
512 if (!q->isValid() || !file->open(QIODevice::ReadWrite)) {
513 kDebug(264) <<
"Couldn't open index file" << mIndexFile;
522 KPixmapCacheIndexHeader indexHeader;
524 int numRead = device->read(reinterpret_cast<char *>(&indexHeader),
sizeof indexHeader);
525 if (
sizeof indexHeader != numRead) {
526 kError(264) <<
"Unable to read header from pixmap cache index.";
531 if (indexHeader.cacheId != mCacheId) {
532 kDebug(264) <<
"Cache has changed, reloading";
539 return indexDevice();
546 QIODevice* KPixmapCache::Private::dataDevice()
548 if (mDataMmapInfo.file) {
550 QFileInfo fi(mDataFile);
552 if (!fi.exists() || fi.size() != mDataMmapInfo.available) {
553 kDebug(264) <<
"File size has changed, re-initializing.";
554 q->recreateCacheFiles();
562 if (fi.exists() && fi.size() == mDataMmapInfo.available) {
564 return new KPCMemoryDevice(
565 reinterpret_cast<char*>(mDataMmapInfo.indexHeader),
566 &mDataMmapInfo.size, mDataMmapInfo.available);
572 QFile* file =
new QFile(mDataFile);
573 if (!file->exists() || (size_t) file->size() <
sizeof(KPixmapCacheDataHeader)) {
574 q->recreateCacheFiles();
580 if (!file->open(QIODevice::ReadWrite)) {
581 kDebug(264) <<
"Couldn't open data file" << mDataFile;
588 int KPixmapCache::Private::binarySearchKey(QDataStream& stream,
const QString& key,
int start)
590 stream.device()->seek(start);
595 qint32 leftchild, rightchild;
596 stream >> fkey >> foffset >> timesused >> lastused >> leftchild >> rightchild;
598 if (fkey.isEmpty()) {
604 return binarySearchKey(stream, key, leftchild);
606 }
else if (key == fkey) {
608 }
else if (rightchild) {
609 return binarySearchKey(stream, key, rightchild);
615 int KPixmapCache::Private::findOffset(
const QString& key)
622 device->seek(mIndexRootOffset);
623 QDataStream stream(device);
628 if (!stream.atEnd()) {
635 if (fkey.isEmpty()) {
640 int nodeoffset = binarySearchKey(stream, key, mIndexRootOffset);
643 device->seek(nodeoffset);
650 stream >> foffset >> timesused;
653 lastused = ::time(0);
654 stream.device()->seek(stream.device()->pos() -
sizeof(
quint32));
655 stream << timesused << lastused;
666 bool KPixmapCache::Private::checkLockFile()
669 if (QFile::exists(mLockFileName)) {
671 kError() <<
"Couldn't remove lockfile" << mLockFileName;
678 bool KPixmapCache::Private::checkFileVersion(
const QString& filename)
684 if (QFile::exists(filename)) {
687 if (!f.open(QIODevice::ReadOnly)) {
688 kError() <<
"Couldn't open file" << filename;
694 KPixmapCacheIndexHeader indexHeader;
697 if(
sizeof indexHeader != f.read(reinterpret_cast<char*>(&indexHeader),
sizeof indexHeader) ||
698 qstrncmp(indexHeader.magic, KPC_MAGIC,
sizeof(indexHeader.magic)) != 0)
700 kDebug(264) <<
"File" << filename <<
"is not KPixmapCache file, or is";
701 kDebug(264) <<
"version <= 0x000207, will recreate...";
702 return q->recreateCacheFiles();
711 kDebug(264) <<
"File" << filename <<
"has newer version, disabling cache";
715 kDebug(264) <<
"File" << filename <<
"is outdated, will recreate...";
718 return q->recreateCacheFiles();
721 bool KPixmapCache::Private::loadDataHeader()
724 QFile file(mDataFile);
725 if (!file.open(QIODevice::ReadOnly)) {
729 KPixmapCacheDataHeader dataHeader;
730 if(
sizeof dataHeader != file.read(reinterpret_cast<char*>(&dataHeader),
sizeof dataHeader)) {
731 kDebug(264) <<
"Unable to read from data file" << mDataFile;
735 mDataMmapInfo.size = dataHeader.size;
739 bool KPixmapCache::Private::loadIndexHeader()
742 QFile file(mIndexFile);
743 if (!file.open(QIODevice::ReadOnly)) {
747 KPixmapCacheIndexHeader indexHeader;
748 if(
sizeof indexHeader != file.read(reinterpret_cast<char*>(&indexHeader),
sizeof indexHeader)) {
749 kWarning(264) <<
"Failed to read index file's header";
750 q->recreateCacheFiles();
754 mCacheId = indexHeader.cacheId;
755 mTimestamp = indexHeader.timestamp;
756 mIndexMmapInfo.size = indexHeader.size;
758 QDataStream stream(&file);
761 if (!q->loadCustomIndexHeader(stream)) {
765 mHeaderSize = file.pos();
766 mIndexRootOffset = file.pos();
773 const QByteArray latin1 = key.toLatin1();
774 return QString(
"%1%2").arg((ushort)qChecksum(latin1.data(), latin1.size()), 4, 16, QLatin1Char(
'0')).arg(key);
780 return mThisString + key;
783 void KPixmapCache::Private::writeIndexEntry(QDataStream& stream,
const QString& key,
int dataoffset)
786 qint32 offset = stream.device()->size();
788 int parentoffset = binarySearchKey(stream, key, mIndexRootOffset);
789 if (parentoffset != stream.device()->size()) {
792 stream.device()->seek(parentoffset);
796 if (key == fkey || fkey.isEmpty()) {
798 offset = parentoffset;
802 stream.device()->seek(offset);
804 stream << key << (
qint32)dataoffset;
813 if (parentoffset != offset) {
814 stream.device()->seek(parentoffset);
818 stream >> fkey >> foffset >> timesused >> lastused;
832 KPCLockFile lock(mLockFileName);
833 if (!lock.isValid()) {
834 kDebug(264) <<
"Couldn't lock cache" << mName;
837 QMutexLocker mutexlocker(&mMutex);
840 QFile indexfile(mIndexFile);
841 if (!indexfile.open(QIODevice::ReadOnly)) {
842 kDebug(264) <<
"Couldn't open old index file";
845 QDataStream istream(&indexfile);
846 QFile datafile(mDataFile);
847 if (!datafile.open(QIODevice::ReadOnly)) {
848 kDebug(264) <<
"Couldn't open old data file";
851 if (datafile.size() <= newsize*1024) {
852 kDebug(264) <<
"Cache size is already within limit (" << datafile.size() <<
" <= " << newsize*1024 <<
")";
855 QDataStream dstream(&datafile);
857 QFile newindexfile(mIndexFile +
".new");
858 if (!newindexfile.open(QIODevice::ReadWrite)) {
859 kDebug(264) <<
"Couldn't open new index file";
862 QDataStream newistream(&newindexfile);
863 QFile newdatafile(mDataFile +
".new");
864 if (!newdatafile.open(QIODevice::WriteOnly)) {
865 kDebug(264) <<
"Couldn't open new data file";
868 QDataStream newdstream(&newdatafile);
871 char*
header =
new char[mHeaderSize];
872 if (istream.readRawData(header, mHeaderSize) != (int)mHeaderSize) {
873 kDebug(264) <<
"Couldn't read index header";
879 reinterpret_cast<KPixmapCacheIndexHeader *
>(header)->size = 0;
880 newistream.writeRawData(header, mHeaderSize);
883 int dataheaderlen =
sizeof(KPixmapCacheDataHeader);
887 if (dstream.readRawData(header, dataheaderlen) != dataheaderlen) {
888 kDebug(264) <<
"Couldn't read data header";
894 reinterpret_cast<KPixmapCacheDataHeader *
>(header)->size = 0;
895 newdstream.writeRawData(header, dataheaderlen);
899 QList<KPixmapCacheEntry> entries;
902 open.enqueue(mIndexRootOffset);
903 while (!open.isEmpty()) {
904 int indexoffset = open.dequeue();
905 indexfile.seek(indexoffset);
909 qint32 leftchild, rightchild;
910 istream >> fkey >> foffset >> timesused >> lastused >> leftchild >> rightchild;
911 entries.append(KPixmapCacheEntry(indexoffset, fkey, foffset, entries.count(), timesused, lastused));
913 open.enqueue(leftchild);
916 open.enqueue(rightchild);
922 if (q->removeEntryStrategy() == RemoveOldest) {
923 qSort(entries.begin(), entries.end(), compareEntriesByAge);
924 }
else if (q->removeEntryStrategy() == RemoveSeldomUsed) {
925 qSort(entries.begin(), entries.end(), compareEntriesByTimesUsed);
927 qSort(entries.begin(), entries.end(), compareEntriesByLastUsed);
931 int entrieswritten = 0;
932 for (entrieswritten = 0; entrieswritten < entries.count(); entrieswritten++) {
933 const KPixmapCacheEntry& entry = entries[entrieswritten];
935 datafile.seek(entry.dataoffset);
936 int entrysize = -datafile.pos();
942 dstream >> format >> w >> h >> bpl;
943 QByteArray imgdatacompressed;
944 dstream >> imgdatacompressed;
946 if (!q->loadCustomData(dstream)) {
950 entrysize += datafile.pos();
953 if (newdatafile.size() + entrysize > newsize*1024) {
958 int newdataoffset = newdatafile.pos();
960 newdstream << format << w << h << bpl;
961 newdstream << imgdatacompressed;
962 q->writeCustomData(newdstream);
965 writeIndexEntry(newistream, entry.key, newdataoffset);
971 newindexfile.rename(mIndexFile);
972 newdatafile.rename(mDataFile);
973 invalidateMmapFiles();
975 kDebug(264) <<
"Wrote back" << entrieswritten <<
"of" << entries.count() <<
"entries";
984 :d(new Private(this))
987 d->mUseQPixmapCache =
true;
988 d->mCacheLimit = 3 * 1024;
1002 void KPixmapCache::Private::init()
1006 #ifdef DISABLE_PIXMAPCACHE
1007 mValid = mEnabled =
false;
1017 mEnabled &= checkLockFile();
1018 mEnabled &= checkFileVersion(mDataFile);
1019 mEnabled &= checkFileVersion(mIndexFile);
1021 kDebug(264) <<
"Pixmap cache" << mName <<
"is disabled";
1025 q->setValid(loadIndexHeader());
1057 return d->mEnabled && d->mValid;
1069 return d->mTimestamp;
1078 KPCLockFile lock(d->mLockFileName);
1079 if (!lock.isValid()) {
1089 KPixmapCacheIndexHeader header;
1091 if(
sizeof header != device->read(reinterpret_cast<char*>(&header),
sizeof header)) {
1096 header.timestamp = ts;
1098 device->write(reinterpret_cast<char *>(&header),
sizeof header);
1106 if (d->mDataMmapInfo.file) {
1107 return d->mDataMmapInfo.size / 1024;
1109 return QFileInfo(d->mDataFile).size() / 1024;
1114 d->mUseQPixmapCache = use;
1119 return d->mUseQPixmapCache;
1124 return d->mCacheLimit;
1134 d->mCacheLimit = kbytes;
1138 if (d->mInited && d->mCacheLimit &&
size() > d->mCacheLimit) {
1139 if (
size() > (
int)(d->mCacheLimit)) {
1141 d->removeEntries(d->mCacheLimit * 0.65);
1148 return d->mRemoveStrategy;
1153 d->mRemoveStrategy = strategy;
1162 KPCLockFile lock(d->mLockFileName);
1165 d->invalidateMmapFiles();
1166 d->mEnabled =
false;
1170 if (!indexfile.
open(QIODevice::WriteOnly)) {
1171 kError() <<
"Couldn't create index file" << d->mIndexFile;
1175 d->mCacheId = ::time(0);
1176 d->mTimestamp = ::time(0);
1180 KPixmapCacheIndexHeader indexHeader = { {0},
KPIXMAPCACHE_VERSION, 0, d->mCacheId, d->mTimestamp };
1181 memcpy(indexHeader.magic, KPC_MAGIC,
sizeof(indexHeader.magic));
1183 indexfile.write(reinterpret_cast<char*>(&indexHeader),
sizeof indexHeader);
1187 if (!datafile.
open(QIODevice::WriteOnly)) {
1188 kError() <<
"Couldn't create data file" << d->mDataFile;
1193 memcpy(dataHeader.magic, KPC_MAGIC,
sizeof(dataHeader.magic));
1195 datafile.write(reinterpret_cast<char*>(&dataHeader),
sizeof dataHeader);
1199 QDataStream istream(&indexfile);
1201 d->mHeaderSize = indexfile.pos();
1203 d->mIndexRootOffset = d->mHeaderSize;
1232 KPCLockFile lock(d->mLockFileName);
1233 if(!lock.isValid()) {
1234 kError(264) <<
"Unable to lock pixmap cache when trying to discard it";
1240 kError(264) <<
"Unable to access index when trying to discard cache";
1244 device->seek(d->mIndexRootOffset);
1245 QDataStream stream(device);
1251 if (d->mUseQPixmapCache) {
1261 newsize = d->mCacheLimit;
1269 d->removeEntries(newsize);
1286 KPCLockFile lock(d->mLockFileName);
1287 if (!lock.isValid()) {
1292 QString indexkey = d->indexKey(key);
1293 int offset = d->findOffset(indexkey);
1300 bool ret = d->loadData(offset, pix);
1301 if (ret && d->mUseQPixmapCache) {
1308 bool KPixmapCache::Private::loadData(
int offset, QPixmap& pix)
1316 if (!device->seek(offset)) {
1317 kError() <<
"Couldn't seek to pos" << offset;
1321 QDataStream stream(device);
1328 qint32 format, w, h, bpl;
1329 stream >> format >> w >> h >> bpl;
1330 QByteArray imgdatacompressed;
1331 stream >> imgdatacompressed;
1337 QByteArray imgdata = qUncompress(imgdatacompressed);
1338 if (!imgdata.isEmpty()) {
1339 QImage img((
const uchar*)imgdata.constData(), w, h, bpl, (QImage::Format)format);
1341 pix = QPixmap::fromImage(img);
1343 pix = QPixmap(w, h);
1346 if (!q->loadCustomData(stream)) {
1353 kError() <<
"stream is bad :-( status=" << stream.status();
1375 if (d->mUseQPixmapCache) {
1379 KPCLockFile lock(d->mLockFileName);
1380 if (!lock.isValid()) {
1385 QString indexkey = d->indexKey(key);
1386 int offset = d->writeData(key, pix);
1392 d->writeIndex(indexkey, offset);
1395 if (d->mCacheLimit &&
size() > d->mCacheLimit) {
1397 if (
size() > (
int)(d->mCacheLimit)) {
1399 d->removeEntries(d->mCacheLimit * 0.65);
1404 int KPixmapCache::Private::writeData(
const QString& key,
const QPixmap& pix)
1411 int offset = device->size();
1412 device->seek(offset);
1413 QDataStream stream(device);
1418 QImage img = pix.toImage();
1419 QByteArray imgdatacompressed = qCompress(img.bits(), img.numBytes());
1421 stream << imgdatacompressed;
1423 q->writeCustomData(stream);
1434 void KPixmapCache::Private::writeIndex(
const QString& key,
int dataoffset)
1441 QDataStream stream(device);
1443 writeIndexEntry(stream, key, dataoffset);
1449 QFileInfo fi(filename);
1452 }
else if (fi.lastModified().toTime_t() >
timestamp()) {
1458 QString key(
"file:" + filename);
1459 if (!
find(key, pix)) {
1461 pix = QPixmap(filename);
1475 QFileInfo fi(filename);
1478 }
else if (fi.lastModified().toTime_t() >
timestamp()) {
1484 QString key =
QString(
"file:%1_%2_%3").arg(filename).arg(size.width()).arg(size.height());
1485 if (!
find(key, pix)) {
1488 if (!svg.load(filename)) {
1491 QSize pixSize = size.isValid() ? size : svg.defaultSize();
1492 pix = QPixmap(pixSize);
1493 pix.fill(Qt::transparent);
1496 svg.render(&p, QRectF(QPointF(), pixSize));