File indexing completed on 2024-05-12 15:55:37
0001 // SPDX-FileCopyrightText: 2003-2020 Jesper K. Pedersen <blackie@kde.org> 0002 // SPDX-FileCopyrightText: 2021-2023 Johannes Zarl-Zierl <johannes@zarl-zierl.at> 0003 // 0004 // SPDX-License-Identifier: GPL-2.0-or-later 0005 0006 #ifndef KPATHUMBNAILS_THUMBNAILCACHE_H 0007 #define KPATHUMBNAILS_THUMBNAILCACHE_H 0008 #include "CacheFileInfo.h" 0009 0010 #include <kpabase/FileNameList.h> 0011 0012 #include <QDir> 0013 #include <QFile> 0014 #include <QHash> 0015 #include <QImage> 0016 #include <QMutex> 0017 0018 template <class Key, class T> 0019 class QCache; 0020 0021 namespace ImageManager 0022 { 0023 0024 class ThumbnailMapping; 0025 0026 /** 0027 * @brief The ThumbnailCache implements thumbnail storage optimized for speed. 0028 * 0029 * ## On-disk storage 0030 * The problem with the FreeDesktop.org thumbnail storage is that there is one file per image. 0031 * This means that showing a full page of thumbnails, containing dozens of images requires many 0032 * file operations. 0033 * 0034 * Our storage scheme introduces a single index file (\c thumbnailindex) that contains 0035 * the index of known thumbnails and their location in the thumbnail storage files (\c thumb-N). 0036 * The thumbnail storage files contain raw JPEG thumbnail data. 0037 * 0038 * This layout creates far less files on the filesystem, 0039 * the files can be memory-mapped, and because similar sets of images are often 0040 * shown together, data locality is used to our advantage. 0041 * 0042 * ## Caveats 0043 * Note that thumbnails are only ever added, never deleted from the thumbnail files. 0044 * Old images remain in the thumbnail files - they are just removed from the index file. 0045 * 0046 * ## Further reading 0047 * - https://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html 0048 */ 0049 class ThumbnailCache : public QObject 0050 { 0051 Q_OBJECT 0052 0053 public: 0054 /** 0055 * @brief ThumbnailCache 0056 * Provide access to a KPhotoAlbum-style thumbnail cache in the given directory. 0057 * @param baseDirectory the directory in which the \c thumbnailindex file resides. 0058 */ 0059 explicit ThumbnailCache(const QString &baseDirectory); 0060 ~ThumbnailCache() override; 0061 /** 0062 * @brief Insert a thumbnail for the given file. 0063 * @param name the image file name 0064 * @param image the thumbnail data 0065 */ 0066 void insert(const DB::FileName &name, const QImage &image); 0067 /** 0068 * @brief insert inserts raw JPEG-encoded data into the thumbnail database. 0069 * It is recommended that you use insert(const DB::FileName&, const QImage&) if possible. 0070 * @param name the image file name 0071 * @param thumbnailData the JPEG encoded image data 0072 */ 0073 void insert(const DB::FileName &name, const QByteArray &thumbnailData); 0074 /** 0075 * @brief lookup and return the thumbnail for the given file. 0076 * Note: this method requires a GuiApplication to exist. 0077 * @param name the image file name 0078 * @return a QPixmap containing the thumbnail, or a null QPixmap if no thumbnail was found. 0079 */ 0080 QPixmap lookup(const DB::FileName &name) const; 0081 /** 0082 * @brief lookupRawData 0083 * @param name the image file name 0084 * @return the raw JPEG thumbnail data or a null QByteArray. 0085 */ 0086 QByteArray lookupRawData(const DB::FileName &name) const; 0087 /** 0088 * @brief Check if the ThumbnailCache contains a thumbnail for the given file. 0089 * @param name the image file name 0090 * @return \c true if the thumbnail exists, \c false otherwise. 0091 */ 0092 bool contains(const DB::FileName &name) const; 0093 /** 0094 * @brief "Forget" the thumbnail for an image. 0095 * @param name the image file name 0096 */ 0097 void removeThumbnail(const DB::FileName &name); 0098 /** 0099 * @brief "Forget" the thumbnails for the given images. 0100 * Like removeThumbnail(), but for a list of images 0101 * @param names a list of image file names 0102 */ 0103 void removeThumbnails(const DB::FileNameList &names); 0104 0105 /** 0106 * @brief thumbnailSize 0107 * Usually, this is the size of the thumbnails in the cache. 0108 * If the index file was converted from an older file version (4), 0109 * the size is read from the configuration file. 0110 * @return the current thumbnail size. 0111 */ 0112 int thumbnailSize() const; 0113 0114 /** 0115 * @brief Returns the file format version of the thumbnailindex file currently on disk. 0116 * 0117 * Usually, this is equal to the current version, but if an old ThumbnailCache 0118 * that is still compatible with this version of KPhotoAlbum is loaded and was not yet stored, 0119 * it may differ. 0120 * @return 4 or 5 if the cache has been written to disk, or -1 for a fresh, unsaved cache. 0121 */ 0122 int actualFileVersion() const; 0123 0124 /** 0125 * @brief Version of the tumbnailindex file when saved. 0126 * @return The file format version of the thumbnailindex file. 0127 */ 0128 static int preferredFileVersion(); 0129 0130 /** 0131 * @brief Check all thumbnails for consistency with thumbnailSize(). 0132 * Only the thumbnails which are saved to disk are checked. 0133 * If you have changed the cache you need to save it to guarantee correct results. 0134 * @return all thumbnails that do not match the expected image dimensions. 0135 */ 0136 DB::FileNameList findIncorrectlySizedThumbnails() const; 0137 0138 /** 0139 * @brief size 0140 * @return the number of (saved) thumbnails in the cache 0141 */ 0142 int size() const; 0143 0144 /** 0145 * @brief Compacts the on-disk storage for the cache by discarding stale data from its files. 0146 * To ensure consistency, this method also saves the cache. 0147 */ 0148 void vacuum(); 0149 0150 public Q_SLOTS: 0151 /** 0152 * @brief Save the thumbnail cache to disk. 0153 * Note: this method emits an internal signal which calls the actual save implementation. 0154 * Therefore, saving may not be finished when this method returns. 0155 */ 0156 void save(); 0157 /** 0158 * @brief Invalidate the ThumbnailCache and remove the thumbnail files and index. 0159 */ 0160 void flush(); 0161 0162 /** 0163 * @brief setThumbnailSize sets the thumbnail size recorded in the thumbnail index file. 0164 * If the value changes, the thumbnail cache is invalidated. 0165 * Except minimal sanity checks, no bounds for thumbnailSize are enforced. 0166 * @param thumbnailSize 0167 */ 0168 void setThumbnailSize(int thumbnailSize); 0169 Q_SIGNALS: 0170 /** 0171 * @brief doSave is emitted when save() is called. 0172 * This signal is more or less an internal signal. 0173 */ 0174 void doSave(); 0175 0176 /** 0177 * @brief cacheInvalidated is emitted when the thumbnails are no longer valid. 0178 * This usually happens when the thumbnail size changed. 0179 * This signal is *not* emitted when the cache was flushed by explicit request. 0180 * @see cacheFlushed() 0181 */ 0182 void cacheInvalidated(); 0183 0184 /** 0185 * @brief cacheFlushed is emitted if the cache was flushed by explicit request. 0186 * @see flush() 0187 * @see cacheInvalidated() 0188 */ 0189 void cacheFlushed(); 0190 0191 /** 0192 * @brief saveComplete is emitted after the thumbnail cache was successfully saved. 0193 * At the time the signal is emitted, the cache is not dirty (i.e. a full save was performed). 0194 */ 0195 void saveComplete(); 0196 0197 /** 0198 * @brief thumbnailChanged is emitted when a thumbnail for a file was inserted. 0199 * @param name the name of the inserted or updated file 0200 */ 0201 void thumbnailUpdated(const DB::FileName &name); 0202 0203 private: 0204 /** 0205 * @brief load the \c thumbnailindex file if possible. 0206 * This function populates the thumbnail hash, but does not 0207 * load any actual thumbnail data. 0208 * If the file does not exist, or if it is not compatible, 0209 * then it is discarded. 0210 */ 0211 void load(); 0212 QString fileNameForIndex(int index) const; 0213 /** 0214 * @brief thumbnailPath 0215 * @param utf8FileName the name of the file (does not have to exist), UTF-8 encoded 0216 * @return the file path for the named file in the thumbnail directory 0217 */ 0218 QString thumbnailPath(const char *utf8FileName) const; 0219 /** 0220 * @brief thumbnailPath 0221 * @param fileName the name of the file (does not have to exist) 0222 * @return the file path for the named file in the thumbnail directory 0223 */ 0224 QString thumbnailPath(const QString &fileName) const; 0225 0226 int m_fileVersion = -1; 0227 int m_thumbnailSize = -1; 0228 const QDir m_baseDir; 0229 QHash<DB::FileName, CacheFileInfo> m_hash; 0230 QHash<DB::FileName, CacheFileInfo> m_unsavedHash; 0231 /* Protects accesses to the data (hash and unsaved hash) */ 0232 mutable QMutex m_dataLock; 0233 /* Prevents multiple saves from happening simultaneously */ 0234 QMutex m_saveLock; 0235 /* Protects writing thumbnails to disk */ 0236 QMutex m_thumbnailWriterLock; 0237 int m_currentFile; 0238 int m_currentOffset; 0239 QTimer *m_timer; 0240 bool m_needsFullSave; 0241 bool m_isDirty; 0242 /** 0243 * @brief saveFull stores the full contents of the index file. 0244 * I.e. the whole file is rewritten. 0245 */ 0246 void saveFull(); 0247 /** 0248 * @brief saveIncremental appends all unsaved hash entries to the index file. 0249 */ 0250 void saveIncremental(); 0251 /** 0252 * @brief saveInternal checks whether a full save is requested or needed and calls the incremental or full save accordingly. 0253 */ 0254 void saveInternal(); 0255 /** 0256 * @brief saveImpl calls saveInternal and resets the save timer. 0257 */ 0258 void saveImpl(); 0259 0260 /** 0261 * Holds an in-memory cache of thumbnail files. 0262 */ 0263 mutable QCache<int, ThumbnailMapping> *m_memcache; 0264 mutable QFile *m_currentWriter; 0265 }; 0266 0267 /** 0268 * @brief defaultThumbnailDirectory 0269 * @return the default thumbnail (sub-)directory name, e.g. ".thumbnails" 0270 */ 0271 QString defaultThumbnailDirectory(); 0272 } 0273 0274 #endif /* KPATHUMBNAILS_THUMBNAILCACHE_H */ 0275 0276 // vi:expandtab:tabstop=4 shiftwidth=4: