feat: add wallpaper transitions, store crop in database per-wallpaper, move CUDA context creation to async thread

This commit is contained in:
2026-10-29 17:44:19 +01:00
parent 906b15b092
commit 3792e9d73f
26 changed files with 2338 additions and 210 deletions
+109 -17
View File
@@ -8,6 +8,8 @@
#include <qfuturewatcher.h>
#include <qimage.h>
#include <qimagereader.h>
#include <qjsondocument.h>
#include <qjsonobject.h>
#include <qlogging.h>
#include <qqmlengine.h>
#include <qsqldatabase.h>
@@ -343,8 +345,8 @@ void WallpaperDb::setCacheSize(const QSize& cacheSize) {
if (db.isOpen()) {
QSqlQuery query(db);
query.prepare(
"UPDATE wallpapers SET cache = NULL, scheme_thumb = NULL"
" WHERE path = ?");
"UPDATE wallpapers SET cache = NULL, scheme_thumb = NULL,"
" poster = NULL WHERE path = ?");
query.addBindValue(entry->path());
query.exec();
}
@@ -413,7 +415,8 @@ void WallpaperDb::openDb() {
query.exec(
"CREATE TABLE IF NOT EXISTS wallpapers("
"path TEXT PRIMARY KEY, kind TEXT, codec TEXT, hardware INTEGER,"
" size INTEGER, mtime INTEGER, cache BLOB, scheme_thumb BLOB)");
" size INTEGER, mtime INTEGER, cache BLOB, scheme_thumb BLOB,"
" poster BLOB, crop TEXT)");
query.exec("SELECT cache FROM wallpapers LIMIT 1");
if (query.lastError().isValid()) {
@@ -421,9 +424,12 @@ void WallpaperDb::openDb() {
query.exec(
"CREATE TABLE wallpapers("
"path TEXT PRIMARY KEY, kind TEXT, codec TEXT, hardware INTEGER,"
" size INTEGER, mtime INTEGER, cache BLOB, scheme_thumb BLOB)");
" size INTEGER, mtime INTEGER, cache BLOB, scheme_thumb BLOB,"
" poster BLOB, crop TEXT)");
} else {
query.exec("ALTER TABLE wallpapers ADD COLUMN scheme_thumb BLOB");
query.exec("ALTER TABLE wallpapers ADD COLUMN poster BLOB");
query.exec("ALTER TABLE wallpapers ADD COLUMN crop TEXT");
}
registerProvider();
@@ -538,6 +544,7 @@ void WallpaperDb::applyScan(const ScanResult& result) {
}
delete it.value();
m_crops.remove(it.key());
it = m_byPath.erase(it);
} else {
++it;
@@ -575,24 +582,28 @@ void WallpaperDb::applyScan(const ScanResult& result) {
if (db.isOpen()) {
QSqlQuery query(db);
query.prepare(
"SELECT cache, scheme_thumb, size, mtime FROM wallpapers"
" WHERE path = ?");
"SELECT cache, scheme_thumb, poster, size, mtime, crop"
" FROM wallpapers WHERE path = ?");
query.addBindValue(path);
query.exec();
if (query.next()) {
const auto cache = query.value(0).toByteArray();
const auto scheme = query.value(1).toByteArray();
const auto storedSize = query.value(2).toLongLong();
const auto storedMtime = query.value(3).toLongLong();
const auto poster = query.value(2).toByteArray();
const auto storedSize = query.value(3).toLongLong();
const auto storedMtime = query.value(4).toLongLong();
loadCrop(path, query.value(5).toString());
const bool posterOk = !isVideo || !poster.isEmpty();
if (storedSize == size && storedMtime == mtime &&
!cache.isEmpty()) {
reused = true;
entry->setThumbnailPath(thumbnailUrl(path));
if (scheme.isEmpty()) {
queueMetadata(entry, isVideo, cache);
if (scheme.isEmpty() || !posterOk) {
queueMetadata(entry, isVideo, cache, poster);
}
}
}
@@ -602,9 +613,13 @@ void WallpaperDb::applyScan(const ScanResult& result) {
if (db.isOpen()) {
QSqlQuery query(db);
query.prepare(
"INSERT OR REPLACE INTO wallpapers"
"INSERT INTO wallpapers"
" (path, kind, hardware, size, mtime, cache)"
" VALUES (?, ?, ?, ?, ?, NULL)");
" VALUES (?, ?, ?, ?, ?, NULL)"
" ON CONFLICT(path) DO UPDATE SET kind = excluded.kind,"
" hardware = excluded.hardware, size = excluded.size,"
" mtime = excluded.mtime, cache = NULL,"
" scheme_thumb = NULL, poster = NULL");
query.addBindValue(path);
query.addBindValue(isVideo ? "video" : "image");
query.addBindValue(
@@ -626,7 +641,10 @@ void WallpaperDb::applyScan(const ScanResult& result) {
}
void WallpaperDb::queueMetadata(
WallpaperEntry* entry, bool isVideo, const QByteArray& existingCache) {
WallpaperEntry* entry,
bool isVideo,
const QByteArray& existingCache,
const QByteArray& existingPoster) {
qCInfo(
dbLog,
"WallpaperDb: queued cache job for %s (%s%s)",
@@ -634,7 +652,8 @@ void WallpaperDb::queueMetadata(
isVideo ? "video" : "image",
existingCache.isEmpty() ? "" : ", existing blob");
m_queue.append(MetadataJob{entry, entry->path(), isVideo, existingCache});
m_queue.append(
MetadataJob{entry, entry->path(), isVideo, existingCache, existingPoster});
if (!m_queueTimer.isActive()) {
m_queueTimer.start();
@@ -672,10 +691,12 @@ void WallpaperDb::runMetadataJob(const MetadataJob& job) {
struct CacheResult {
QString path;
QByteArray cache;
QByteArray poster;
QByteArray scheme;
};
const QSize cacheSize = m_cacheSize;
const bool isVideoJob = job.isVideo;
QElapsedTimer clock;
clock.start();
@@ -685,7 +706,7 @@ void WallpaperDb::runMetadataJob(const MetadataJob& job) {
watcher,
&QFutureWatcher<CacheResult>::finished,
this,
[this, watcher, clock]() {
[this, watcher, clock, isVideoJob]() {
const auto result = watcher->result();
qCInfo(
@@ -695,15 +716,27 @@ void WallpaperDb::runMetadataJob(const MetadataJob& job) {
int(result.cache.size()),
int(clock.elapsed()));
qCInfo(
dbLog,
"WallpaperDb: %s poster %d bytes",
result.path.toUtf8().constData(),
int(result.poster.size()));
if (isVideoJob && result.poster.isEmpty()) {
qWarning(dbLog)
<< "WallpaperDb: no poster produced for" << result.path;
}
if (QSqlDatabase::contains(m_uuid)) {
const auto db = QSqlDatabase::database(m_uuid, false);
if (db.isOpen()) {
QSqlQuery query(db);
query.prepare(
"UPDATE wallpapers SET cache = ?, scheme_thumb = ?"
" WHERE path = ?");
"UPDATE wallpapers SET cache = ?, scheme_thumb = ?,"
" poster = ? WHERE path = ?");
query.addBindValue(result.cache);
query.addBindValue(result.scheme);
query.addBindValue(result.poster);
query.addBindValue(result.path);
if (!query.exec()) {
qWarning(dbLog)
@@ -736,7 +769,14 @@ void WallpaperDb::runMetadataJob(const MetadataJob& job) {
: imageCacheBytes(job.path, cacheSize);
}
QByteArray poster = job.existingPoster;
if (poster.isEmpty()) {
poster =
job.isVideo ? videoPosterBytes(job.path, cacheSize) : cache;
}
result.cache = cache;
result.poster = poster;
result.scheme = cache.isEmpty() ? QByteArray()
: schemeThumbBytes(cache, 128);
@@ -781,4 +821,56 @@ bool WallpaperDb::entryAllowed(const WallpaperEntry* entry) const {
return entry->hardware();
}
QVariantMap WallpaperDb::crop(const QString& path, const QString& screen) const {
const auto pathCrops = m_crops.constFind(path);
if (pathCrops == m_crops.constEnd()) return {};
return pathCrops.value().value(screen).toMap();
}
void WallpaperDb::setCrop(
const QString& path, const QString& screen, const QVariantMap& crop) {
if (path.isEmpty() || screen.isEmpty()) return;
QVariantMap& pathCrops = m_crops[path];
pathCrops[screen] = crop;
storeCropJson(path);
emit cropsChanged(path);
}
void WallpaperDb::loadCrop(const QString& path, const QString& json) {
if (json.isEmpty()) {
m_crops.remove(path);
return;
}
const auto doc = QJsonDocument::fromJson(json.toUtf8());
if (!doc.isObject()) {
m_crops.remove(path);
return;
}
m_crops[path] = doc.object().toVariantMap();
}
void WallpaperDb::storeCropJson(const QString& path) {
const auto db = QSqlDatabase::database(m_uuid, false);
if (!db.isOpen()) return;
const auto pathCrops = m_crops.value(path);
const QByteArray json =
pathCrops.isEmpty()
? QByteArray()
: QJsonDocument(QJsonObject::fromVariantMap(pathCrops))
.toJson(QJsonDocument::Compact);
QSqlQuery query(db);
query.prepare("UPDATE wallpapers SET crop = ? WHERE path = ?");
query.addBindValue(json.isEmpty() ? QVariant() : QVariant(json));
query.addBindValue(path);
if (!query.exec()) {
qWarning(dbLog) << "WallpaperDb: could not store the crop for" << path
<< query.lastError();
}
}
} // namespace ZShell::models