You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
402 lines
10 KiB
402 lines
10 KiB
/*
|
|
Gwenview - A simple image viewer for TDE
|
|
Copyright 2000-2004 Aurélien Gâteau
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
#include "cache.h"
|
|
|
|
// TQt
|
|
|
|
// KDE
|
|
#include <tdeconfig.h>
|
|
#include <kdebug.h>
|
|
#include <tdeversion.h>
|
|
#include <ksharedptr.h>
|
|
#include <kstaticdeleter.h>
|
|
#include <tdeio/global.h>
|
|
|
|
#include "cache.moc"
|
|
|
|
namespace Gwenview {
|
|
|
|
// Local
|
|
|
|
#undef ENABLE_LOG
|
|
#undef LOG
|
|
//#define ENABLE_LOG
|
|
#ifdef ENABLE_LOG
|
|
#define LOG(x) kdDebug() << k_funcinfo << x << endl
|
|
#else
|
|
#define LOG(x) ;
|
|
#endif
|
|
|
|
//#define DEBUG_CACHE
|
|
|
|
const char CONFIG_CACHE_MAXSIZE[]="maxSize";
|
|
|
|
|
|
struct ImageData : public TDEShared {
|
|
ImageData( const KURL& url, const TQDateTime& _timestamp )
|
|
: timestamp(_timestamp)
|
|
, age(0)
|
|
, fast_url( url.isLocalFile() && !TDEIO::probably_slow_mounted( url.path()))
|
|
, priority( false ) {
|
|
}
|
|
|
|
void addFile( const TQByteArray& file );
|
|
void addImage( const ImageFrames& frames, const TQCString& format );
|
|
void addThumbnail( const TQPixmap& thumbnail, TQSize imagesize );
|
|
long long cost() const;
|
|
int size() const;
|
|
TQByteArray file;
|
|
ImageFrames frames;
|
|
TQPixmap thumbnail;
|
|
TQSize imagesize;
|
|
TQCString format;
|
|
TQDateTime timestamp;
|
|
mutable int age;
|
|
bool fast_url;
|
|
bool priority;
|
|
int fileSize() const;
|
|
int imageSize() const;
|
|
int thumbnailSize() const;
|
|
bool reduceSize();
|
|
bool isEmpty() const;
|
|
|
|
typedef TDESharedPtr<ImageData> Ptr;
|
|
};
|
|
|
|
typedef TQMap<KURL, ImageData::Ptr> ImageMap;
|
|
|
|
struct Cache::Private {
|
|
ImageMap mImages;
|
|
int mMaxSize;
|
|
int mThumbnailSize;
|
|
TQValueList< KURL > mPriorityURLs;
|
|
|
|
|
|
/**
|
|
* This function tries to returns a valid ImageData for url and timestamp.
|
|
* If it can't find one, it will create a new one and return it.
|
|
*/
|
|
ImageData::Ptr getOrCreateImageData(const KURL& url, const TQDateTime& timestamp) {
|
|
if (mImages.contains(url)) {
|
|
ImageData::Ptr data = mImages[url];
|
|
if (data->timestamp == timestamp) return data;
|
|
}
|
|
|
|
ImageData::Ptr data = new ImageData(url, timestamp);
|
|
mImages[url] = data;
|
|
if (mPriorityURLs.contains(url)) data->priority = true;
|
|
return data;
|
|
}
|
|
};
|
|
|
|
|
|
Cache::Cache()
|
|
{
|
|
d = new Private;
|
|
d->mMaxSize = DEFAULT_MAXSIZE;
|
|
// don't remember size for every thumbnail, but have one global and dump all if needed
|
|
d->mThumbnailSize = 0;
|
|
}
|
|
|
|
|
|
Cache::~Cache() {
|
|
d->mImages.clear();
|
|
delete d;
|
|
}
|
|
|
|
|
|
static Cache* sCache;
|
|
static KStaticDeleter<Cache> sCacheDeleter;
|
|
|
|
|
|
Cache* Cache::instance() {
|
|
if (!sCache) {
|
|
sCacheDeleter.setObject(sCache, new Cache());
|
|
}
|
|
return sCache;
|
|
}
|
|
|
|
// Priority URLs are used e.g. when prefetching for the slideshow - after an image is prefetched,
|
|
// the loader tries to put the image in the cache. When the slideshow advances, the next loader
|
|
// just gets the image from the cache. However, the prefetching may be useless if the image
|
|
// actually doesn't stay long enough in the cache, e.g. because of being too big for the cache.
|
|
// Marking an URL as a priority one will make sure it stays in the cache and that the cache
|
|
// will be even enlarged as necessary if needed.
|
|
void Cache::setPriorityURL( const KURL& url, bool set ) {
|
|
if( set ) {
|
|
d->mPriorityURLs.append( url );
|
|
if( d->mImages.contains( url )) {
|
|
d->mImages[ url ]->priority = true;
|
|
}
|
|
} else {
|
|
d->mPriorityURLs.remove( url );
|
|
if( d->mImages.contains( url )) {
|
|
d->mImages[ url ]->priority = false;
|
|
}
|
|
checkMaxSize();
|
|
}
|
|
}
|
|
|
|
|
|
void Cache::addFile( const KURL& url, const TQByteArray& file, const TQDateTime& timestamp ) {
|
|
LOG(url.prettyURL());
|
|
updateAge();
|
|
d->getOrCreateImageData(url, timestamp)->addFile(file);
|
|
checkMaxSize();
|
|
}
|
|
|
|
void Cache::addImage( const KURL& url, const ImageFrames& frames, const TQCString& format, const TQDateTime& timestamp ) {
|
|
LOG(url.prettyURL());
|
|
updateAge();
|
|
d->getOrCreateImageData(url, timestamp)->addImage(frames, format);
|
|
checkMaxSize();
|
|
}
|
|
|
|
void Cache::addThumbnail( const KURL& url, const TQPixmap& thumbnail, TQSize imagesize, const TQDateTime& timestamp ) {
|
|
// Thumbnails are many and often - things would age too quickly. Therefore
|
|
// when adding thumbnails updateAge() is called from the outside only once for all of them.
|
|
// updateAge();
|
|
d->getOrCreateImageData(url, timestamp)->addThumbnail(thumbnail, imagesize);
|
|
checkMaxSize();
|
|
}
|
|
|
|
void Cache::invalidate( const KURL& url ) {
|
|
d->mImages.remove( url );
|
|
}
|
|
|
|
TQDateTime Cache::timestamp( const KURL& url ) const {
|
|
LOG(url.prettyURL());
|
|
if( d->mImages.contains( url )) return d->mImages[ url ]->timestamp;
|
|
return TQDateTime();
|
|
}
|
|
|
|
TQByteArray Cache::file( const KURL& url ) const {
|
|
LOG(url.prettyURL());
|
|
if( d->mImages.contains( url )) {
|
|
const ImageData::Ptr data = d->mImages[ url ];
|
|
if( data->file.isNull()) return TQByteArray();
|
|
data->age = 0;
|
|
return data->file;
|
|
}
|
|
return TQByteArray();
|
|
}
|
|
|
|
void Cache::getFrames( const KURL& url, ImageFrames* frames, TQCString* format ) const {
|
|
LOG(url.prettyURL());
|
|
Q_ASSERT(frames);
|
|
Q_ASSERT(format);
|
|
frames->clear();
|
|
*format = TQCString();
|
|
if( d->mImages.contains( url )) {
|
|
const ImageData::Ptr data = d->mImages[ url ];
|
|
if( data->frames.isEmpty()) return;
|
|
*frames = data->frames;
|
|
*format = data->format;
|
|
data->age = 0;
|
|
}
|
|
}
|
|
|
|
TQPixmap Cache::thumbnail( const KURL& url, TQSize& imagesize ) const {
|
|
if( d->mImages.contains( url )) {
|
|
const ImageData::Ptr data = d->mImages[ url ];
|
|
if( data->thumbnail.isNull()) return TQPixmap();
|
|
imagesize = data->imagesize;
|
|
// data.age = 0;
|
|
return data->thumbnail;
|
|
}
|
|
return TQPixmap();
|
|
}
|
|
|
|
void Cache::updateAge() {
|
|
for( ImageMap::Iterator it = d->mImages.begin();
|
|
it != d->mImages.end();
|
|
++it ) {
|
|
(*it)->age++;
|
|
}
|
|
}
|
|
|
|
void Cache::checkThumbnailSize( int size ) {
|
|
if( size != d->mThumbnailSize ) {
|
|
// simply remove all thumbnails, should happen rarely
|
|
for( ImageMap::Iterator it = d->mImages.begin();
|
|
it != d->mImages.end();
|
|
) {
|
|
if( !(*it)->thumbnail.isNull()) {
|
|
ImageMap::Iterator it2 = it;
|
|
++it;
|
|
d->mImages.remove( it2 );
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
d->mThumbnailSize = size;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_CACHE
|
|
static KURL _cache_url; // hack only for debugging for item to show also its key
|
|
#endif
|
|
|
|
void Cache::checkMaxSize() {
|
|
for(;;) {
|
|
int size = 0;
|
|
ImageMap::Iterator max;
|
|
long long max_cost = -1;
|
|
#ifdef DEBUG_CACHE
|
|
int with_file = 0;
|
|
int with_thumb = 0;
|
|
int with_image = 0;
|
|
#endif
|
|
for( ImageMap::Iterator it = d->mImages.begin();
|
|
it != d->mImages.end();
|
|
++it ) {
|
|
size += (*it)->size();
|
|
#ifdef DEBUG_CACHE
|
|
if( !(*it).file.isNull()) ++with_file;
|
|
if( !(*it).thumbnail.isNull()) ++with_thumb;
|
|
if( !(*it).frames.isEmpty()) ++with_image;
|
|
#endif
|
|
long long cost = (*it)->cost();
|
|
if( cost > max_cost && ! (*it)->priority ) {
|
|
max_cost = cost;
|
|
max = it;
|
|
}
|
|
}
|
|
if( size <= d->mMaxSize || max_cost == -1 ) {
|
|
#if 0
|
|
#ifdef DEBUG_CACHE
|
|
kdDebug() << "Cache: Statistics (" << d->mImages.size() << "/" << with_file << "/"
|
|
<< with_thumb << "/" << with_image << ")" << endl;
|
|
#endif
|
|
#endif
|
|
break;
|
|
}
|
|
#ifdef DEBUG_CACHE
|
|
_cache_url = max.key();
|
|
#endif
|
|
|
|
if( !(*max)->reduceSize() || (*max)->isEmpty()) d->mImages.remove( max );
|
|
}
|
|
}
|
|
|
|
void Cache::readConfig(TDEConfig* config,const TQString& group) {
|
|
TDEConfigGroupSaver saver( config, group );
|
|
d->mMaxSize = config->readNumEntry( CONFIG_CACHE_MAXSIZE, d->mMaxSize );
|
|
checkMaxSize();
|
|
}
|
|
|
|
|
|
void ImageData::addFile( const TQByteArray& f ) {
|
|
file = f;
|
|
file.detach(); // explicit sharing
|
|
age = 0;
|
|
}
|
|
|
|
void ImageData::addImage( const ImageFrames& fs, const TQCString& f ) {
|
|
frames = fs;
|
|
format = f;
|
|
age = 0;
|
|
}
|
|
|
|
void ImageData::addThumbnail( const TQPixmap& thumb, TQSize imgsize ) {
|
|
thumbnail = thumb;
|
|
imagesize = imgsize;
|
|
// age = 0;
|
|
}
|
|
|
|
int ImageData::size() const {
|
|
return TQMAX( fileSize() + imageSize() + thumbnailSize(), 100 ); // some minimal size per item
|
|
}
|
|
|
|
int ImageData::fileSize() const {
|
|
return !file.isNull() ? file.size() : 0;
|
|
}
|
|
|
|
int ImageData::thumbnailSize() const {
|
|
return !thumbnail.isNull() ? thumbnail.height() * thumbnail.width() * thumbnail.depth() / 8 : 0;
|
|
}
|
|
|
|
int ImageData::imageSize() const {
|
|
int ret = 0;
|
|
for( ImageFrames::ConstIterator it = frames.begin(); it != frames.end(); ++it ) {
|
|
ret += (*it).image.height() * (*it).image.width() * (*it).image.depth() / 8;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool ImageData::reduceSize() {
|
|
if( !file.isNull() && fast_url && !frames.isEmpty()) {
|
|
file = TQByteArray();
|
|
#ifdef DEBUG_CACHE
|
|
kdDebug() << "Cache: Dumping fast file: " << _cache_url.prettyURL() << ":" << cost() << endl;
|
|
#endif
|
|
return true;
|
|
}
|
|
if( !thumbnail.isNull()) {
|
|
#ifdef DEBUG_CACHE
|
|
kdDebug() << "Cache: Dumping thumbnail: " << _cache_url.prettyURL() << ":" << cost() << endl;
|
|
#endif
|
|
thumbnail = TQPixmap();
|
|
return true;
|
|
}
|
|
if( !file.isNull() && !frames.isEmpty()) {
|
|
// possibly slow file to fetch - dump the image data unless the image
|
|
// is JPEG (which needs raw data anyway) or the raw data much larger than the image
|
|
if( format == "JPEG" || fileSize() < imageSize() / 10 ) {
|
|
frames.clear();
|
|
#ifdef DEBUG_CACHE
|
|
kdDebug() << "Cache: Dumping images: " << _cache_url.prettyURL() << ":" << cost() << endl;
|
|
#endif
|
|
} else {
|
|
file = TQByteArray();
|
|
#ifdef DEBUG_CACHE
|
|
kdDebug() << "Cache: Dumping file: " << _cache_url.prettyURL() << ":" << cost() << endl;
|
|
#endif
|
|
}
|
|
return true;
|
|
}
|
|
#ifdef DEBUG_CACHE
|
|
kdDebug() << "Cache: Dumping completely: " << _cache_url.prettyURL() << ":" << cost() << endl;
|
|
#endif
|
|
return false; // reducing here would mean clearing everything
|
|
}
|
|
|
|
bool ImageData::isEmpty() const {
|
|
return file.isNull() && frames.isEmpty() && thumbnail.isNull();
|
|
}
|
|
|
|
long long ImageData::cost() const {
|
|
long long s = size();
|
|
if( fast_url && !file.isNull()) {
|
|
s *= ( format == "JPEG" ? 10 : 100 ); // heavy penalty for storing local files
|
|
} else if( !thumbnail.isNull()) {
|
|
s *= 10 * 10; // thumbnails are small, and try to get rid of them soon
|
|
}
|
|
static const int mod[] = { 50, 30, 20, 16, 12, 10 };
|
|
if( age <= 5 ) {
|
|
return s * 10 / mod[ age ];
|
|
} else {
|
|
return s * ( age - 5 );
|
|
}
|
|
}
|
|
|
|
} // namespace
|