From 5df0b53df05a5c13fdfc722f5bcaebaf1dc46ca5 Mon Sep 17 00:00:00 2001 From: Uyanide Date: Sun, 1 Mar 2026 06:28:08 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20implement=20image=20cache?= =?UTF-8?q?=20management=20with=20max=20entries=20limit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + README.md | 12 +- WallReel/Core/Cache/manager.cpp | 254 ++++++++++++++++++++++----- WallReel/Core/Cache/manager.hpp | 13 +- WallReel/Core/Config/data.hpp | 1 + WallReel/Core/Config/manager.cpp | 6 + WallReel/Core/Image/data.cpp | 5 +- WallReel/Core/Image/data.hpp | 3 +- WallReel/Core/Provider/bootstrap.hpp | 16 +- WallReel/main.cpp | 6 +- config.schema.json | 5 + 11 files changed, 256 insertions(+), 67 deletions(-) diff --git a/.gitignore b/.gitignore index 78eab52..a24cca6 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,5 @@ CMakeLists.txt.user* .uic/ /build*/ .cache + +.vscode diff --git a/README.md b/README.md index 536039f..491a9d1 100644 --- a/README.md +++ b/README.md @@ -126,10 +126,11 @@ Controls the layout and dimensions of the application window and image items. Controls what UI state is persisted between sessions. -| Property | Type | Default | Description | -| :--------------- | :------ | :------ | :------------------------------------------ | -| `saveSortMethod` | Boolean | `true` | Whether to persist the sort type and order. | -| `savePalette` | Boolean | `true` | Whether to persist the selected palette. | +| Property | Type | Default | Description | +| :---------------- | :------ | :------ | :---------------------------------------------------------------------------- | +| `saveSortMethod` | Boolean | `true` | Whether to persist the sort type and order. | +| `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": { "saveSortMethod": true, - "savePalette": true + "savePalette": true, + "maxImageEntries": 300 } } ``` diff --git a/WallReel/Core/Cache/manager.cpp b/WallReel/Core/Cache/manager.cpp index 5f93046..9c569cb 100644 --- a/WallReel/Core/Cache/manager.cpp +++ b/WallReel/Core/Cache/manager.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "logger.hpp" @@ -26,20 +27,41 @@ static QLatin1StringView settingKey(SettingsType type) { } 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( QCryptographicHash::hash(raw.toUtf8(), QCryptographicHash::Sha256).toHex()); } -Manager::Manager(const QDir& cacheDir) - : 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())) { +Manager::Manager(const QDir& cacheDir, int maxEntries) + : 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)); // Open a connection on the constructing thread so the schema is // guaranteed to exist before any worker thread first calls _db(). _db(); } +void Manager::evictOldEntries() { + if (m_maxEntries > 0) + m_cleanupFuture = QtConcurrent::run([this] { _runCleanup(); }); +} + 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 names; { QMutexLocker lock(&m_connectionsMutex); @@ -59,23 +81,23 @@ void Manager::clearCache(Type type) { if ((type & Type::Image) != Type::None) { int removed = 0; 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()) { QFile::remove(m_cacheDir.filePath(selectQuery.value(0).toString())); ++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)); } 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); } 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); } } @@ -84,17 +106,25 @@ QColor Manager::getColor(const QString& key, const std::function& comp QSqlDatabase db = _db(); if (db.isOpen()) { QSqlQuery query(db); - query.prepare(QStringLiteral( - "SELECT r, g, b, a FROM color_cache WHERE key = :key")); + query.prepare(u"SELECT r, g, b, a FROM color_cache WHERE key = :key"_s); query.bindValue(u":key"_s, key); if (query.exec() && query.next()) { WR_DEBUG(u"Color cache hit [%1]"_s.arg(key)); - return QColor( + QColor result( query.value(0).toInt(), query.value(1).toInt(), query.value(2).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& comp if (db.isOpen()) { QSqlQuery insertQuery(db); - insertQuery.prepare(QStringLiteral( - "INSERT OR REPLACE INTO color_cache (key, r, g, b, a) " - "VALUES (:key, :r, :g, :b, :a)")); + insertQuery.prepare( + u"INSERT OR REPLACE INTO color_cache (key, r, g, b, a, last_accessed) " + "VALUES (:key, :r, :g, :b, :a, CURRENT_TIMESTAMP)"_s); insertQuery.bindValue(u":key"_s, key); insertQuery.bindValue(u":r"_s, color.red()); insertQuery.bindValue(u":g"_s, color.green()); @@ -135,8 +165,7 @@ QFileInfo Manager::getImage(const QString& key, const std::function& c QSqlDatabase db = _db(); if (db.isOpen()) { QSqlQuery query(db); - query.prepare(QStringLiteral( - "SELECT file_name FROM image_cache WHERE key = :key")); + query.prepare(u"SELECT file_name FROM image_cache WHERE key = :key"_s); query.bindValue(u":key"_s, key); if (query.exec() && query.next()) { @@ -144,13 +173,21 @@ QFileInfo Manager::getImage(const QString& key, const std::function& c if (cached.exists()) { WR_DEBUG(u"Image cache hit [%1] -> %2"_s .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; } // File was deleted externally — evict the stale DB record. WR_WARN(u"Image cache stale, file missing [%1], evicting"_s.arg(key)); 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.exec(); } @@ -168,10 +205,10 @@ QFileInfo Manager::getImage(const QString& key, const std::function& c return QFileInfo{}; } - const QString fileName = key + u".png"_s; + const QString fileName = key + u".jpg"_s; 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)); return QFileInfo{}; } @@ -179,14 +216,18 @@ QFileInfo Manager::getImage(const QString& key, const std::function& c if (db.isOpen()) { QSqlQuery insertQuery(db); - insertQuery.prepare(QStringLiteral( - "INSERT OR REPLACE INTO image_cache (key, file_name) " - "VALUES (:key, :file_name)")); + insertQuery.prepare( + u"INSERT OR REPLACE INTO image_cache (key, file_name, last_accessed) " + "VALUES (:key, :file_name, CURRENT_TIMESTAMP)"_s); insertQuery.bindValue(u":key"_s, key); insertQuery.bindValue(u":file_name"_s, fileName); if (!insertQuery.exec()) WR_WARN(u"Failed to record image in db [%1]: %2"_s .arg(key, insertQuery.lastError().text())); + else { + QMutexLocker lock(&m_hotKeysMutex); + m_hotImageKeys.insert(key); + } } return QFileInfo(filePath); @@ -198,8 +239,7 @@ QString Manager::getSetting(SettingsType key, const std::function& co if (db.isOpen()) { QSqlQuery query(db); - query.prepare(QStringLiteral( - "SELECT value FROM settings_cache WHERE key = :key")); + query.prepare(u"SELECT value FROM settings_cache WHERE key = :key"_s); query.bindValue(u":key"_s, keyStr); if (query.exec() && query.next()) { @@ -218,9 +258,9 @@ QString Manager::getSetting(SettingsType key, const std::function& co if (db.isOpen() && !value.isNull()) { QSqlQuery insertQuery(db); - insertQuery.prepare(QStringLiteral( - "INSERT OR REPLACE INTO settings_cache (key, value) " - "VALUES (:key, :value)")); + insertQuery.prepare( + u"INSERT OR REPLACE INTO settings_cache (key, value) " + "VALUES (:key, :value)"_s); insertQuery.bindValue(u":key"_s, keyStr); insertQuery.bindValue(u":value"_s, value); if (!insertQuery.exec()) @@ -240,8 +280,7 @@ void Manager::storeSetting(SettingsType key, const QString& value) { if (db.isOpen()) { if (value.isNull()) { QSqlQuery deleteQuery(db); - deleteQuery.prepare(QStringLiteral( - "DELETE FROM settings_cache WHERE key = :key")); + deleteQuery.prepare(u"DELETE FROM settings_cache WHERE key = :key"_s); deleteQuery.bindValue(u":key"_s, keyStr); if (!deleteQuery.exec()) 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)); } else { QSqlQuery insertQuery(db); - insertQuery.prepare(QStringLiteral( - "INSERT OR REPLACE INTO settings_cache (key, value) " - "VALUES (:key, :value)")); + insertQuery.prepare( + u"INSERT OR REPLACE INTO settings_cache (key, value) " + "VALUES (:key, :value)"_s); insertQuery.bindValue(u":key"_s, keyStr); insertQuery.bindValue(u":value"_s, value); if (!insertQuery.exec()) @@ -321,24 +360,145 @@ QSqlDatabase Manager::_db() const { void Manager::_setupTables(QSqlDatabase& db) const { QSqlQuery q(db); - q.exec(QStringLiteral( - "CREATE TABLE IF NOT EXISTS color_cache (" - " key TEXT PRIMARY KEY NOT NULL," - " r INTEGER NOT NULL," - " g INTEGER NOT NULL," - " b INTEGER NOT NULL," - " a INTEGER NOT NULL" - ")")); - q.exec(QStringLiteral( - "CREATE TABLE IF NOT EXISTS image_cache (" - " key TEXT PRIMARY KEY NOT NULL," - " file_name TEXT NOT NULL" - ")")); - q.exec(QStringLiteral( - "CREATE TABLE IF NOT EXISTS settings_cache (" + q.exec( + u"CREATE TABLE IF NOT EXISTS color_cache (" + " key TEXT PRIMARY KEY NOT NULL," + " r INTEGER NOT NULL," + " g INTEGER NOT NULL," + " b INTEGER NOT NULL," + " a INTEGER NOT NULL," + " last_accessed TEXT" + ")"_s); + q.exec( + u"CREATE TABLE IF NOT EXISTS image_cache (" + " key TEXT PRIMARY KEY NOT NULL," + " file_name TEXT NOT NULL," + " last_accessed TEXT" + ")"_s); + q.exec( + u"CREATE TABLE IF NOT EXISTS settings_cache (" " key TEXT PRIMARY KEY 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; + 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> 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 diff --git a/WallReel/Core/Cache/manager.hpp b/WallReel/Core/Cache/manager.hpp index f8295fa..8b23d60 100644 --- a/WallReel/Core/Cache/manager.hpp +++ b/WallReel/Core/Cache/manager.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -15,10 +16,12 @@ class Manager { public: static QString cacheKey(const QFileInfo& fileInfo, const QSize& imageSize); - Manager(const QDir& cacheDir); + Manager(const QDir& cacheDir, int maxEntries = 1000); ~Manager(); + void evictOldEntries(); + void clearCache(Type type = Type::Image | Type::Color); QColor getColor(const QString& key, const std::function& computeFunc = nullptr); @@ -31,14 +34,22 @@ class Manager { private: QDir m_cacheDir; + int m_maxEntries; QString m_dbPath; QString m_connectionPrefix; mutable QMutex m_connectionsMutex; mutable QSet m_connectionNames; + mutable QMutex m_hotKeysMutex; + mutable QSet m_hotColorKeys; + mutable QSet m_hotImageKeys; + + QFuture m_cleanupFuture; + QSqlDatabase _db() const; void _setupTables(QSqlDatabase& db) const; + void _runCleanup(); }; } // namespace WallReel::Core::Cache diff --git a/WallReel/Core/Config/data.hpp b/WallReel/Core/Config/data.hpp index 08838a7..018e7fe 100644 --- a/WallReel/Core/Config/data.hpp +++ b/WallReel/Core/Config/data.hpp @@ -47,6 +47,7 @@ // // cache.saveSortMethod boolean true Whether to persist the sort type and order // 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 { diff --git a/WallReel/Core/Config/manager.cpp b/WallReel/Core/Config/manager.cpp index 5087643..be760a7 100644 --- a/WallReel/Core/Config/manager.cpp +++ b/WallReel/Core/Config/manager.cpp @@ -321,6 +321,12 @@ void Manager::_loadCacheConfig(const QJsonObject& root) { 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() { diff --git a/WallReel/Core/Image/data.cpp b/WallReel/Core/Image/data.cpp index 3600cd4..0b35b39 100644 --- a/WallReel/Core/Image/data.cpp +++ b/WallReel/Core/Image/data.cpp @@ -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_id = cacheMgr.cacheKey(m_file, m_targetSize); 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(); } -QImage WallReel::Core::Image::Data::loadImage() const { +QImage WallReel::Core::Image::Data::loadImageFromCache() const { QImageReader reader(m_cachedFile.absoluteFilePath()); + if (!reader.canRead()) { WR_WARN("Cannot read cached image: " + m_cachedFile.absoluteFilePath()); return QImage(); diff --git a/WallReel/Core/Image/data.hpp b/WallReel/Core/Image/data.hpp index d45a3d2..abd69d5 100644 --- a/WallReel/Core/Image/data.hpp +++ b/WallReel/Core/Image/data.hpp @@ -58,6 +58,7 @@ class Data { QImage computeImage() const; QColor computeDominantColor(const QImage& image) const; + QImage loadImageFromCache() const; Data(const QString& path, const QSize& size, Cache::Manager& cacheMgr); @@ -89,8 +90,6 @@ class Data { const QFileInfo& getFileInfo() const { return m_file; } - QImage loadImage() const; - const QColor& getDominantColor() const { return m_dominantColor; } std::optional getCachedColor(const QString& paletteName) const { diff --git a/WallReel/Core/Provider/bootstrap.hpp b/WallReel/Core/Provider/bootstrap.hpp index 4c1d4c8..ea9cc83 100644 --- a/WallReel/Core/Provider/bootstrap.hpp +++ b/WallReel/Core/Provider/bootstrap.hpp @@ -18,18 +18,21 @@ class Bootstrap { public: Bootstrap(const AppOptions& options) { - cacheMgr = new Cache::Manager(Utils::getCacheDir()); - - if (options.clearCache) { - cacheMgr->clearCache(); - return; - } configMgr = new Config::Manager( Utils::getConfigDir(), Utils::getPicturesDir(), options.appendDirs, options.configPath); + cacheMgr = new Cache::Manager( + Utils::getCacheDir(), + configMgr->getCacheConfig().maxImageEntries); + + if (options.clearCache) { + cacheMgr->clearCache(); + return; + } + imageMgr = new Image::Manager( *cacheMgr, configMgr->getFocusImageSize()); @@ -47,6 +50,7 @@ class Bootstrap { } void start() { + cacheMgr->evictOldEntries(); configMgr->captureState(); imageMgr->loadAndProcess(configMgr->getWallpapers()); } diff --git a/WallReel/main.cpp b/WallReel/main.cpp index 56a97e1..e92e710 100644 --- a/WallReel/main.cpp +++ b/WallReel/main.cpp @@ -25,7 +25,7 @@ int main(int argc, char* argv[]) { QApplication a(argc, argv); a.setApplicationName(APP_NAME); 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; a.setWindowIcon(QIcon(u":/%1.svg"_s.arg(APP_NAME))); #else @@ -66,11 +66,9 @@ int main(int argc, char* argv[]) { []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); -#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) +#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) using namespace Qt::StringLiterals; engine.loadFromModule(UIMODULE_URI, u"Main"_s); -#elif QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) - engine.loadFromModule(UIMODULE_URI, u"Main"_qs); #else engine.addImportPath(u"qrc:/"_qs); engine.load(QUrl(u"qrc:/WallReel/UI/Main.qml"_qs)); diff --git a/config.schema.json b/config.schema.json index 1fad2d1..4c9af98 100644 --- a/config.schema.json +++ b/config.schema.json @@ -201,6 +201,11 @@ "type": "boolean", "default": true, "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)" } } }