Files
QSfera/Desktop/src/libsync/networkjobs/resources.cpp
T
Курнат Андрей 2315f25754 Initial QSfera import
2026-06-07 10:20:04 +03:00

120 lines
3.8 KiB
C++

#include "libsync/networkjobs/resources.h"
#include "libsync/account.h"
#include <QFile>
#include <QIcon>
#include <QMimeDatabase>
namespace OCC {
Q_LOGGING_CATEGORY(lcResources, "sync.networkjob.resource")
namespace {
QString hashMd5(const QString &data)
{
QCryptographicHash hash(QCryptographicHash::Algorithm::Md5);
hash.addData(data.toLatin1());
return QString::fromLatin1(hash.result().toHex());
}
QString cacheKey(QNetworkReply *reply)
{
const QString urlHash = hashMd5(reply->url().toString());
const QString eTagHash = hashMd5(reply->header(QNetworkRequest::ETagHeader).toString());
const auto db = QMimeDatabase();
QString extension = db.mimeTypeForName(reply->header(QNetworkRequest::ContentTypeHeader).toString()).preferredSuffix();
if (extension.isEmpty()) {
extension = db.mimeTypeForData(reply).preferredSuffix();
}
if (extension.isEmpty()) {
extension = QStringLiteral("unknown");
}
return QStringLiteral("%1-%2.%3").arg(urlHash, eTagHash, extension);
}
}
void ResourceJob::finished()
{
if (reply()->error() != QNetworkReply::NoError) {
qCWarning(lcResources) << u"Network error: " << this << errorString();
} else {
_cacheKey = cacheKey(reply());
}
// storing the file on disk enables Qt to apply some optimizations, e.g., in QIcon
// also, specifically for icons, loading from disk allows easy management of scalable and non-scalable entities
if (httpStatusCode() == 200 && reply()->size() > 0) {
const QString path = _cache->path(_cacheKey);
qCDebug(lcResources) << u"cache file path:" << path;
// furthermore, we can skip writing the file if the cache key has not changed (i.e., a file exists) and the file has come from the network cache
if (QFileInfo::exists(path) && reply()->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool()) {
qCDebug(lcResources) << u"file has come from network cache, skipping writing";
} else {
QFile cacheFile(path);
// note: we want to truncate the file if it exists
if (!cacheFile.open(QIODevice::WriteOnly)) {
qCCritical(lcResources) << u"failed to open cache file for writing:" << cacheFile.fileName();
} else {
if (!cacheFile.write(reply()->readAll())) {
qCCritical(lcResources) << u"failed to write to cache file:" << cacheFile.fileName();
}
}
}
}
SimpleNetworkJob::finished();
}
QIcon ResourceJob::asIcon() const
{
// storing the file on disk enables Qt to apply some optimizations (e.g., caching of rendered pixmaps)
Q_ASSERT(!_cacheKey.isEmpty());
return QIcon(_cache->path(_cacheKey));
}
ResourceJob::ResourceJob(const ResourcesCache *cache, const QUrl &rootUrl, const QString &path, QObject *parent)
: SimpleNetworkJob(cache->account()->sharedFromThis(), rootUrl, path, "GET", {}, parent)
, _cache(cache)
{
setStoreInCache(true);
}
ResourcesCache::ResourcesCache(const QString &cacheDirectory, Account *account)
: QObject(account)
, _account(account)
, _cacheDirectory(QStringLiteral("%1/tmp.XXXXXX").arg(cacheDirectory))
{
Q_ASSERT(_cacheDirectory.isValid());
}
ResourceJob *ResourcesCache::makeGetJob(const QUrl &rootUrl, const QString &path, QObject *parent) const
{
return new ResourceJob(this, rootUrl, path, parent);
}
ResourceJob *ResourcesCache::makeGetJob(const QString &path, QObject *parent) const
{
return makeGetJob(_account->url(), path, parent);
}
Account *ResourcesCache::account() const
{
return _account;
}
QString ResourcesCache::path(const QString &cacheKey) const
{
Q_ASSERT(!cacheKey.isEmpty());
Q_ASSERT(_cacheDirectory.isValid());
return _cacheDirectory.filePath(cacheKey);
}
}