#include "libsync/networkjobs/resources.h" #include "libsync/account.h" #include #include #include 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); } }