git repos / SagoImageBrowser

blame: src/thumbnailcache.cpp

normal view · raw

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 <QStandardPaths>
ac476ca8 Poul Sander 2026-05-18 16:47 7
#include <QTemporaryFile>
ac476ca8 Poul Sander 2026-05-18 16:47 8
#include <QUrl>
ac476ca8 Poul Sander 2026-05-18 16:47 9
80a9926d Poul Sander 2026-05-19 20:28 10
#include <sys/stat.h>
80a9926d Poul Sander 2026-05-19 20:28 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
80a9926d Poul Sander 2026-05-19 20:28 41
QByteArray ThumbnailCache::canonicalUri(const QByteArray &absPath)
ac476ca8 Poul Sander 2026-05-18 16:47 42
{
80a9926d Poul Sander 2026-05-19 20:28 43
    // Build a correct file:// URI by percent-encoding the raw byte path.
80a9926d Poul Sander 2026-05-19 20:28 44
    // Forward slashes are kept unencoded; all other bytes (including non-UTF-8
80a9926d Poul Sander 2026-05-19 20:28 45
    // ones such as Latin-1 \xF8) are percent-encoded.
80a9926d Poul Sander 2026-05-19 20:28 46
    return "file://" + QUrl::toPercentEncoding(absPath, "/");
ac476ca8 Poul Sander 2026-05-18 16:47 47
}
ac476ca8 Poul Sander 2026-05-18 16:47 48
80a9926d Poul Sander 2026-05-19 20:28 49
QString ThumbnailCache::hashedName(const QByteArray &canonicalUri)
ac476ca8 Poul Sander 2026-05-18 16:47 50
{
80a9926d Poul Sander 2026-05-19 20:28 51
    QByteArray hash =
80a9926d Poul Sander 2026-05-19 20:28 52
        QCryptographicHash::hash(canonicalUri, QCryptographicHash::Md5).toHex();
ac476ca8 Poul Sander 2026-05-18 16:47 53
    return QString::fromLatin1(hash) + ".png";
ac476ca8 Poul Sander 2026-05-18 16:47 54
}
ac476ca8 Poul Sander 2026-05-18 16:47 55
80a9926d Poul Sander 2026-05-19 20:28 56
bool ThumbnailCache::isInsideCache(const QByteArray &absPath)
ac476ca8 Poul Sander 2026-05-18 16:47 57
{
80a9926d Poul Sander 2026-05-19 20:28 58
    QByteArray root = cacheRoot().toUtf8(); // cache root is always valid UTF-8
80a9926d Poul Sander 2026-05-19 20:28 59
    return absPath.startsWith(root + '/') || absPath == root;
ac476ca8 Poul Sander 2026-05-18 16:47 60
}
ac476ca8 Poul Sander 2026-05-18 16:47 61
80a9926d Poul Sander 2026-05-19 20:28 62
QImage ThumbnailCache::load(const QByteArray &sourcePath, Size size)
ac476ca8 Poul Sander 2026-05-18 16:47 63
{
80a9926d Poul Sander 2026-05-19 20:28 64
    struct ::stat st{};
80a9926d Poul Sander 2026-05-19 20:28 65
    if (::stat(sourcePath.constData(), &st) != 0 || !S_ISREG(st.st_mode))
ac476ca8 Poul Sander 2026-05-18 16:47 66
        return {};
ac476ca8 Poul Sander 2026-05-18 16:47 67
80a9926d Poul Sander 2026-05-19 20:28 68
    const QByteArray uri = canonicalUri(sourcePath);
ac476ca8 Poul Sander 2026-05-18 16:47 69
    const QString cachePath = subdirFor(size) + "/" + hashedName(uri);
ac476ca8 Poul Sander 2026-05-18 16:47 70
ac476ca8 Poul Sander 2026-05-18 16:47 71
    QImage img;
ac476ca8 Poul Sander 2026-05-18 16:47 72
    if (!img.load(cachePath, "PNG"))
ac476ca8 Poul Sander 2026-05-18 16:47 73
        return {};
ac476ca8 Poul Sander 2026-05-18 16:47 74
80a9926d Poul Sander 2026-05-19 20:28 75
    const qint64 mtime = static_cast<qint64>(st.st_mtime);
ac476ca8 Poul Sander 2026-05-18 16:47 76
    bool ok = false;
ac476ca8 Poul Sander 2026-05-18 16:47 77
    const qint64 cachedMTime = img.text("Thumb::MTime").toLongLong(&ok);
ac476ca8 Poul Sander 2026-05-18 16:47 78
    if (!ok || cachedMTime != mtime)
ac476ca8 Poul Sander 2026-05-18 16:47 79
        return {};
ac476ca8 Poul Sander 2026-05-18 16:47 80
ac476ca8 Poul Sander 2026-05-18 16:47 81
    return img;
ac476ca8 Poul Sander 2026-05-18 16:47 82
}
ac476ca8 Poul Sander 2026-05-18 16:47 83
80a9926d Poul Sander 2026-05-19 20:28 84
void ThumbnailCache::save(const QByteArray &sourcePath, Size size, const QImage &thumbImage)
ac476ca8 Poul Sander 2026-05-18 16:47 85
{
ac476ca8 Poul Sander 2026-05-18 16:47 86
    if (thumbImage.isNull())
ac476ca8 Poul Sander 2026-05-18 16:47 87
        return;
ac476ca8 Poul Sander 2026-05-18 16:47 88
80a9926d Poul Sander 2026-05-19 20:28 89
    struct ::stat st{};
80a9926d Poul Sander 2026-05-19 20:28 90
    if (::stat(sourcePath.constData(), &st) != 0 || !S_ISREG(st.st_mode))
ac476ca8 Poul Sander 2026-05-18 16:47 91
        return;
ac476ca8 Poul Sander 2026-05-18 16:47 92
80a9926d Poul Sander 2026-05-19 20:28 93
    const QByteArray uri = canonicalUri(sourcePath);
ac476ca8 Poul Sander 2026-05-18 16:47 94
    const QString subdir = subdirFor(size);
ac476ca8 Poul Sander 2026-05-18 16:47 95
    const QString finalPath = subdir + "/" + hashedName(uri);
ac476ca8 Poul Sander 2026-05-18 16:47 96
ac476ca8 Poul Sander 2026-05-18 16:47 97
    QDir dir;
ac476ca8 Poul Sander 2026-05-18 16:47 98
    if (!dir.mkpath(subdir))
ac476ca8 Poul Sander 2026-05-18 16:47 99
        return;
ac476ca8 Poul Sander 2026-05-18 16:47 100
    QFile::setPermissions(subdir, dirPerms());
ac476ca8 Poul Sander 2026-05-18 16:47 101
    QFile::setPermissions(cacheRoot(), dirPerms());
ac476ca8 Poul Sander 2026-05-18 16:47 102
ac476ca8 Poul Sander 2026-05-18 16:47 103
    QImage tagged = thumbImage;
80a9926d Poul Sander 2026-05-19 20:28 104
    tagged.setText("Thumb::URI", QString::fromLatin1(uri));
ac476ca8 Poul Sander 2026-05-18 16:47 105
    tagged.setText("Thumb::MTime",
80a9926d Poul Sander 2026-05-19 20:28 106
                   QString::number(static_cast<qint64>(st.st_mtime)));
80a9926d Poul Sander 2026-05-19 20:28 107
    tagged.setText("Thumb::Size", QString::number(static_cast<qint64>(st.st_size)));
ac476ca8 Poul Sander 2026-05-18 16:47 108
    tagged.setText("Software", QString::fromLatin1(kSoftware));
ac476ca8 Poul Sander 2026-05-18 16:47 109
ac476ca8 Poul Sander 2026-05-18 16:47 110
    QTemporaryFile tmp(subdir + "/tmp_XXXXXX.png");
ac476ca8 Poul Sander 2026-05-18 16:47 111
    tmp.setAutoRemove(false);
ac476ca8 Poul Sander 2026-05-18 16:47 112
    if (!tmp.open())
ac476ca8 Poul Sander 2026-05-18 16:47 113
        return;
ac476ca8 Poul Sander 2026-05-18 16:47 114
    const QString tmpPath = tmp.fileName();
ac476ca8 Poul Sander 2026-05-18 16:47 115
    tmp.close();
ac476ca8 Poul Sander 2026-05-18 16:47 116
ac476ca8 Poul Sander 2026-05-18 16:47 117
    if (!tagged.save(tmpPath, "PNG"))
ac476ca8 Poul Sander 2026-05-18 16:47 118
    {
ac476ca8 Poul Sander 2026-05-18 16:47 119
        QFile::remove(tmpPath);
ac476ca8 Poul Sander 2026-05-18 16:47 120
        return;
ac476ca8 Poul Sander 2026-05-18 16:47 121
    }
ac476ca8 Poul Sander 2026-05-18 16:47 122
ac476ca8 Poul Sander 2026-05-18 16:47 123
    QFile::setPermissions(tmpPath, filePerms());
ac476ca8 Poul Sander 2026-05-18 16:47 124
ac476ca8 Poul Sander 2026-05-18 16:47 125
    // Atomic replace: remove existing target then rename.
ac476ca8 Poul Sander 2026-05-18 16:47 126
    QFile::remove(finalPath);
ac476ca8 Poul Sander 2026-05-18 16:47 127
    if (!QFile::rename(tmpPath, finalPath))
ac476ca8 Poul Sander 2026-05-18 16:47 128
        QFile::remove(tmpPath);
ac476ca8 Poul Sander 2026-05-18 16:47 129
}
1970-01-01 00:00 130