From 583e46f2f07714d968c6aadf3ddce604b287c7cf Mon Sep 17 00:00:00 2001 From: purian23 Date: Mon, 6 Jul 2026 00:35:00 -0400 Subject: [PATCH] fix(imageCache): improve cache management & file handling --- quickshell/Common/CacheUtils.qml | 47 ++++++++++++++++++++++++++++- quickshell/Widgets/CachingImage.qml | 20 +++++++++--- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/quickshell/Common/CacheUtils.qml b/quickshell/Common/CacheUtils.qml index 85d4f2b4e..5e277a566 100644 --- a/quickshell/Common/CacheUtils.qml +++ b/quickshell/Common/CacheUtils.qml @@ -7,17 +7,62 @@ import qs.Common Singleton { id: root + // Filenames present on disk, so CachingImage never points an Image at a + // missing cache file (Qt logs that as a "Cannot open" warning). + property var cachedFiles: ({}) + + Component.onCompleted: scan() + + function scan() { + Proc.runCommand("imagecache_scan", ["ls", "-1", Paths.stringify(Paths.imagecache)], function (output, exitCode) { + const map = {}; + if (exitCode === 0 && output) { + const lines = output.split("\n"); + for (var i = 0; i < lines.length; i++) { + const name = lines[i].trim(); + if (name) + map[name] = true; + } + } + root.cachedFiles = map; + }); + } + + function hasCachedFile(name) { + return name ? root.cachedFiles[name] === true : false; + } + + function recordCachedFile(name) { + if (name) + root.cachedFiles[name] = true; + } + + function forgetCachedFile(name) { + if (name && root.cachedFiles[name] !== undefined) + delete root.cachedFiles[name]; + } + function clearImageCache() { Quickshell.execDetached(["rm", "-rf", Paths.stringify(Paths.imagecache)]); Paths.mkdir(Paths.imagecache); + root.cachedFiles = ({}); } function clearOldCache(ageInMinutes) { - Quickshell.execDetached(["find", Paths.stringify(Paths.imagecache), "-name", "*.png", "-mmin", `+${ageInMinutes}`, "-delete"]); + // Rescan on delete completion since we can't know which files matched. + Proc.runCommand("imagecache_prune", ["find", Paths.stringify(Paths.imagecache), "-name", "*.png", "-mmin", `+${ageInMinutes}`, "-delete"], function (output, exitCode) { + root.scan(); + }); } function clearCacheForSize(size) { Quickshell.execDetached(["find", Paths.stringify(Paths.imagecache), "-name", `*@${size}x${size}.png`, "-delete"]); + const suffix = `@${size}x${size}.png`; + const map = {}; + for (const key in root.cachedFiles) + if (!key.endsWith(suffix)) + map[key] = true; + root.cachedFiles = map; } function getCacheSize(callback) { diff --git a/quickshell/Widgets/CachingImage.qml b/quickshell/Widgets/CachingImage.qml index b8f410406..f20096e26 100644 --- a/quickshell/Widgets/CachingImage.qml +++ b/quickshell/Widgets/CachingImage.qml @@ -39,7 +39,8 @@ Item { } readonly property string imageHash: normalizedPath ? djb2Hash(normalizedPath) : "" - readonly property string cachePath: imageHash && !isRemoteUrl && !isAnimated ? `${Paths.stringify(Paths.imagecache)}/${imageHash}@${maxCacheSize}x${maxCacheSize}.png` : "" + readonly property string cacheFileName: imageHash && !isRemoteUrl && !isAnimated ? `${imageHash}@${maxCacheSize}x${maxCacheSize}.png` : "" + readonly property string cachePath: cacheFileName ? `${Paths.stringify(Paths.imagecache)}/${cacheFileName}` : "" readonly property string encodedImagePath: { if (!normalizedPath) return ""; @@ -74,6 +75,7 @@ Item { if (!root._fromCache) return; root._fromCache = false; + CacheUtils.forgetCachedFile(root.cacheFileName); source = root.encodedImagePath; return; case Image.Ready: @@ -83,7 +85,11 @@ Item { return; Paths.mkdir(Paths.imagecache); const grabPath = root.cachePath; - grabToImage(res => res.saveToFile(grabPath)); + const grabName = root.cacheFileName; + grabToImage(res => { + if (res.saveToFile(grabPath)) + CacheUtils.recordCachedFile(grabName); + }); return; } } @@ -108,7 +114,13 @@ Item { return; } Paths.mkdir(Paths.imagecache); - _fromCache = true; - staticImg.source = cachePath; + // Read cache only when present; else load source and cache it on Ready + if (CacheUtils.hasCachedFile(cacheFileName)) { + _fromCache = true; + staticImg.source = cachePath; + } else { + _fromCache = false; + staticImg.source = encodedImagePath; + } } }