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; }
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) {
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();
248 static unsigned kpcNumber;
250 int findOffset(
const QString& key);
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);
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;
358 unsigned KPixmapCache::Private::kpcNumber = 0;
363 mCaches.append(
this);
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) {
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);
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) {
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);
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)
595 qint32 leftchild, rightchild;
596 stream >> fkey >> foffset >> timesused >> lastused >> leftchild >> rightchild;
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);
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);
655 stream << timesused << lastused;
666 bool KPixmapCache::Private::checkLockFile()
671 kError() <<
"Couldn't remove lockfile" << mLockFileName;
678 bool KPixmapCache::Private::checkFileVersion(
const QString& 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;
761 if (!q->loadCustomIndexHeader(stream)) {
765 mHeaderSize = file.
pos();
766 mIndexRootOffset = file.
pos();
780 return mThisString + key;
783 void KPixmapCache::Private::writeIndexEntry(
QDataStream& stream,
const QString& key,
int dataoffset)
788 int parentoffset = binarySearchKey(stream, key, mIndexRootOffset);
789 if (parentoffset != stream.
device()->
size()) {
796 if (key == fkey || fkey.
isEmpty()) {
798 offset = parentoffset;
804 stream << key << (
qint32)dataoffset;
813 if (parentoffset != offset) {
814 stream.device()->seek(parentoffset);
818 stream >> fkey >> foffset >> timesused >> lastused;
830 bool KPixmapCache::Private::removeEntries(
int newsize)
832 KPCLockFile lock(mLockFileName);
833 if (!lock.isValid()) {
834 kDebug(264) <<
"Couldn't lock cache" << mName;
840 QFile indexfile(mIndexFile);
841 if (!indexfile.open(QIODevice::ReadOnly)) {
842 kDebug(264) <<
"Couldn't open old index file";
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 <<
")";
857 QFile newindexfile(mIndexFile +
".new");
858 if (!newindexfile.open(QIODevice::ReadWrite)) {
859 kDebug(264) <<
"Couldn't open new index file";
863 QFile newdatafile(mDataFile +
".new");
864 if (!newdatafile.open(QIODevice::WriteOnly)) {
865 kDebug(264) <<
"Couldn't open new data file";
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);
902 open.
enqueue(mIndexRootOffset);
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));
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;
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;
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);
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);
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;
1328 qint32 format, w, h, bpl;
1329 stream >> format >> w >> h >> bpl;
1331 stream >> imgdatacompressed;
1337 QByteArray imgdata = qUncompress(imgdatacompressed);
1339 QImage img((
const uchar*)imgdata.
constData(), w, h, bpl, (QImage::Format)format);
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);
1421 stream << imgdatacompressed;
1423 q->writeCustomData(stream);
1434 void KPixmapCache::Private::writeIndex(
const QString& key,
int dataoffset)
1443 writeIndexEntry(stream, key, dataoffset);
1458 QString key(
"file:" + filename);
1459 if (!
find(key, pix)) {
1485 if (!
find(key, pix)) {
1488 if (!svg.
load(filename)) {
1493 pix.
fill(Qt::transparent);
void setValid(bool valid)
Sets whether this cache is valid or not.
virtual void insert(const QString &key, const QPixmap &pix)
Inserts the pixmap pix into the cache, associated with the key key.
void fill(const QColor &color)
void render(QPainter *painter)
virtual bool seek(qint64 pos)
virtual qint64 pos() const
virtual bool loadCustomData(QDataStream &stream)
Can be used by subclasses to load custom data from the stream.
virtual bool open(OpenMode flags=QIODevice::ReadWrite)
bool useQPixmapCache() const
Whether QPixmapCache should be used to cache pixmaps in memory in addition to caching them on the dis...
const char * name(StandardAction id)
This will return the internal name of a given standard action.
void ensureInited() const
Makes sure that the cache is initialized correctly, including the loading of the cache index and data...
unsigned int timestamp() const
static QDebug kError(bool cond, int area=KDE_DEFAULT_DEBUG_AREA)
QPixmap fromImage(const QImage &image, QFlags< Qt::ImageConversionFlag > flags)
General-purpose pixmap cache for KDE.
least recently used entries are removed first.
QPixmap loadFromFile(const QString &filename)
Loads a pixmap from given file, using the cache.
virtual qint64 readData(char *data, qint64 maxSize)=0
static QDebug kDebug(bool cond, int area=KDE_DEFAULT_DEBUG_AREA)
QSize defaultSize() const
virtual qint64 pos() const
bool recreateCacheFiles()
This function causes the cache files to be recreate by invalidating the cache.
int open(const QString &pathname, int flags, mode_t mode)
virtual void writeCustomIndexHeader(QDataStream &stream)
Can be used by subclasses to write custom data into cache's header.
KPixmapCache(const QString &name)
Constucts the pixmap cache object.
virtual bool find(const QString &key, QPixmap &pix)
Tries to load pixmap with the specified key from cache.
int count(const T &value) const
void append(const T &value)
bool load(const QString &filename)
virtual qint64 size() const
virtual bool loadCustomIndexHeader(QDataStream &stream)
Can be used by subclasses to load custom data from cache's header.
void setUseQPixmapCache(bool use)
Sets whether QPixmapCache (memory caching) should be used in addition to disk cache.
RemoveStrategy removeEntryStrategy() const
const char * constData() const
qint64 read(char *data, qint64 maxSize)
QDateTime lastModified() const
static void deleteCache(const QString &name)
Deletes a pixmap cache.
virtual bool open(QFlags< QIODevice::OpenModeFlag > mode)
void setTimestamp(unsigned int time)
Sets the timestamp of app-specific cache.
virtual qint64 size() const
#define KPIXMAPCACHE_VERSION
void removeEntries(int newsize=0)
Removes some of the entries in the cache according to current removeEntryStrategy().
void setRemoveEntryStrategy(RemoveStrategy strategy)
Sets the removeEntryStrategy used when removing entries.
void discard()
Deletes all entries and reinitializes this cache.
QByteArray toLatin1() const
virtual bool writeCustomData(QDataStream &stream)
Can be used by subclasses to write custom data into the stream.
static QString locateLocal(const char *type, const QString &filename, const KComponentData &cData=KGlobal::mainComponent())
virtual qint64 writeData(const char *data, qint64 maxSize)=0
QPixmap * find(const QString &key)
static QDebug kWarning(bool cond, int area=KDE_DEFAULT_DEBUG_AREA)
int madvise(caddr_t addr, size_t len, int advice)
qint64 write(const char *data, qint64 maxSize)
void setCacheLimit(int kbytes)
Sets the maximum size of the cache (in kilobytes).
QIODevice * device() const
bool insert(const QString &key, const QPixmap &pixmap)
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
bool isEnabled() const
Cache will be disabled when e.g.
QPixmap loadFromSvg(const QString &filename, const QSize &size=QSize())
Same as loadFromFile(), but using an SVG file instead.
RemoveStrategy
Describes which entries will be removed first during cache cleanup.