1 // SPDX-FileCopyrightText: 2003-2020 Jesper K. Pedersen <blackie@kde.org> 2 // SPDX-FileCopyrightText: 2021 Johannes Zarl-Zierl <johannes@zarl-zierl.at> 3 // 4 // SPDX-License-Identifier: GPL-2.0-or-later 5 6 #ifndef KPATHUMBNAILS_THUMBNAILCACHE_H 7 #define KPATHUMBNAILS_THUMBNAILCACHE_H 8 #include "CacheFileInfo.h" 9 10 #include <kpabase/FileNameList.h> 11 12 #include <QDir> 13 #include <QFile> 14 #include <QHash> 15 #include <QImage> 16 #include <QMutex> 17 18 template <class Key, class T> 19 class QCache; 20 21 namespace ImageManager 22 { 23 24 class ThumbnailMapping; 25 26 /** 27 * @brief The ThumbnailCache implements thumbnail storage optimized for speed. 28 * 29 * ## On-disk storage 30 * The problem with the FreeDesktop.org thumbnail storage is that there is one file per image. 31 * This means that showing a full page of thumbnails, containing dozens of images requires many 32 * file operations. 33 * 34 * Our storage scheme introduces a single index file (\c thumbnailindex) that contains 35 * the index of known thumbnails and their location in the thumbnail storage files (\c thumb-N). 36 * The thumbnail storage files contain raw JPEG thumbnail data. 37 * 38 * This layout creates far less files on the filesystem, 39 * the files can be memory-mapped, and because similar sets of images are often 40 * shown together, data locality is used to our advantage. 41 * 42 * ## Caveats 43 * Note that thumbnails are only ever added, never deleted from the thumbnail files. 44 * Old images remain in the thumbnail files - they are just removed from the index file. 45 * 46 * ## Further reading 47 * - https://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html 48 */ 49 class ThumbnailCache : public QObject 50 { 51 Q_OBJECT 52 53 public: 54 /** 55 * @brief ThumbnailCache 56 * Provide access to a KPhotoAlbum-style thumbnail cache in the given directory. 57 * @param baseDirectory the directory in which the \c thumbnailindex file resides. 58 */ 59 ThumbnailCache(const QString &baseDirectory); 60 ~ThumbnailCache() override; 61 /** 62 * @brief Insert a thumbnail for the given file. 63 * @param name the image file name 64 * @param image the thumbnail data 65 */ 66 void insert(const DB::FileName &name, const QImage &image); 67 /** 68 * @brief insert inserts raw JPEG-encoded data into the thumbnail database. 69 * It is recommended that you use insert(const DB::FileName&, const QImage&) if possible. 70 * @param name the image file name 71 * @param thumbnailData the JPEG encoded image data 72 */ 73 void insert(const DB::FileName &name, const QByteArray &thumbnailData); 74 /** 75 * @brief lookup and return the thumbnail for the given file. 76 * Note: this method requires a GuiApplication to exist. 77 * @param name the image file name 78 * @return a QPixmap containing the thumbnail, or a null QPixmap if no thumbnail was found. 79 */ 80 QPixmap lookup(const DB::FileName &name) const; 81 /** 82 * @brief lookupRawData 83 * @param name the image file name 84 * @return the raw JPEG thumbnail data or a null QByteArray. 85 */ 86 QByteArray lookupRawData(const DB::FileName &name) const; 87 /** 88 * @brief Check if the ThumbnailCache contains a thumbnail for the given file. 89 * @param name the image file name 90 * @return \c true if the thumbnail exists, \c false otherwise. 91 */ 92 bool contains(const DB::FileName &name) const; 93 /** 94 * @brief "Forget" the thumbnail for an image. 95 * @param name the image file name 96 */ 97 void removeThumbnail(const DB::FileName &name); 98 /** 99 * @brief "Forget" the thumbnails for the given images. 100 * Like removeThumbnail(), but for a list of images 101 * @param names a list of image file names 102 */ 103 void removeThumbnails(const DB::FileNameList &names); 104 105 /** 106 * @brief thumbnailSize 107 * Usually, this is the size of the thumbnails in the cache. 108 * If the index file was converted from an older file version (4), 109 * the size is read from the configuration file. 110 * @return the current thumbnail size. 111 */ 112 int thumbnailSize() const; 113 114 /** 115 * @brief Returns the file format version of the thumbnailindex file currently on disk. 116 * 117 * Usually, this is equal to the current version, but if an old ThumbnailCache 118 * that is still compatible with this version of KPhotoAlbum is loaded and was not yet stored, 119 * it may differ. 120 * @return 4 or 5 if the cache has been written to disk, or -1 for a fresh, unsaved cache. 121 */ 122 int actualFileVersion() const; 123 124 /** 125 * @brief Version of the tumbnailindex file when saved. 126 * @return The file format version of the thumbnailindex file. 127 */ 128 static int preferredFileVersion(); 129 130 /** 131 * @brief Check all thumbnails for consistency with thumbnailSize(). 132 * Only the thumbnails which are saved to disk are checked. 133 * If you have changed changed the cache you need to save it to guarantee correct results. 134 * @return all thumbnails that do not match the expected image dimensions. 135 */ 136 DB::FileNameList findIncorrectlySizedThumbnails() const; 137 138 /** 139 * @brief size 140 * @return the number of (saved) thumbnails in the cache 141 */ 142 int size() const; 143 144 /** 145 * @brief Compacts the on-disk storage for the cache by discarding stale data from its files. 146 * To ensure consistency, this method also saves the cache. 147 */ 148 void vacuum(); 149 150 public slots: 151 /** 152 * @brief Save the thumbnail cache to disk. 153 * Note: this method emits an internal signal which calls the actual save implementation. 154 * Therefore, saving may not be finished when this method returns. 155 */ 156 void save() const; 157 /** 158 * @brief Invalidate the ThumbnailCache and remove the thumbnail files and index. 159 */ 160 void flush(); 161 162 /** 163 * @brief setThumbnailSize sets the thumbnail size recorded in the thumbnail index file. 164 * If the value changes, the thumbnail cache is invalidated. 165 * Except minimal sanity checks, no bounds for thumbnailSize are enforced. 166 * @param thumbnailSize 167 */ 168 void setThumbnailSize(int thumbnailSize); 169 signals: 170 /** 171 * @brief doSave is emitted when save() is called. 172 * This signal is more or less an internal signal. 173 */ 174 void doSave() const; 175 176 /** 177 * @brief cacheInvalidated is emitted when the thumbnails are no longer valid. 178 * This usually happens when the thumbnail size changed. 179 * This signal is *not* emitted when the cache was flushed by explicit request. 180 * @see cacheFlushed() 181 */ 182 void cacheInvalidated(); 183 184 /** 185 * @brief cacheFlushed is emitted if the cache was flushed by explicit request. 186 * @see flush() 187 * @see cacheInvalidated() 188 */ 189 void cacheFlushed(); 190 191 /** 192 * @brief saveComplete is emitted after the thumbnail cache was successfully saved. 193 * At the time the signal is emitted, the cache is not dirty (i.e. a full save was performed). 194 */ 195 void saveComplete() const; 196 197 private: 198 /** 199 * @brief load the \c thumbnailindex file if possible. 200 * This function populates the thumbnail hash, but does not 201 * load any actual thumbnail data. 202 * If the file does not exist, or if it is not compatible, 203 * then it is discarded. 204 */ 205 void load(); 206 QString fileNameForIndex(int index) const; 207 /** 208 * @brief thumbnailPath 209 * @param utf8FileName the name of the file (does not have to exist), UTF-8 encoded 210 * @return the file path for the named file in the thumbnail directory 211 */ 212 QString thumbnailPath(const char *utf8FileName) const; 213 /** 214 * @brief thumbnailPath 215 * @param fileName the name of the file (does not have to exist) 216 * @return the file path for the named file in the thumbnail directory 217 */ 218 QString thumbnailPath(const QString &fileName) const; 219 220 // mutable because saveIncremental is const 221 mutable int m_fileVersion = -1; 222 int m_thumbnailSize = -1; 223 const QDir m_baseDir; 224 QHash<DB::FileName, CacheFileInfo> m_hash; 225 mutable QHash<DB::FileName, CacheFileInfo> m_unsavedHash; 226 /* Protects accesses to the data (hash and unsaved hash) */ 227 mutable QMutex m_dataLock; 228 /* Prevents multiple saves from happening simultaneously */ 229 mutable QMutex m_saveLock; 230 /* Protects writing thumbnails to disk */ 231 mutable QMutex m_thumbnailWriterLock; 232 int m_currentFile; 233 int m_currentOffset; 234 mutable QTimer *m_timer; 235 mutable bool m_needsFullSave; 236 mutable bool m_isDirty; 237 /** 238 * @brief saveFull stores the full contents of the index file. 239 * I.e. the whole file is rewritten. 240 */ 241 void saveFull() const; 242 /** 243 * @brief saveIncremental appends all unsaved hash entries to the index file. 244 */ 245 void saveIncremental() const; 246 /** 247 * @brief saveInternal checks whether a full save is requested or needed and calls the incremental or full save accordingly. 248 */ 249 void saveInternal() const; 250 /** 251 * @brief saveImpl calls saveInternal and resets the save timer. 252 */ 253 void saveImpl() const; 254 255 /** 256 * Holds an in-memory cache of thumbnail files. 257 */ 258 mutable QCache<int, ThumbnailMapping> *m_memcache; 259 mutable QFile *m_currentWriter; 260 }; 261 262 /** 263 * @brief defaultThumbnailDirectory 264 * @return the default thumbnail (sub-)directory name, e.g. ".thumbnails" 265 */ 266 QString defaultThumbnailDirectory(); 267 } 268 269 #endif /* KPATHUMBNAILS_THUMBNAILCACHE_H */ 270 271 // vi:expandtab:tabstop=4 shiftwidth=4: 272