thumbnailer: better caching

This commit is contained in:
2 * r + 2 * t 2025-05-05 19:06:32 +10:00
parent 57f815bdc8
commit 1a650b73bc
3 changed files with 75 additions and 66 deletions

View file

@ -28,23 +28,25 @@ Item {
id: two id: two
} }
component Img: Image { component Img: CachingImage {
id: img id: img
function update(): void { function update(): void {
if (source === root.source) const srcPath = `${root.source}`.slice(7);
if (thumbnail.originalPath === srcPath) {
root.current = this; root.current = this;
else } else
source = root.source; path = srcPath;
} }
anchors.fill: parent anchors.fill: parent
asynchronous: true asynchronous: true
cache: false
fillMode: Image.PreserveAspectCrop fillMode: Image.PreserveAspectCrop
opacity: 0 opacity: 0
scale: Wallpapers.showPreview ? 1 : 0.8 scale: Wallpapers.showPreview ? 1 : 0.8
sourceSize.width: width
sourceSize.height: height
onStatusChanged: { onStatusChanged: {
if (status === Image.Ready) if (status === Image.Ready)

View file

@ -1,7 +1,9 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior: Bound
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import QtQuick
import Qt.labs.platform import Qt.labs.platform
Singleton { Singleton {
@ -9,36 +11,64 @@ Singleton {
readonly property string thumbDir: `${StandardPaths.standardLocations(StandardPaths.GenericCacheLocation)[0]}/caelestia/thumbnails`.slice(7) readonly property string thumbDir: `${StandardPaths.standardLocations(StandardPaths.GenericCacheLocation)[0]}/caelestia/thumbnails`.slice(7)
function go(sha: string, path: string, width: int, height: int): string { function go(path: string, width: int, height: int): var {
if (!sha || !path || !width || !height) return thumbComp.createObject(root, {
return ""; originalPath: path,
width: width,
const thumbPath = `${thumbDir}/${sha}@${width}x${height}-exact.png`; height: height
});
thumbProc.path = path;
thumbProc.thumbPath = thumbPath;
thumbProc.width = width;
thumbProc.height = height;
thumbProc.startDetached();
return thumbPath;
} }
Process { component Thumbnail: QtObject {
id: thumbProc id: obj
required property string originalPath
required property int width
required property int height
property string path property string path
property string thumbPath
property int width
property int height
readonly property Process shaProc: Process {
running: true
command: ["sha1sum", obj.originalPath]
stdout: SplitParser {
onRead: data => {
const sha = data.split(" ")[0];
obj.path = `${root.thumbDir}/${sha}@${obj.width}x${obj.height}-exact.png`;
obj.thumbProc.running = true;
}
}
}
readonly property Process thumbProc: Process {
command: ["fish", "-c", ` command: ["fish", "-c", `
if ! test -f ${thumbPath} if test -f ${obj.path}
set -l size (identify -ping -format '%w\n%h' ${path}) exit 1
if test $size[1] -gt ${width} -o $size[2] -gt ${height} else
magick ${path} -thumbnail ${width}x${height}^ -background none -gravity center -extent ${width}x${height} -unsharp 0x.5 ${thumbPath} set -l size (identify -ping -format '%w\n%h' ${obj.originalPath})
if test $size[1] -gt ${obj.width} -o $size[2] -gt ${obj.height}
magick ${obj.originalPath} -${obj.width > 1024 || obj.height > 1024 ? "resize" : "thumbnail"} ${obj.width}x${obj.height}^ -background none -gravity center -extent ${obj.width}x${obj.height} -unsharp 0x.5 ${obj.path}
else
cp ${obj.originalPath} ${obj.path}
end end
end end`]
`] onExited: code => {
if (code === 0) {
const path = obj.path;
obj.path = "";
obj.path = path;
}
}
}
function reload(): void {
shaProc.running = true;
}
}
Component {
id: thumbComp
Thumbnail {}
} }
} }

View file

@ -5,46 +5,23 @@ import QtQuick
Image { Image {
id: root id: root
required property string path property string path
property string thumbnail readonly property Thumbnailer.Thumbnail thumbnail: Thumbnailer.go(path, width, height)
source: { source: thumbnail.path ? `file://${thumbnail.path}` : ""
if (thumbnail)
return `file://${thumbnail}`;
shaProc.running = true;
return "";
}
asynchronous: true asynchronous: true
fillMode: Image.PreserveAspectCrop fillMode: Image.PreserveAspectCrop
onPathChanged: shaProc.running = true onPathChanged: {
onWidthChanged: shaProc.running = true thumbnail.originalPath = path;
onHeightChanged: shaProc.running = true thumbnail.reload();
onStatusChanged: {
if (status === Image.Error)
waitProc.running = true;
}
Process {
id: shaProc
command: ["sha1sum", root.path]
stdout: SplitParser {
onRead: data => root.thumbnail = Thumbnailer.go(data.split(" ")[0], root.path, root.width, root.height)
}
}
Process {
id: waitProc
command: ["inotifywait", "-q", "-e", "close_write", "--format", "%w%f", "-m", root.thumbnail.slice(0, root.thumbnail.lastIndexOf("/"))]
stdout: SplitParser {
onRead: file => {
if (file === root.thumbnail) {
root.source = file;
waitProc.running = false;
}
} }
onWidthChanged: {
thumbnail.width = width;
thumbnail.reload();
} }
onHeightChanged: {
thumbnail.height = height;
thumbnail.reload();
} }
} }