feat: implement image cache management with max entries limit

This commit is contained in:
2026-03-01 06:28:08 +01:00
parent bf2f3d57c7
commit 5df0b53df0
11 changed files with 256 additions and 67 deletions
+2
View File
@@ -81,3 +81,5 @@ CMakeLists.txt.user*
.uic/ .uic/
/build*/ /build*/
.cache .cache
.vscode
+7 -5
View File
@@ -126,10 +126,11 @@ Controls the layout and dimensions of the application window and image items.
Controls what UI state is persisted between sessions. Controls what UI state is persisted between sessions.
| Property | Type | Default | Description | | Property | Type | Default | Description |
| :--------------- | :------ | :------ | :------------------------------------------ | | :---------------- | :------ | :------ | :---------------------------------------------------------------------------- |
| `saveSortMethod` | Boolean | `true` | Whether to persist the sort type and order. | | `saveSortMethod` | Boolean | `true` | Whether to persist the sort type and order. |
| `savePalette` | Boolean | `true` | Whether to persist the selected palette. | | `savePalette` | Boolean | `true` | Whether to persist the selected palette. |
| `maxImageEntries` | Integer | `1000` | Maximum number of entries in the image cache (older entries will be evicted). |
--- ---
@@ -183,7 +184,8 @@ Controls what UI state is persisted between sessions.
}, },
"cache": { "cache": {
"saveSortMethod": true, "saveSortMethod": true,
"savePalette": true "savePalette": true,
"maxImageEntries": 300
} }
} }
``` ```
+207 -47
View File
@@ -7,6 +7,7 @@
#include <QSqlError> #include <QSqlError>
#include <QSqlQuery> #include <QSqlQuery>
#include <QThread> #include <QThread>
#include <QtConcurrent>
#include "logger.hpp" #include "logger.hpp"
@@ -26,20 +27,41 @@ static QLatin1StringView settingKey(SettingsType type) {
} }
QString Manager::cacheKey(const QFileInfo& fileInfo, const QSize& imageSize) { QString Manager::cacheKey(const QFileInfo& fileInfo, const QSize& imageSize) {
const QString raw = fileInfo.absoluteFilePath() + QString::number(fileInfo.lastModified().toMSecsSinceEpoch()) + u'x' + QString::number(imageSize.width()) + u'x' + QString::number(imageSize.height()); const QString raw = fileInfo.absoluteFilePath() +
QString::number(fileInfo.lastModified().toMSecsSinceEpoch()) +
u'x' + QString::number(imageSize.width()) +
u'x' + QString::number(imageSize.height());
return QString::fromLatin1( return QString::fromLatin1(
QCryptographicHash::hash(raw.toUtf8(), QCryptographicHash::Sha256).toHex()); QCryptographicHash::hash(raw.toUtf8(), QCryptographicHash::Sha256).toHex());
} }
Manager::Manager(const QDir& cacheDir) Manager::Manager(const QDir& cacheDir, int maxEntries)
: m_cacheDir(cacheDir), m_dbPath(cacheDir.filePath(u"cache.db"_s)), m_connectionPrefix(u"WallReelCache:"_s + QString::fromLatin1(QCryptographicHash::hash(m_dbPath.toUtf8(), QCryptographicHash::Md5).toHex())) { : m_cacheDir(cacheDir),
m_maxEntries(maxEntries),
m_dbPath(cacheDir.filePath(u"cache.db"_s)),
m_connectionPrefix(u"WallReelCache:"_s +
QString::fromLatin1(QCryptographicHash::hash(
m_dbPath.toUtf8(),
QCryptographicHash::Md5)
.toHex())) {
WR_DEBUG(u"Initializing cache db: %1"_s.arg(m_dbPath)); WR_DEBUG(u"Initializing cache db: %1"_s.arg(m_dbPath));
// Open a connection on the constructing thread so the schema is // Open a connection on the constructing thread so the schema is
// guaranteed to exist before any worker thread first calls _db(). // guaranteed to exist before any worker thread first calls _db().
_db(); _db();
} }
void Manager::evictOldEntries() {
if (m_maxEntries > 0)
m_cleanupFuture = QtConcurrent::run([this] { _runCleanup(); });
}
Manager::~Manager() { Manager::~Manager() {
// Wait for the background cleanup to finish before tearing down DB connections.
if (m_cleanupFuture.isValid() && !m_cleanupFuture.isFinished()) {
WR_DEBUG(u"Waiting for cache cleanup to finish..."_s);
m_cleanupFuture.waitForFinished();
}
QSet<QString> names; QSet<QString> names;
{ {
QMutexLocker lock(&m_connectionsMutex); QMutexLocker lock(&m_connectionsMutex);
@@ -59,23 +81,23 @@ void Manager::clearCache(Type type) {
if ((type & Type::Image) != Type::None) { if ((type & Type::Image) != Type::None) {
int removed = 0; int removed = 0;
QSqlQuery selectQuery(db); QSqlQuery selectQuery(db);
if (selectQuery.exec(QStringLiteral("SELECT file_name FROM image_cache"))) { if (selectQuery.exec(u"SELECT file_name FROM image_cache"_s)) {
while (selectQuery.next()) { while (selectQuery.next()) {
QFile::remove(m_cacheDir.filePath(selectQuery.value(0).toString())); QFile::remove(m_cacheDir.filePath(selectQuery.value(0).toString()));
++removed; ++removed;
} }
} }
QSqlQuery(db).exec(QStringLiteral("DELETE FROM image_cache")); QSqlQuery(db).exec(u"DELETE FROM image_cache"_s);
WR_INFO(u"Cleared %1 image cache file(s)"_s.arg(removed)); WR_INFO(u"Cleared %1 image cache file(s)"_s.arg(removed));
} }
if ((type & Type::Color) != Type::None) { if ((type & Type::Color) != Type::None) {
QSqlQuery(db).exec(QStringLiteral("DELETE FROM color_cache")); QSqlQuery(db).exec(u"DELETE FROM color_cache"_s);
WR_INFO(u"Cleared color cache"_s); WR_INFO(u"Cleared color cache"_s);
} }
if ((type & Type::Settings) != Type::None) { if ((type & Type::Settings) != Type::None) {
QSqlQuery(db).exec(QStringLiteral("DELETE FROM settings_cache")); QSqlQuery(db).exec(u"DELETE FROM settings_cache"_s);
WR_INFO(u"Cleared settings cache"_s); WR_INFO(u"Cleared settings cache"_s);
} }
} }
@@ -84,17 +106,25 @@ QColor Manager::getColor(const QString& key, const std::function<QColor()>& comp
QSqlDatabase db = _db(); QSqlDatabase db = _db();
if (db.isOpen()) { if (db.isOpen()) {
QSqlQuery query(db); QSqlQuery query(db);
query.prepare(QStringLiteral( query.prepare(u"SELECT r, g, b, a FROM color_cache WHERE key = :key"_s);
"SELECT r, g, b, a FROM color_cache WHERE key = :key"));
query.bindValue(u":key"_s, key); query.bindValue(u":key"_s, key);
if (query.exec() && query.next()) { if (query.exec() && query.next()) {
WR_DEBUG(u"Color cache hit [%1]"_s.arg(key)); WR_DEBUG(u"Color cache hit [%1]"_s.arg(key));
return QColor( QColor result(
query.value(0).toInt(), query.value(0).toInt(),
query.value(1).toInt(), query.value(1).toInt(),
query.value(2).toInt(), query.value(2).toInt(),
query.value(3).toInt()); query.value(3).toInt());
{
QMutexLocker lk(&m_hotKeysMutex);
m_hotColorKeys.insert(key);
}
QSqlQuery touchQuery(db);
touchQuery.prepare(u"UPDATE color_cache SET last_accessed = CURRENT_TIMESTAMP WHERE key = :key"_s);
touchQuery.bindValue(u":key"_s, key);
touchQuery.exec();
return result;
} }
} }
@@ -113,9 +143,9 @@ QColor Manager::getColor(const QString& key, const std::function<QColor()>& comp
if (db.isOpen()) { if (db.isOpen()) {
QSqlQuery insertQuery(db); QSqlQuery insertQuery(db);
insertQuery.prepare(QStringLiteral( insertQuery.prepare(
"INSERT OR REPLACE INTO color_cache (key, r, g, b, a) " u"INSERT OR REPLACE INTO color_cache (key, r, g, b, a, last_accessed) "
"VALUES (:key, :r, :g, :b, :a)")); "VALUES (:key, :r, :g, :b, :a, CURRENT_TIMESTAMP)"_s);
insertQuery.bindValue(u":key"_s, key); insertQuery.bindValue(u":key"_s, key);
insertQuery.bindValue(u":r"_s, color.red()); insertQuery.bindValue(u":r"_s, color.red());
insertQuery.bindValue(u":g"_s, color.green()); insertQuery.bindValue(u":g"_s, color.green());
@@ -135,8 +165,7 @@ QFileInfo Manager::getImage(const QString& key, const std::function<QImage()>& c
QSqlDatabase db = _db(); QSqlDatabase db = _db();
if (db.isOpen()) { if (db.isOpen()) {
QSqlQuery query(db); QSqlQuery query(db);
query.prepare(QStringLiteral( query.prepare(u"SELECT file_name FROM image_cache WHERE key = :key"_s);
"SELECT file_name FROM image_cache WHERE key = :key"));
query.bindValue(u":key"_s, key); query.bindValue(u":key"_s, key);
if (query.exec() && query.next()) { if (query.exec() && query.next()) {
@@ -144,13 +173,21 @@ QFileInfo Manager::getImage(const QString& key, const std::function<QImage()>& c
if (cached.exists()) { if (cached.exists()) {
WR_DEBUG(u"Image cache hit [%1] -> %2"_s WR_DEBUG(u"Image cache hit [%1] -> %2"_s
.arg(key, cached.absoluteFilePath())); .arg(key, cached.absoluteFilePath()));
{
QMutexLocker lk(&m_hotKeysMutex);
m_hotImageKeys.insert(key);
}
QSqlQuery touchQuery(db);
touchQuery.prepare(u"UPDATE image_cache SET last_accessed = CURRENT_TIMESTAMP WHERE key = :key"_s);
touchQuery.bindValue(u":key"_s, key);
touchQuery.exec();
return cached; return cached;
} }
// File was deleted externally — evict the stale DB record. // File was deleted externally — evict the stale DB record.
WR_WARN(u"Image cache stale, file missing [%1], evicting"_s.arg(key)); WR_WARN(u"Image cache stale, file missing [%1], evicting"_s.arg(key));
QSqlQuery evict(db); QSqlQuery evict(db);
evict.prepare(QStringLiteral("DELETE FROM image_cache WHERE key = :key")); evict.prepare(u"DELETE FROM image_cache WHERE key = :key"_s);
evict.bindValue(u":key"_s, key); evict.bindValue(u":key"_s, key);
evict.exec(); evict.exec();
} }
@@ -168,10 +205,10 @@ QFileInfo Manager::getImage(const QString& key, const std::function<QImage()>& c
return QFileInfo{}; return QFileInfo{};
} }
const QString fileName = key + u".png"_s; const QString fileName = key + u".jpg"_s;
const QString filePath = m_cacheDir.filePath(fileName); const QString filePath = m_cacheDir.filePath(fileName);
if (!image.save(filePath, "PNG")) { if (!image.save(filePath, "JPEG", 85)) {
WR_WARN(u"Failed to save image to %1"_s.arg(filePath)); WR_WARN(u"Failed to save image to %1"_s.arg(filePath));
return QFileInfo{}; return QFileInfo{};
} }
@@ -179,14 +216,18 @@ QFileInfo Manager::getImage(const QString& key, const std::function<QImage()>& c
if (db.isOpen()) { if (db.isOpen()) {
QSqlQuery insertQuery(db); QSqlQuery insertQuery(db);
insertQuery.prepare(QStringLiteral( insertQuery.prepare(
"INSERT OR REPLACE INTO image_cache (key, file_name) " u"INSERT OR REPLACE INTO image_cache (key, file_name, last_accessed) "
"VALUES (:key, :file_name)")); "VALUES (:key, :file_name, CURRENT_TIMESTAMP)"_s);
insertQuery.bindValue(u":key"_s, key); insertQuery.bindValue(u":key"_s, key);
insertQuery.bindValue(u":file_name"_s, fileName); insertQuery.bindValue(u":file_name"_s, fileName);
if (!insertQuery.exec()) if (!insertQuery.exec())
WR_WARN(u"Failed to record image in db [%1]: %2"_s WR_WARN(u"Failed to record image in db [%1]: %2"_s
.arg(key, insertQuery.lastError().text())); .arg(key, insertQuery.lastError().text()));
else {
QMutexLocker lock(&m_hotKeysMutex);
m_hotImageKeys.insert(key);
}
} }
return QFileInfo(filePath); return QFileInfo(filePath);
@@ -198,8 +239,7 @@ QString Manager::getSetting(SettingsType key, const std::function<QString()>& co
if (db.isOpen()) { if (db.isOpen()) {
QSqlQuery query(db); QSqlQuery query(db);
query.prepare(QStringLiteral( query.prepare(u"SELECT value FROM settings_cache WHERE key = :key"_s);
"SELECT value FROM settings_cache WHERE key = :key"));
query.bindValue(u":key"_s, keyStr); query.bindValue(u":key"_s, keyStr);
if (query.exec() && query.next()) { if (query.exec() && query.next()) {
@@ -218,9 +258,9 @@ QString Manager::getSetting(SettingsType key, const std::function<QString()>& co
if (db.isOpen() && !value.isNull()) { if (db.isOpen() && !value.isNull()) {
QSqlQuery insertQuery(db); QSqlQuery insertQuery(db);
insertQuery.prepare(QStringLiteral( insertQuery.prepare(
"INSERT OR REPLACE INTO settings_cache (key, value) " u"INSERT OR REPLACE INTO settings_cache (key, value) "
"VALUES (:key, :value)")); "VALUES (:key, :value)"_s);
insertQuery.bindValue(u":key"_s, keyStr); insertQuery.bindValue(u":key"_s, keyStr);
insertQuery.bindValue(u":value"_s, value); insertQuery.bindValue(u":value"_s, value);
if (!insertQuery.exec()) if (!insertQuery.exec())
@@ -240,8 +280,7 @@ void Manager::storeSetting(SettingsType key, const QString& value) {
if (db.isOpen()) { if (db.isOpen()) {
if (value.isNull()) { if (value.isNull()) {
QSqlQuery deleteQuery(db); QSqlQuery deleteQuery(db);
deleteQuery.prepare(QStringLiteral( deleteQuery.prepare(u"DELETE FROM settings_cache WHERE key = :key"_s);
"DELETE FROM settings_cache WHERE key = :key"));
deleteQuery.bindValue(u":key"_s, keyStr); deleteQuery.bindValue(u":key"_s, keyStr);
if (!deleteQuery.exec()) if (!deleteQuery.exec())
WR_WARN(u"Failed to delete setting [%1]: %2"_s WR_WARN(u"Failed to delete setting [%1]: %2"_s
@@ -250,9 +289,9 @@ void Manager::storeSetting(SettingsType key, const QString& value) {
WR_DEBUG(u"Setting deleted [%1]"_s.arg(keyStr)); WR_DEBUG(u"Setting deleted [%1]"_s.arg(keyStr));
} else { } else {
QSqlQuery insertQuery(db); QSqlQuery insertQuery(db);
insertQuery.prepare(QStringLiteral( insertQuery.prepare(
"INSERT OR REPLACE INTO settings_cache (key, value) " u"INSERT OR REPLACE INTO settings_cache (key, value) "
"VALUES (:key, :value)")); "VALUES (:key, :value)"_s);
insertQuery.bindValue(u":key"_s, keyStr); insertQuery.bindValue(u":key"_s, keyStr);
insertQuery.bindValue(u":value"_s, value); insertQuery.bindValue(u":value"_s, value);
if (!insertQuery.exec()) if (!insertQuery.exec())
@@ -321,24 +360,145 @@ QSqlDatabase Manager::_db() const {
void Manager::_setupTables(QSqlDatabase& db) const { void Manager::_setupTables(QSqlDatabase& db) const {
QSqlQuery q(db); QSqlQuery q(db);
q.exec(QStringLiteral( q.exec(
"CREATE TABLE IF NOT EXISTS color_cache (" u"CREATE TABLE IF NOT EXISTS color_cache ("
" key TEXT PRIMARY KEY NOT NULL," " key TEXT PRIMARY KEY NOT NULL,"
" r INTEGER NOT NULL," " r INTEGER NOT NULL,"
" g INTEGER NOT NULL," " g INTEGER NOT NULL,"
" b INTEGER NOT NULL," " b INTEGER NOT NULL,"
" a INTEGER NOT NULL" " a INTEGER NOT NULL,"
")")); " last_accessed TEXT"
q.exec(QStringLiteral( ")"_s);
"CREATE TABLE IF NOT EXISTS image_cache (" q.exec(
" key TEXT PRIMARY KEY NOT NULL," u"CREATE TABLE IF NOT EXISTS image_cache ("
" file_name TEXT NOT NULL" " key TEXT PRIMARY KEY NOT NULL,"
")")); " file_name TEXT NOT NULL,"
q.exec(QStringLiteral( " last_accessed TEXT"
"CREATE TABLE IF NOT EXISTS settings_cache (" ")"_s);
q.exec(
u"CREATE TABLE IF NOT EXISTS settings_cache ("
" key TEXT PRIMARY KEY NOT NULL," " key TEXT PRIMARY KEY NOT NULL,"
" value TEXT NOT NULL" " value TEXT NOT NULL"
");")); ");"_s);
// Migrate existing databases that predate the last_accessed column.
q.exec(u"ALTER TABLE color_cache ADD COLUMN last_accessed TEXT"_s);
q.exec(u"ALTER TABLE image_cache ADD COLUMN last_accessed TEXT"_s);
}
void Manager::_runCleanup() {
WR_DEBUG(u"Cache cleanup started (maxEntries=%1)"_s.arg(m_maxEntries));
QSqlDatabase db = _db();
if (!db.isOpen())
return;
// Evict image_cache rows whose backing file no longer exists
{
QSqlQuery sel(db);
if (sel.exec(u"SELECT key, file_name FROM image_cache"_s)) {
struct Stale {
QString key, fileName;
};
QList<Stale> stale;
while (sel.next()) {
const QString k = sel.value(0).toString();
const QString file = sel.value(1).toString();
if (!QFileInfo::exists(m_cacheDir.filePath(file)))
stale.push_back({k, file});
}
int evicted = 0;
for (const auto& s : std::as_const(stale)) {
{
QMutexLocker lk(&m_hotKeysMutex);
if (m_hotImageKeys.contains(s.key))
continue;
}
QSqlQuery del(db);
del.prepare(u"DELETE FROM image_cache WHERE key = :key"_s);
del.bindValue(u":key"_s, s.key);
if (del.exec())
++evicted;
}
if (evicted)
WR_INFO(u"Cleanup evicted %1 stale image cache row(s)"_s.arg(evicted));
}
}
// Trim image_cache to m_maxEntries (oldest last_accessed first)
{
QSqlQuery countQ(db);
if (countQ.exec(u"SELECT COUNT(*) FROM image_cache"_s) && countQ.next()) {
int excess = countQ.value(0).toInt() - m_maxEntries;
if (excess > 0) {
QSqlQuery sel(db);
sel.exec(u"SELECT key, file_name FROM image_cache ORDER BY last_accessed ASC"_s);
QList<QPair<QString, QString>> toDelete;
while (sel.next() && excess > 0) {
const QString k = sel.value(0).toString();
QMutexLocker lk(&m_hotKeysMutex);
if (!m_hotImageKeys.contains(k)) {
toDelete.push_back({k, sel.value(1).toString()});
--excess;
}
}
int removed = 0;
for (const auto& [k, fileName] : std::as_const(toDelete)) {
{
QMutexLocker lk(&m_hotKeysMutex);
if (m_hotImageKeys.contains(k))
continue;
}
QFile::remove(m_cacheDir.filePath(fileName));
QSqlQuery del(db);
del.prepare(u"DELETE FROM image_cache WHERE key = :key"_s);
del.bindValue(u":key"_s, k);
if (del.exec())
++removed;
}
if (removed)
WR_INFO(u"Cleanup trimmed %1 image cache entry(ies)"_s.arg(removed));
}
}
}
// Trim color_cache to m_maxEntries (oldest last_accessed first)
{
QSqlQuery countQ(db);
if (countQ.exec(u"SELECT COUNT(*) FROM color_cache"_s) && countQ.next()) {
int excess = countQ.value(0).toInt() - m_maxEntries;
if (excess > 0) {
QSqlQuery sel(db);
sel.exec(u"SELECT key FROM color_cache ORDER BY last_accessed ASC"_s);
QStringList toDelete;
while (sel.next() && excess > 0) {
const QString k = sel.value(0).toString();
QMutexLocker lk(&m_hotKeysMutex);
if (!m_hotColorKeys.contains(k)) {
toDelete << k;
--excess;
}
}
int removed = 0;
for (const QString& k : std::as_const(toDelete)) {
{
QMutexLocker lk(&m_hotKeysMutex);
if (m_hotColorKeys.contains(k))
continue;
}
QSqlQuery del(db);
del.prepare(u"DELETE FROM color_cache WHERE key = :key"_s);
del.bindValue(u":key"_s, k);
if (del.exec())
++removed;
}
if (removed)
WR_INFO(u"Cleanup trimmed %1 color cache entry(ies)"_s.arg(removed));
}
}
}
WR_DEBUG(u"Cache cleanup complete"_s);
} }
} // namespace WallReel::Core::Cache } // namespace WallReel::Core::Cache
+12 -1
View File
@@ -3,6 +3,7 @@
#include <QDir> #include <QDir>
#include <QFileInfo> #include <QFileInfo>
#include <QFuture>
#include <QMutex> #include <QMutex>
#include <QSet> #include <QSet>
#include <QtSql> #include <QtSql>
@@ -15,10 +16,12 @@ class Manager {
public: public:
static QString cacheKey(const QFileInfo& fileInfo, const QSize& imageSize); static QString cacheKey(const QFileInfo& fileInfo, const QSize& imageSize);
Manager(const QDir& cacheDir); Manager(const QDir& cacheDir, int maxEntries = 1000);
~Manager(); ~Manager();
void evictOldEntries();
void clearCache(Type type = Type::Image | Type::Color); void clearCache(Type type = Type::Image | Type::Color);
QColor getColor(const QString& key, const std::function<QColor()>& computeFunc = nullptr); QColor getColor(const QString& key, const std::function<QColor()>& computeFunc = nullptr);
@@ -31,14 +34,22 @@ class Manager {
private: private:
QDir m_cacheDir; QDir m_cacheDir;
int m_maxEntries;
QString m_dbPath; QString m_dbPath;
QString m_connectionPrefix; QString m_connectionPrefix;
mutable QMutex m_connectionsMutex; mutable QMutex m_connectionsMutex;
mutable QSet<QString> m_connectionNames; mutable QSet<QString> m_connectionNames;
mutable QMutex m_hotKeysMutex;
mutable QSet<QString> m_hotColorKeys;
mutable QSet<QString> m_hotImageKeys;
QFuture<void> m_cleanupFuture;
QSqlDatabase _db() const; QSqlDatabase _db() const;
void _setupTables(QSqlDatabase& db) const; void _setupTables(QSqlDatabase& db) const;
void _runCleanup();
}; };
} // namespace WallReel::Core::Cache } // namespace WallReel::Core::Cache
+1
View File
@@ -47,6 +47,7 @@
// //
// cache.saveSortMethod boolean true Whether to persist the sort type and order // cache.saveSortMethod boolean true Whether to persist the sort type and order
// cache.savePalette bool true Whether to persist the selected palette // cache.savePalette bool true Whether to persist the selected palette
// cache.maxImageEntries number 1000 Maximum number of entries in the image cache (older entries will be evicted)
namespace WallReel::Core::Config { namespace WallReel::Core::Config {
+6
View File
@@ -321,6 +321,12 @@ void Manager::_loadCacheConfig(const QJsonObject& root) {
m_cacheConfig.savePalette = val.toBool(); m_cacheConfig.savePalette = val.toBool();
} }
} }
if (config.contains("maxImageEntries")) {
const auto& val = config["maxImageEntries"];
if (val.isDouble() && val.toDouble() > 0) {
m_cacheConfig.maxImageEntries = val.toInt();
}
}
} }
void Manager::_loadWallpapers() { void Manager::_loadWallpapers() {
+3 -2
View File
@@ -24,12 +24,13 @@ WallReel::Core::Image::Data::Data(const QString& path, const QSize& targetSize,
: m_cacheMgr(cacheMgr), m_file(path), m_targetSize(targetSize) { : m_cacheMgr(cacheMgr), m_file(path), m_targetSize(targetSize) {
m_id = cacheMgr.cacheKey(m_file, m_targetSize); m_id = cacheMgr.cacheKey(m_file, m_targetSize);
m_cachedFile = cacheMgr.getImage(m_id, [this]() { return computeImage(); }); m_cachedFile = cacheMgr.getImage(m_id, [this]() { return computeImage(); });
m_dominantColor = cacheMgr.getColor(m_id, [this]() { return computeDominantColor(loadImage()); }); m_dominantColor = cacheMgr.getColor(m_id, [this]() { return computeDominantColor(loadImageFromCache()); });
m_isValid = m_cachedFile.isFile() && m_dominantColor.isValid(); m_isValid = m_cachedFile.isFile() && m_dominantColor.isValid();
} }
QImage WallReel::Core::Image::Data::loadImage() const { QImage WallReel::Core::Image::Data::loadImageFromCache() const {
QImageReader reader(m_cachedFile.absoluteFilePath()); QImageReader reader(m_cachedFile.absoluteFilePath());
if (!reader.canRead()) { if (!reader.canRead()) {
WR_WARN("Cannot read cached image: " + m_cachedFile.absoluteFilePath()); WR_WARN("Cannot read cached image: " + m_cachedFile.absoluteFilePath());
return QImage(); return QImage();
+1 -2
View File
@@ -58,6 +58,7 @@ class Data {
QImage computeImage() const; QImage computeImage() const;
QColor computeDominantColor(const QImage& image) const; QColor computeDominantColor(const QImage& image) const;
QImage loadImageFromCache() const;
Data(const QString& path, const QSize& size, Cache::Manager& cacheMgr); Data(const QString& path, const QSize& size, Cache::Manager& cacheMgr);
@@ -89,8 +90,6 @@ class Data {
const QFileInfo& getFileInfo() const { return m_file; } const QFileInfo& getFileInfo() const { return m_file; }
QImage loadImage() const;
const QColor& getDominantColor() const { return m_dominantColor; } const QColor& getDominantColor() const { return m_dominantColor; }
std::optional<QString> getCachedColor(const QString& paletteName) const { std::optional<QString> getCachedColor(const QString& paletteName) const {
+10 -6
View File
@@ -18,18 +18,21 @@ class Bootstrap {
public: public:
Bootstrap(const AppOptions& options) { Bootstrap(const AppOptions& options) {
cacheMgr = new Cache::Manager(Utils::getCacheDir());
if (options.clearCache) {
cacheMgr->clearCache();
return;
}
configMgr = new Config::Manager( configMgr = new Config::Manager(
Utils::getConfigDir(), Utils::getConfigDir(),
Utils::getPicturesDir(), Utils::getPicturesDir(),
options.appendDirs, options.appendDirs,
options.configPath); options.configPath);
cacheMgr = new Cache::Manager(
Utils::getCacheDir(),
configMgr->getCacheConfig().maxImageEntries);
if (options.clearCache) {
cacheMgr->clearCache();
return;
}
imageMgr = new Image::Manager( imageMgr = new Image::Manager(
*cacheMgr, *cacheMgr,
configMgr->getFocusImageSize()); configMgr->getFocusImageSize());
@@ -47,6 +50,7 @@ class Bootstrap {
} }
void start() { void start() {
cacheMgr->evictOldEntries();
configMgr->captureState(); configMgr->captureState();
imageMgr->loadAndProcess(configMgr->getWallpapers()); imageMgr->loadAndProcess(configMgr->getWallpapers());
} }
+2 -4
View File
@@ -25,7 +25,7 @@ int main(int argc, char* argv[]) {
QApplication a(argc, argv); QApplication a(argc, argv);
a.setApplicationName(APP_NAME); a.setApplicationName(APP_NAME);
a.setApplicationVersion(APP_VERSION); a.setApplicationVersion(APP_VERSION);
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) #if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
using namespace Qt::StringLiterals; using namespace Qt::StringLiterals;
a.setWindowIcon(QIcon(u":/%1.svg"_s.arg(APP_NAME))); a.setWindowIcon(QIcon(u":/%1.svg"_s.arg(APP_NAME)));
#else #else
@@ -66,11 +66,9 @@ int main(int argc, char* argv[]) {
[]() { QCoreApplication::exit(-1); }, []() { QCoreApplication::exit(-1); },
Qt::QueuedConnection); Qt::QueuedConnection);
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) #if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
using namespace Qt::StringLiterals; using namespace Qt::StringLiterals;
engine.loadFromModule(UIMODULE_URI, u"Main"_s); engine.loadFromModule(UIMODULE_URI, u"Main"_s);
#elif QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
engine.loadFromModule(UIMODULE_URI, u"Main"_qs);
#else #else
engine.addImportPath(u"qrc:/"_qs); engine.addImportPath(u"qrc:/"_qs);
engine.load(QUrl(u"qrc:/WallReel/UI/Main.qml"_qs)); engine.load(QUrl(u"qrc:/WallReel/UI/Main.qml"_qs));
+5
View File
@@ -201,6 +201,11 @@
"type": "boolean", "type": "boolean",
"default": true, "default": true,
"description": "Whether to persist the selected palette" "description": "Whether to persist the selected palette"
},
"maxImageEntries": {
"type": "integer",
"default": 1000,
"description": "Maximum number of entries in the image cache (older entries will be evicted)"
} }
} }
} }