✨ feat: implement image cache management with max entries limit
This commit is contained in:
@@ -81,3 +81,5 @@ CMakeLists.txt.user*
|
||||
.uic/
|
||||
/build*/
|
||||
.cache
|
||||
|
||||
.vscode
|
||||
|
||||
@@ -127,9 +127,10 @@ 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. |
|
||||
| `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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
+202
-42
@@ -7,6 +7,7 @@
|
||||
#include <QSqlError>
|
||||
#include <QSqlQuery>
|
||||
#include <QThread>
|
||||
#include <QtConcurrent>
|
||||
|
||||
#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<QString> 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<QColor()>& 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<QColor()>& 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<QImage()>& 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<QImage()>& 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<QImage()>& 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<QImage()>& 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<QString()>& 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<QString()>& 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 ("
|
||||
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"
|
||||
")"));
|
||||
q.exec(QStringLiteral(
|
||||
"CREATE TABLE IF NOT EXISTS image_cache ("
|
||||
" 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"
|
||||
")"));
|
||||
q.exec(QStringLiteral(
|
||||
"CREATE TABLE IF NOT EXISTS settings_cache ("
|
||||
" 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> 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
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QFuture>
|
||||
#include <QMutex>
|
||||
#include <QSet>
|
||||
#include <QtSql>
|
||||
@@ -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<QColor()>& 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<QString> m_connectionNames;
|
||||
|
||||
mutable QMutex m_hotKeysMutex;
|
||||
mutable QSet<QString> m_hotColorKeys;
|
||||
mutable QSet<QString> m_hotImageKeys;
|
||||
|
||||
QFuture<void> m_cleanupFuture;
|
||||
|
||||
QSqlDatabase _db() const;
|
||||
void _setupTables(QSqlDatabase& db) const;
|
||||
void _runCleanup();
|
||||
};
|
||||
|
||||
} // namespace WallReel::Core::Cache
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<QString> getCachedColor(const QString& paletteName) const {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
+2
-4
@@ -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));
|
||||
|
||||
@@ -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)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user