| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
1 |
#include "thumbnailcache.h" |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
2 |
|
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
3 |
#include <QCryptographicHash> |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
4 |
#include <QDir> |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
5 |
#include <QFile> |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
6 |
#include <QFileInfo> |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
7 |
#include <QMimeDatabase> |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
8 |
#include <QStandardPaths> |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
9 |
#include <QTemporaryFile> |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
10 |
#include <QUrl> |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
11 |
|
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
12 |
namespace |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
13 |
{ |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
14 |
constexpr const char *kSoftware = "SagoImageBrowser"; |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
15 |
|
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
16 |
QFile::Permissions filePerms() |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
17 |
{ |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
18 |
return QFile::ReadOwner | QFile::WriteOwner; |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
19 |
} |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
20 |
|
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
21 |
QFile::Permissions dirPerms() |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
22 |
{ |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
23 |
return QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner; |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
24 |
} |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
25 |
} // namespace |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
26 |
|
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
27 |
QString ThumbnailCache::cacheRoot() |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
28 |
{ |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
29 |
QString base = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
30 |
if (base.isEmpty()) |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
31 |
base = QDir::homePath() + "/.cache"; |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
32 |
return base + "/thumbnails"; |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
33 |
} |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
34 |
|
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
35 |
QString ThumbnailCache::subdirFor(Size size) |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
36 |
{ |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
37 |
const char *name = (size == Size::Large) ? "large" : "normal"; |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
38 |
return cacheRoot() + "/" + name; |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
39 |
} |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
40 |
|
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
41 |
QString ThumbnailCache::canonicalUri(const QString &absPath) |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
42 |
{ |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
43 |
// Per the spec, use the absolute file URI (percent-encoded). |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
44 |
return QUrl::fromLocalFile(absPath).toString(QUrl::FullyEncoded); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
45 |
} |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
46 |
|
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
47 |
QString ThumbnailCache::hashedName(const QString &canonicalUri) |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
48 |
{ |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
49 |
QByteArray hash = QCryptographicHash::hash(canonicalUri.toUtf8(), |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
50 |
QCryptographicHash::Md5) |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
51 |
.toHex(); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
52 |
return QString::fromLatin1(hash) + ".png"; |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
53 |
} |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
54 |
|
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
55 |
bool ThumbnailCache::isInsideCache(const QString &absPath) |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
56 |
{ |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
57 |
QString abs = QFileInfo(absPath).absoluteFilePath(); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
58 |
QString root = QFileInfo(cacheRoot()).absoluteFilePath(); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
59 |
if (root.isEmpty()) |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
60 |
return false; |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
61 |
return abs.startsWith(root + "/") || abs == root; |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
62 |
} |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
63 |
|
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
64 |
QImage ThumbnailCache::load(const QString &sourcePath, Size size) |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
65 |
{ |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
66 |
QFileInfo info(sourcePath); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
67 |
if (!info.exists() || !info.isFile()) |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
68 |
return {}; |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
69 |
|
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
70 |
const QString uri = canonicalUri(info.absoluteFilePath()); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
71 |
const QString cachePath = subdirFor(size) + "/" + hashedName(uri); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
72 |
|
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
73 |
QImage img; |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
74 |
if (!img.load(cachePath, "PNG")) |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
75 |
return {}; |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
76 |
|
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
77 |
const qint64 mtime = info.lastModified().toSecsSinceEpoch(); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
78 |
bool ok = false; |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
79 |
const qint64 cachedMTime = img.text("Thumb::MTime").toLongLong(&ok); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
80 |
if (!ok || cachedMTime != mtime) |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
81 |
return {}; |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
82 |
|
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
83 |
return img; |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
84 |
} |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
85 |
|
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
86 |
void ThumbnailCache::save(const QString &sourcePath, Size size, const QImage &thumbImage) |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
87 |
{ |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
88 |
if (thumbImage.isNull()) |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
89 |
return; |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
90 |
|
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
91 |
QFileInfo info(sourcePath); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
92 |
if (!info.exists() || !info.isFile()) |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
93 |
return; |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
94 |
|
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
95 |
const QString uri = canonicalUri(info.absoluteFilePath()); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
96 |
const QString subdir = subdirFor(size); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
97 |
const QString finalPath = subdir + "/" + hashedName(uri); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
98 |
|
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
99 |
QDir dir; |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
100 |
if (!dir.mkpath(subdir)) |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
101 |
return; |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
102 |
QFile::setPermissions(subdir, dirPerms()); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
103 |
QFile::setPermissions(cacheRoot(), dirPerms()); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
104 |
|
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
105 |
QImage tagged = thumbImage; |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
106 |
tagged.setText("Thumb::URI", uri); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
107 |
tagged.setText("Thumb::MTime", |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
108 |
QString::number(info.lastModified().toSecsSinceEpoch())); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
109 |
tagged.setText("Thumb::Size", QString::number(info.size())); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
110 |
QMimeDatabase mimeDb; |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
111 |
QString mime = mimeDb.mimeTypeForFile(info).name(); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
112 |
if (!mime.isEmpty()) |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
113 |
tagged.setText("Thumb::Mimetype", mime); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
114 |
tagged.setText("Thumb::Image::Width", QString::number(thumbImage.width())); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
115 |
tagged.setText("Thumb::Image::Height", QString::number(thumbImage.height())); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
116 |
tagged.setText("Software", QString::fromLatin1(kSoftware)); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
117 |
|
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
118 |
QTemporaryFile tmp(subdir + "/tmp_XXXXXX.png"); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
119 |
tmp.setAutoRemove(false); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
120 |
if (!tmp.open()) |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
121 |
return; |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
122 |
const QString tmpPath = tmp.fileName(); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
123 |
tmp.close(); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
124 |
|
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
125 |
if (!tagged.save(tmpPath, "PNG")) |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
126 |
{ |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
127 |
QFile::remove(tmpPath); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
128 |
return; |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
129 |
} |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
130 |
|
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
131 |
QFile::setPermissions(tmpPath, filePerms()); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
132 |
|
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
133 |
// Atomic replace: remove existing target then rename. |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
134 |
QFile::remove(finalPath); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
135 |
if (!QFile::rename(tmpPath, finalPath)) |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
136 |
QFile::remove(tmpPath); |
| ac476ca8 |
Poul Sander |
2026-05-18 16:47 |
137 |
} |
|
|
1970-01-01 00:00 |
138 |
|