120 lines
3.8 KiB
C++
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);
|
|
}
|
|
|
|
}
|