File indexing completed on 2024-05-12 05:09:50
0001 /*************************************************************************** 0002 Copyright (C) 2003-2009 Robby Stephenson <robby@periapsis.org> 0003 ***************************************************************************/ 0004 0005 /*************************************************************************** 0006 * * 0007 * This program is free software; you can redistribute it and/or * 0008 * modify it under the terms of the GNU General Public License as * 0009 * published by the Free Software Foundation; either version 2 of * 0010 * the License or (at your option) version 3 or any later version * 0011 * accepted by the membership of KDE e.V. (or its successor approved * 0012 * by the membership of KDE e.V.), which shall act as a proxy * 0013 * defined in Section 14 of version 3 of the license. * 0014 * * 0015 * This program is distributed in the hope that it will be useful, * 0016 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0017 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0018 * GNU General Public License for more details. * 0019 * * 0020 * You should have received a copy of the GNU General Public License * 0021 * along with this program. If not, see <http://www.gnu.org/licenses/>. * 0022 * * 0023 ***************************************************************************/ 0024 0025 #include "imagefactory.h" 0026 #include "image.h" 0027 #include "imageinfo.h" 0028 #include "imagedirectory.h" 0029 #include "imagejob.h" 0030 #include "../config/tellico_config.h" 0031 #include "../utils/tellico_utils.h" 0032 #include "../utils/gradient.h" 0033 #include "../tellico_debug.h" 0034 0035 #include <KColorUtils> 0036 #include <KZip> 0037 0038 #include <QCache> 0039 #include <QFileInfo> 0040 #include <QDir> 0041 0042 #define RELEASE_IMAGES 0043 0044 using namespace Tellico; 0045 using Tellico::ImageFactory; 0046 0047 // this image info map is primarily for big images that don't fit 0048 // in the cache, so that don't have to be continually reloaded to get info 0049 QHash<QString, Tellico::Data::ImageInfo> ImageFactory::s_imageInfoMap; 0050 Tellico::StringSet ImageFactory::s_imagesToRelease; 0051 0052 Tellico::ImageFactory* ImageFactory::factory = nullptr; 0053 0054 class ImageFactory::Private { 0055 public: 0056 Private() = default; 0057 0058 QHash<QString, Data::Image*> imageDict; 0059 QCache<QString, Data::Image> imageCache; 0060 QCache<QString, QPixmap> pixmapCache; 0061 ImageDirectory dataImageDir; // kept in $HOME/.local/share/tellico/data/ 0062 ImageDirectory localImageDir; // kept local to data file 0063 TemporaryImageDirectory tempImageDir; // kept in tmp directory 0064 ImageZipArchive imageZipArchive; 0065 StringSet nullImages; 0066 }; 0067 0068 ImageFactory::ImageFactory() : QObject(), d(new Private()) { 0069 } 0070 0071 ImageFactory::~ImageFactory() { 0072 delete d; 0073 } 0074 0075 void ImageFactory::init() { 0076 if(factory) { 0077 return; 0078 } 0079 factory = new ImageFactory(); 0080 factory->d->imageCache.setMaxCost(Config::imageCacheSize()); 0081 factory->d->pixmapCache.setMaxCost(Config::imageCacheSize()); 0082 factory->d->dataImageDir.setPath(Tellico::saveLocation(QStringLiteral("data/"))); 0083 } 0084 0085 Tellico::ImageFactory* ImageFactory::self() { 0086 Q_ASSERT(factory && "ImageFactory is not initialized!"); 0087 return factory; 0088 } 0089 0090 QString ImageFactory::tempDir() { 0091 return factory->d->tempImageDir.path(); 0092 } 0093 0094 QString ImageFactory::dataDir() { 0095 return factory->d->dataImageDir.path(); 0096 } 0097 0098 QString ImageFactory::localDir() { 0099 const QString dir = factory->d->localImageDir.path(); 0100 return dir.isEmpty() ? dataDir() : dir; 0101 } 0102 0103 QString ImageFactory::imageDir() { 0104 Q_ASSERT(factory); 0105 switch(cacheDir()) { 0106 case LocalDir: return localDir(); 0107 case DataDir: return dataDir(); 0108 case ZipArchive: return tempDir(); 0109 case TempDir: return tempDir(); 0110 } 0111 return tempDir(); 0112 } 0113 0114 Tellico::ImageFactory::CacheDir ImageFactory::cacheDir() { 0115 CacheDir dir = TempDir; 0116 switch(Config::imageLocation()) { 0117 case Config::ImagesInLocalDir: dir = LocalDir; break; 0118 case Config::ImagesInAppDir: dir = DataDir; break; 0119 case Config::ImagesInFile: dir = TempDir; break; 0120 } 0121 // special case when configured to use local dir but for a new collection when no local dir exists 0122 if(dir == LocalDir && factory->d->localImageDir.path().isEmpty()) { 0123 dir = TempDir; 0124 } 0125 return dir; 0126 } 0127 0128 QString ImageFactory::addImage(const QUrl& url_, bool quiet_, const QUrl& refer_, bool link_) { 0129 Q_ASSERT(factory && "ImageFactory is not initialized!"); 0130 return factory->addImageImpl(url_, quiet_, refer_, link_).id(); 0131 } 0132 0133 const Tellico::Data::Image& ImageFactory::addImageImpl(const QUrl& url_, bool quiet_, const QUrl& refer_, bool link_) { 0134 if(url_.isEmpty() || !url_.isValid() || d->nullImages.contains(url_.url())) { 0135 myDebug() << "Returning null image"; 0136 return Data::Image::null; 0137 } 0138 ImageJob* job = new ImageJob(url_, QString(), quiet_); 0139 job->setLinkOnly(link_); 0140 job->setReferrer(refer_); 0141 0142 if(!job->exec()) { 0143 myDebug() << "ImageJob failed to exec:" << job->errorString(); 0144 // ERR_UNKNOWN is used when the returned image is truly null 0145 // rather than network error or some such 0146 if(job->error() == KIO::ERR_UNKNOWN) { 0147 d->nullImages.add(url_.url()); 0148 } 0149 return Data::Image::null; 0150 } 0151 0152 const Data::Image& img = job->image(); 0153 if(img.isNull()) { 0154 myDebug() << "Null image for" << url_.toDisplayString(); 0155 return Data::Image::null; 0156 } 0157 0158 // hold the image in memory since it probably isn't written locally to disk yet 0159 if(!d->imageDict.contains(img.id())) { 0160 d->imageDict.insert(img.id(), new Data::Image(img)); 0161 s_imageInfoMap.insert(img.id(), Data::ImageInfo(img)); 0162 } 0163 return img; 0164 } 0165 0166 QString ImageFactory::addImage(const QImage& image_, const QString& format_) { 0167 Q_ASSERT(factory && "ImageFactory is not initialized!"); 0168 return factory->addImageImpl(image_, format_).id(); 0169 } 0170 0171 QString ImageFactory::addImage(const QPixmap& pix_, const QString& format_) { 0172 Q_ASSERT(factory && "ImageFactory is not initialized!"); 0173 return factory->addImageImpl(pix_.toImage(), format_).id(); 0174 } 0175 0176 const Tellico::Data::Image& ImageFactory::addImageImpl(const QImage& image_, const QString& format_) { 0177 Data::Image* img = new Data::Image(image_, format_); 0178 if(hasImageInMemory(img->id())) { 0179 const Data::Image& img2 = imageById(img->id()); 0180 if(!img2.isNull()) { 0181 delete img; 0182 return img2; 0183 } 0184 } 0185 if(img->isNull()) { 0186 delete img; 0187 return Data::Image::null; 0188 } 0189 d->imageDict.insert(img->id(), img); 0190 s_imageInfoMap.insert(img->id(), Data::ImageInfo(*img)); 0191 return *img; 0192 } 0193 0194 QString ImageFactory::addImage(const QByteArray& data_, const QString& format_, const QString& id_) { 0195 Q_ASSERT(factory && "ImageFactory is not initialized!"); 0196 return factory->addImageImpl(data_, format_, id_).id(); 0197 } 0198 0199 const Tellico::Data::Image& ImageFactory::addImageImpl(const QByteArray& data_, const QString& format_, 0200 const QString& id_) { 0201 Data::Image* img; 0202 if(!id_.isEmpty()) { 0203 // do not call imageById(), it causes infinite looping with Document::loadImage() 0204 img = d->imageCache.object(id_); 0205 if(img) { 0206 myLog() << "already exists in cache: " << id_; 0207 return *img; 0208 } 0209 img = d->imageDict.value(id_); 0210 if(img) { 0211 myLog() << "already exists in dict: " << id_; 0212 return *img; 0213 } 0214 } 0215 img = new Data::Image(data_, format_, id_); 0216 if(img->isNull()) { 0217 myDebug() << "NULL IMAGE!!!!!"; 0218 delete img; 0219 return Data::Image::null; 0220 } 0221 0222 // myLog() << "format = " << format_ << ", id = "<< img->id(); 0223 0224 d->imageDict.insert(img->id(), img); 0225 s_imageInfoMap.insert(img->id(), Data::ImageInfo(*img)); 0226 return *img; 0227 } 0228 0229 const Tellico::Data::Image& ImageFactory::addCachedImageImpl(const QString& id_, CacheDir dir_) { 0230 // myLog() << "dir =" << (dir_ == DataDir ? "DataDir" : "TmpDir" ) << "; id =" << id_; 0231 Data::Image* img = nullptr; 0232 switch(dir_) { 0233 case DataDir: 0234 img = d->dataImageDir.imageById(id_); 0235 break; 0236 case LocalDir: 0237 img = d->localImageDir.imageById(id_); 0238 break; 0239 case TempDir: 0240 img = d->tempImageDir.imageById(id_); 0241 break; 0242 case ZipArchive: 0243 img = d->imageZipArchive.imageById(id_); 0244 break; 0245 } 0246 if(!img) { 0247 myWarning() << "image not found:" << id_; 0248 return Data::Image::null; 0249 } 0250 0251 s_imageInfoMap.insert(img->id(), Data::ImageInfo(*img)); 0252 0253 // if byteCount() is greater than maxCost, then trying and failing to insert it would 0254 // mean the image gets deleted 0255 if(img->sizeInBytes() > d->imageCache.maxCost()) { 0256 // can't hold it in the cache 0257 myWarning() << "Image cache is unable to hold the image, it's too big!"; 0258 myWarning() << "Image name is " << img->id(); 0259 myWarning() << "Image size is " << img->sizeInBytes(); 0260 myWarning() << "Max cache size is " << d->imageCache.maxCost(); 0261 0262 // add it back to the dict, but add the image to the list of 0263 // images to release later. Necessary to avoid a memory leak since new Image() 0264 // was called, we need to keep the pointer 0265 d->imageDict.insert(img->id(), img); 0266 s_imagesToRelease.add(img->id()); 0267 } else if(!d->imageCache.insert(img->id(), img, img->sizeInBytes())) { 0268 // at this point, img has been deleted! 0269 myWarning() << "Unable to insert into image cache"; 0270 return Data::Image::null; 0271 } 0272 return *img; 0273 } 0274 0275 bool ImageFactory::writeCachedImage(const QString& id_, CacheDir dir_, bool force_ /*=false*/) { 0276 if(id_.isEmpty()) { 0277 return false; 0278 } 0279 // myLog() << "dir =" << (dir_ == DataDir ? "DataDir" : "TmpDir" ) << "; id =" << id_; 0280 ImageDirectory* imgDir = nullptr; 0281 switch(dir_) { 0282 case DataDir: 0283 imgDir = &factory->d->dataImageDir; 0284 break; 0285 case TempDir: 0286 imgDir = &factory->d->tempImageDir; 0287 break; 0288 case LocalDir: 0289 // special case when configured to use local dir but for a new collection when no local dir exists 0290 imgDir = factory->d->localImageDir.path().isEmpty() ? 0291 &factory->d->tempImageDir : 0292 &factory->d->localImageDir; 0293 break; 0294 case ZipArchive: 0295 myDebug() << "writeCachedImage() - ZipArchive - should never be called"; 0296 imgDir = &factory->d->tempImageDir; 0297 break; 0298 } 0299 0300 Q_ASSERT(imgDir); 0301 bool success = writeCachedImage(id_, imgDir, force_); 0302 0303 if(success) { 0304 // remove from dict and add to cache 0305 // it might not be in dict though 0306 if(factory->d->imageDict.contains(id_)) { 0307 Data::Image* img = factory->d->imageDict.take(id_); 0308 Q_ASSERT(img); 0309 // imageCache.insert will delete the image by itself if the cost exceeds the cache size 0310 if(factory->d->imageCache.insert(img->id(), img, img->sizeInBytes())) { 0311 s_imageInfoMap.remove(id_); 0312 } 0313 } 0314 } 0315 return success; 0316 } 0317 0318 bool ImageFactory::writeCachedImage(const QString& id_, ImageDirectory* imgDir_, bool force_ /*=false*/) { 0319 if(id_.isEmpty() || !imgDir_) { 0320 return false; 0321 } 0322 // myLog() << "ImageFactory::writeCachedImage() - dir =" << imgDir_->path() << "; id =" << id_; 0323 const bool exists = imgDir_->hasImage(id_); 0324 // only write if it doesn't exist 0325 bool success = (!force_ && exists); 0326 if(!success) { 0327 const Data::Image& img = imageById(id_); 0328 if(!img.isNull()) { 0329 success = imgDir_->writeImage(img); 0330 } 0331 } 0332 return success; 0333 } 0334 0335 const Tellico::Data::Image& ImageFactory::imageById(const QString& id_) { 0336 Q_ASSERT(factory && "ImageFactory is not initialized!"); 0337 if(id_.isEmpty() || !factory || factory->d->nullImages.contains(id_)) { 0338 return Data::Image::null; 0339 } 0340 // myLog() << "imageById" << id_; 0341 0342 // can't think of a better place to regularly check for images to release 0343 // but don't release image that just got asked for 0344 s_imagesToRelease.remove(id_); 0345 factory->releaseImages(); 0346 0347 // first check the cache, used for images that are in the data file, or are only temporary 0348 // then the dict, used for images downloaded, but not yet saved anywhere 0349 Data::Image* img = factory->d->imageCache.object(id_); 0350 if(img) { 0351 // myLog() << "found in cache"; 0352 return *img; 0353 } 0354 0355 img = factory->d->imageDict.value(id_); 0356 if(img) { 0357 // myLog() << "found in dict"; 0358 return *img; 0359 } 0360 0361 // if the image is link only, we need to load it 0362 // but can't call imageInfo() since that might recurse into imageById() 0363 // also, the image info cache might not have it so check if the 0364 // id is a valid absolute url 0365 // yeah, it's probably slow 0366 if((s_imageInfoMap.contains(id_) && s_imageInfoMap[id_].linkOnly) || !QUrl(id_).isRelative()) { 0367 QUrl u(id_); 0368 if(u.isValid()) { 0369 return factory->addImageImpl(u, true, QUrl(), true); 0370 } 0371 } 0372 0373 // the document does a delayed loading of the images, sometimes 0374 // so an image could be in the tmp dir and not be in the cache 0375 // or it could be too big for the cache 0376 if(factory->d->tempImageDir.hasImage(id_)) { 0377 const Data::Image& img2 = factory->addCachedImageImpl(id_, TempDir); 0378 if(!img2.isNull()) { 0379 // myLog() << "found in tmp dir"; 0380 return img2; 0381 } 0382 } 0383 0384 // try to do a delayed loading of the image 0385 if(factory->d->imageZipArchive.hasImage(id_)) { 0386 const Data::Image& img2 = factory->addCachedImageImpl(id_, ZipArchive); 0387 if(!img2.isNull()) { 0388 // myLog() << "found in zip archive"; 0389 // go ahead and write image to disk so we don't have to keep it in memory 0390 // calling pixmap() could be loading all the covers, and we don't want one 0391 // to get pushed out of the cache yet 0392 writeCachedImage(id_, TempDir); 0393 return img2; 0394 } 0395 } 0396 0397 // This section uses the config image setting plus the fallback location 0398 // to provide confidence that the user's image can be found 0399 CacheDir configLoc = TempDir; 0400 CacheDir fallbackLoc = TempDir; 0401 ImageDirectory* configImgDir = nullptr; 0402 ImageDirectory* fallbackImgDir = nullptr; 0403 if(Config::imageLocation() == Config::ImagesInLocalDir) { 0404 configLoc = LocalDir; 0405 fallbackLoc = DataDir; 0406 configImgDir = &factory->d->localImageDir; 0407 fallbackImgDir = &factory->d->dataImageDir; 0408 } else if(Config::imageLocation() == Config::ImagesInAppDir) { 0409 configLoc = DataDir; 0410 fallbackLoc = LocalDir; 0411 configImgDir = &factory->d->dataImageDir; 0412 fallbackImgDir = &factory->d->localImageDir; 0413 } 0414 0415 // check the configured location first 0416 if(configImgDir && configImgDir->hasImage(id_)) { 0417 const Data::Image& img2 = factory->addCachedImageImpl(id_, configLoc); 0418 if(!img2.isNull()) { 0419 // myLog() << "found image in configured location" << configImgDir->path(); 0420 return img2; 0421 } else { 0422 myDebug() << "tried to add" << id_ << "from" << configImgDir->path() << "but failed"; 0423 } 0424 } else if(fallbackImgDir && fallbackImgDir->hasImage(id_)) { 0425 const Data::Image& img2 = factory->addCachedImageImpl(id_, fallbackLoc); 0426 if(!img2.isNull()) { 0427 // myLog() << "found image in fallback location" << fallbackImgDir->path() << id_; 0428 // the img is in the other location 0429 factory->emitImageMismatch(); 0430 return img2; 0431 } else { 0432 myDebug() << "tried to add" << id_ << "from" << fallbackImgDir->path() << "but failed"; 0433 } 0434 } 0435 // at this point, there's a possibility that the user has changed settings so that the images 0436 // are currently in a local or data directory, but Config::imageLocation() doesn't match that location 0437 if(factory->d->dataImageDir.hasImage(id_)) { 0438 const Data::Image& img2 = factory->addCachedImageImpl(id_, DataDir); 0439 if(!img2.isNull()) { 0440 // myLog() << "found image in data dir location"; 0441 return img2; 0442 } 0443 } 0444 if(factory->d->localImageDir.hasImage(id_)) { 0445 const Data::Image& img2 = factory->addCachedImageImpl(id_, LocalDir); 0446 if(!img2.isNull()) { 0447 // myLog() << "found image in local dir location"; 0448 return img2; 0449 } 0450 } 0451 // now, it appears the image doesn't exist. The only remaining possibility 0452 // is that the file name has multiple periods and as a result of the fix for bug 348088, 0453 // the image is lurking in a local image directory without the multiple periods 0454 if(Config::imageLocation() == Config::ImagesInLocalDir && QDir(localDir()).dirName().contains(QLatin1Char('.'))) { 0455 QString realImageDir = localDir(); 0456 QDir d(realImageDir); 0457 // try to cd up and into the other old directory 0458 QString cdString = QLatin1String("../") + d.dirName().section(QLatin1Char('.'), 0, 0) + QLatin1String("_files/"); 0459 if(d.cd(cdString)) { 0460 factory->d->localImageDir.setPath(d.path() + QDir::separator()); 0461 if(factory->d->localImageDir.hasImage(id_)) { 0462 // myDebug() << "Reading image from old local directory" << (cdString+id_); 0463 const Data::Image& img2 = factory->addCachedImageImpl(id_, LocalDir); 0464 // Be sure to reset the image directory location!! 0465 factory->d->localImageDir.setPath(realImageDir); 0466 factory->d->localImageDir.writeImage(img2); 0467 return img2; 0468 } 0469 factory->d->localImageDir.setPath(realImageDir); 0470 } 0471 } 0472 0473 myDebug() << "***ImageFactory::imageById() - not found:" << id_; 0474 return Data::Image::null; 0475 } 0476 0477 bool ImageFactory::hasLocalImage(const QString& id_) { 0478 Q_ASSERT(factory && "ImageFactory is not initialized!"); 0479 if(id_.isEmpty() || !factory) { 0480 return false; 0481 } 0482 const QUrl u(id_); 0483 return factory->d->imageCache.contains(id_) || 0484 factory->d->imageDict.contains(id_) || 0485 factory->d->tempImageDir.hasImage(id_) || 0486 factory->d->imageZipArchive.hasImage(id_) || 0487 (u.isValid() && !u.isRelative() && u.isLocalFile()) || 0488 (Config::imageLocation() == Config::ImagesInLocalDir && 0489 factory->d->localImageDir.hasImage(id_)) || 0490 (Config::imageLocation() == Config::ImagesInAppDir && 0491 factory->d->dataImageDir.hasImage(id_)); 0492 } 0493 0494 void ImageFactory::requestImageById(const QString& id_) { 0495 Q_ASSERT(factory && "ImageFactory is not initialized!"); 0496 if(hasLocalImage(id_)) { 0497 emit factory->imageAvailable(id_); 0498 return; 0499 } 0500 if(factory->d->nullImages.contains(id_)) { 0501 // don't emit anything 0502 return; 0503 } 0504 const QUrl u(id_); 0505 // what does it mean when the Id is an absolute Url and yet the image is not link only? 0506 // Probably a heritage image id before the bugs were fixed. 0507 const bool linkOnly = (s_imageInfoMap.contains(id_) && s_imageInfoMap[id_].linkOnly); 0508 if(linkOnly || !u.isRelative()) { 0509 if(u.isValid()) { 0510 factory->requestImageByUrlImpl(u, true /* quiet */, QUrl() /* referrer */, linkOnly); 0511 if(!linkOnly) { 0512 myDebug() << "Loading an image url that is not link only. The image id will get updated."; 0513 } 0514 } 0515 } 0516 } 0517 0518 void ImageFactory::requestImageByUrlImpl(const QUrl& url_, bool quiet_, const QUrl& refer_, bool link_) { 0519 ImageJob* job = new ImageJob(url_, QString() /* id, use calculated one */, quiet_); 0520 job->setLinkOnly(link_); 0521 job->setReferrer(refer_); 0522 connect(job, &ImageJob::result, 0523 this, &ImageFactory::slotImageJobResult); 0524 } 0525 0526 Tellico::Data::ImageInfo ImageFactory::imageInfo(const QString& id_) { 0527 if(s_imageInfoMap.contains(id_)) { 0528 return s_imageInfoMap[id_]; 0529 } 0530 0531 const Data::Image& img = imageById(id_); 0532 if(img.isNull()) { 0533 return Data::ImageInfo(); 0534 } 0535 return Data::ImageInfo(img); 0536 } 0537 0538 void ImageFactory::cacheImageInfo(const Tellico::Data::ImageInfo& info) { 0539 s_imageInfoMap.insert(info.id, info); 0540 } 0541 0542 bool ImageFactory::hasImageInfo(const QString& id_) { 0543 return s_imageInfoMap.contains(id_); 0544 } 0545 0546 bool ImageFactory::validImage(const QString& id_) { 0547 // don't try s_imageInfoMap[id_] cause it inserts an empty image info 0548 return s_imageInfoMap.contains(id_) || factory->hasImageInMemory(id_) || !imageById(id_).isNull(); 0549 } 0550 0551 QPixmap ImageFactory::pixmap(const QString& id_, int width_, int height_) { 0552 if(id_.isEmpty()) { 0553 return QPixmap(); 0554 } 0555 0556 const QString key = id_ + QLatin1Char('|') + QString::number(width_) + QLatin1Char('|') + QString::number(height_); 0557 QPixmap* pix = factory->d->pixmapCache.object(key); 0558 if(pix) { 0559 return *pix; 0560 } 0561 0562 const Data::Image& img = imageById(id_); 0563 if(img.isNull()) { 0564 return QPixmap(); 0565 } 0566 0567 if(width_ > 0 && height_ > 0) { 0568 pix = new QPixmap(img.convertToPixmap(width_, height_)); 0569 } else { 0570 pix = new QPixmap(img.convertToPixmap()); 0571 } 0572 0573 QPixmap pix2(*pix); // retain a copy of pix in case it doesn't go into the cache 0574 // pixmap size is w x h x d, divided by 8 bits 0575 const int size = (pix->width()*pix->height()*pix->depth()/8); 0576 if(!factory->d->pixmapCache.insert(key, pix, pix->width()*pix->height()*pix->depth()/8)) { 0577 // at this point, pix might be deleted 0578 myWarning() << "can't save in cache: " << id_; 0579 myWarning() << "### Current pixmap size is " << size; 0580 myWarning() << "### Max pixmap cache size is " << factory->d->pixmapCache.maxCost(); 0581 return pix2; 0582 } 0583 return *pix; 0584 } 0585 0586 void ImageFactory::clean(bool purgeTempDirectory_) { 0587 // the caches all auto-delete 0588 s_imagesToRelease.clear(); 0589 qDeleteAll(factory->d->imageDict); 0590 factory->d->imageDict.clear(); 0591 s_imageInfoMap.clear(); 0592 factory->d->imageCache.clear(); 0593 factory->d->pixmapCache.clear(); 0594 if(purgeTempDirectory_) { 0595 factory->d->tempImageDir.purge(); 0596 // just to make sure all the image locations clean themselves up 0597 // delete the factory (which deletes the storage objects) and 0598 // then recreate the factory, in case anything else needs it 0599 // be sure to save local image directory if it's not a temp dir! 0600 const QString localDirName = localDir(); 0601 delete factory; 0602 factory = nullptr; 0603 ImageFactory::init(); 0604 if(QDir(localDirName).exists()) { 0605 setLocalDirectory(QUrl::fromLocalFile(localDirName)); 0606 } 0607 } 0608 } 0609 0610 void ImageFactory::createStyleImages(int collectionType_, const Tellico::StyleOptions& opt_) { 0611 const QColor& baseColor = opt_.baseColor.isValid() 0612 ? opt_.baseColor 0613 : Config::templateBaseColor(collectionType_); 0614 const QColor& highColor = opt_.highlightedBaseColor.isValid() 0615 ? opt_.highlightedBaseColor 0616 : Config::templateHighlightedBaseColor(collectionType_); 0617 0618 const QString bgname(QStringLiteral("gradient_bg.png")); 0619 const QColor& bgc1 = KColorUtils::mix(baseColor, highColor, 0.3); 0620 QImage bgImage = Tellico::gradient(QSize(600, 1), bgc1, baseColor, 0621 Tellico::PipeCrossGradient); 0622 bgImage = bgImage.transformed(QTransform().rotate(90)); 0623 0624 const QString hdrname(QStringLiteral("gradient_header.png")); 0625 const QColor& bgc2 = KColorUtils::mix(baseColor, highColor, 0.5); 0626 QImage hdrImage = Tellico::unbalancedGradient(QSize(1, 10), highColor, bgc2, 0627 Tellico::VerticalGradient, 100, -100); 0628 0629 if(opt_.imgDir.isEmpty()) { 0630 ImageFactory::removeImage(bgname, true /*delete */); 0631 factory->addImageImpl(Data::Image::byteArray(bgImage, "PNG"), QStringLiteral("PNG"), bgname); 0632 ImageFactory::writeCachedImage(bgname, cacheDir(), true /*force*/); 0633 0634 ImageFactory::removeImage(hdrname, true /*delete */); 0635 factory->addImageImpl(Data::Image::byteArray(hdrImage, "PNG"), QStringLiteral("PNG"), hdrname); 0636 ImageFactory::writeCachedImage(hdrname, cacheDir(), true /*force*/); 0637 } else { 0638 bgImage.save(opt_.imgDir + bgname, "PNG"); 0639 hdrImage.save(opt_.imgDir + hdrname, "PNG"); 0640 } 0641 } 0642 0643 void ImageFactory::removeImage(const QString& id_, bool deleteImage_) { 0644 // be careful using this 0645 delete factory->d->imageDict.take(id_); 0646 factory->d->imageCache.remove(id_); 0647 0648 if(deleteImage_) { 0649 // remove from everywhere 0650 factory->d->dataImageDir.removeImage(id_); 0651 factory->d->localImageDir.removeImage(id_); 0652 factory->d->tempImageDir.removeImage(id_); 0653 } 0654 } 0655 0656 Tellico::StringSet ImageFactory::imagesNotInCache() { 0657 StringSet set; 0658 QHash<QString, Tellico::Data::Image*>::const_iterator end = factory->d->imageDict.constEnd(); 0659 for(QHash<QString, Tellico::Data::Image*>::const_iterator it = factory->d->imageDict.constBegin(); it != end; ++it) { 0660 if(!factory->d->imageCache.contains(it.key())) { 0661 set.add(it.key()); 0662 } 0663 } 0664 return set; 0665 } 0666 0667 bool ImageFactory::hasImageInMemory(const QString& id_) const { 0668 return d->imageCache.contains(id_) || d->imageDict.contains(id_); 0669 } 0670 0671 bool ImageFactory::hasNullImage(const QString& id_) const { 0672 return d->nullImages.contains(id_); 0673 } 0674 0675 // the purpose here is to remove images from the dict if they're is on the disk somewhere, 0676 // either in tempDir() or in dataDir(). The use for this is for calling pixmap() on an 0677 // image too big to stay in the cache. Then it stays in the dict forever. 0678 void ImageFactory::releaseImages() { 0679 #ifdef RELEASE_IMAGES 0680 if(s_imagesToRelease.isEmpty()) { 0681 return; 0682 } 0683 0684 foreach(const QString& id, s_imagesToRelease) { 0685 if(!d->imageDict.contains(id)) { 0686 continue; 0687 } 0688 if(d->dataImageDir.hasImage(id) || 0689 d->localImageDir.hasImage(id) || 0690 d->tempImageDir.hasImage(id)) { 0691 delete d->imageDict.take(id); 0692 } 0693 } 0694 s_imagesToRelease.clear(); 0695 #endif 0696 } 0697 0698 void ImageFactory::emitImageMismatch() { 0699 emit imageLocationMismatch(); 0700 } 0701 0702 QString ImageFactory::localDirectory(const QUrl& url_) { 0703 if(url_.isEmpty()) { 0704 return QString(); 0705 } 0706 if(!url_.isLocalFile()) { 0707 myWarning() << "Tellico can only save images to local disk"; 0708 myWarning() << "unable to save to " << url_; 0709 return QString(); 0710 } 0711 QString dir = url_.adjusted(QUrl::RemoveFilename).toLocalFile(); 0712 // could have already been set once 0713 if(!dir.contains(QLatin1String("_files"))) { 0714 QFileInfo fi(url_.fileName()); 0715 dir += fi.completeBaseName() + QLatin1String("_files/"); 0716 } 0717 return dir; 0718 } 0719 0720 void ImageFactory::setLocalDirectory(const QUrl& url_) { 0721 Q_ASSERT(factory && "ImageFactory is not initialized!"); 0722 const QString localDirName = localDirectory(url_); 0723 if(!localDirName.isEmpty()) { 0724 factory->d->localImageDir.setPath(localDirName); 0725 } 0726 } 0727 0728 void ImageFactory::setZipArchive(std::unique_ptr<KZip> zip_) { 0729 Q_ASSERT(factory && "ImageFactory is not initialized!"); 0730 if(!zip_) { 0731 return; 0732 } 0733 factory->d->imageZipArchive.setZip(std::move(zip_)); 0734 } 0735 0736 void ImageFactory::slotImageJobResult(KJob* job_) { 0737 ImageJob* imageJob = qobject_cast<ImageJob*>(job_); 0738 Q_ASSERT(imageJob); 0739 if(!imageJob) { 0740 myWarning() << "No image job"; 0741 return; 0742 } 0743 const Data::Image& img = imageJob->image(); 0744 if(img.isNull()) { 0745 myDebug() << "null image for" << imageJob->url(); 0746 d->nullImages.add(imageJob->url().url()); 0747 // don't emit anything 0748 return; 0749 } 0750 0751 // hold the image in memory since it probably isn't written locally to disk yet 0752 if(!d->imageDict.contains(img.id())) { 0753 d->imageDict.insert(img.id(), new Data::Image(img)); 0754 s_imageInfoMap.insert(img.id(), Data::ImageInfo(img)); 0755 } 0756 emit factory->imageAvailable(img.id()); 0757 } 0758 0759 #undef RELEASE_IMAGES