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: