refactor: more net cleanup
This runs clang-tidy on some other files in launcher/net/. This also makes use of some JSON wrappers in HttpMetaCache, instead of using the Qt stuff directly. Lastly, this removes useless null checks (crashes don't occur because of this, but because of concurrent usage / free of the QByteArray pointer), and fix a fixme in Download.h
This commit is contained in:
parent
efa3fbff39
commit
040ee919e5
@ -6,6 +6,8 @@ namespace Net {
|
||||
|
||||
/*
|
||||
* Sink object for downloads that uses an external QByteArray it doesn't own as a target.
|
||||
* FIXME: It is possible that the QByteArray is freed while we're doing some operation on it,
|
||||
* causing a segmentation fault.
|
||||
*/
|
||||
class ByteArraySink : public Sink {
|
||||
public:
|
||||
@ -16,9 +18,6 @@ class ByteArraySink : public Sink {
|
||||
public:
|
||||
auto init(QNetworkRequest& request) -> Task::State override
|
||||
{
|
||||
if(!m_output)
|
||||
return Task::State::Failed;
|
||||
|
||||
m_output->clear();
|
||||
if (initAllValidators(request))
|
||||
return Task::State::Running;
|
||||
@ -27,9 +26,6 @@ class ByteArraySink : public Sink {
|
||||
|
||||
auto write(QByteArray& data) -> Task::State override
|
||||
{
|
||||
if(!m_output)
|
||||
return Task::State::Failed;
|
||||
|
||||
m_output->append(data);
|
||||
if (writeAllValidators(data))
|
||||
return Task::State::Running;
|
||||
@ -38,9 +34,6 @@ class ByteArraySink : public Sink {
|
||||
|
||||
auto abort() -> Task::State override
|
||||
{
|
||||
if(!m_output)
|
||||
return Task::State::Failed;
|
||||
|
||||
m_output->clear();
|
||||
failAllValidators();
|
||||
return Task::State::Failed;
|
||||
|
@ -1,55 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "Validator.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <memory>
|
||||
#include <QFile>
|
||||
|
||||
namespace Net {
|
||||
class ChecksumValidator: public Validator
|
||||
{
|
||||
public: /* con/des */
|
||||
class ChecksumValidator : public Validator {
|
||||
public:
|
||||
ChecksumValidator(QCryptographicHash::Algorithm algorithm, QByteArray expected = QByteArray())
|
||||
:m_checksum(algorithm), m_expected(expected)
|
||||
{
|
||||
};
|
||||
virtual ~ChecksumValidator() {};
|
||||
: m_checksum(algorithm), m_expected(expected){};
|
||||
virtual ~ChecksumValidator() = default;
|
||||
|
||||
public: /* methods */
|
||||
bool init(QNetworkRequest &) override
|
||||
public:
|
||||
auto init(QNetworkRequest&) -> bool override
|
||||
{
|
||||
m_checksum.reset();
|
||||
return true;
|
||||
}
|
||||
bool write(QByteArray & data) override
|
||||
|
||||
auto write(QByteArray& data) -> bool override
|
||||
{
|
||||
m_checksum.addData(data);
|
||||
return true;
|
||||
}
|
||||
bool abort() override
|
||||
|
||||
auto abort() -> bool override { return true; }
|
||||
|
||||
auto validate(QNetworkReply&) -> bool override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
bool validate(QNetworkReply &) override
|
||||
{
|
||||
if(m_expected.size() && m_expected != hash())
|
||||
{
|
||||
if (m_expected.size() && m_expected != hash()) {
|
||||
qWarning() << "Checksum mismatch, download is bad.";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
QByteArray hash()
|
||||
{
|
||||
return m_checksum.result();
|
||||
}
|
||||
void setExpected(QByteArray expected)
|
||||
{
|
||||
m_expected = expected;
|
||||
}
|
||||
|
||||
private: /* data */
|
||||
auto hash() -> QByteArray { return m_checksum.result(); }
|
||||
|
||||
void setExpected(QByteArray expected) { m_expected = expected; }
|
||||
|
||||
private:
|
||||
QCryptographicHash m_checksum;
|
||||
QByteArray m_expected;
|
||||
};
|
||||
}
|
||||
} // namespace Net
|
||||
|
@ -33,30 +33,29 @@ Download::Download() : NetAction()
|
||||
m_state = State::Inactive;
|
||||
}
|
||||
|
||||
Download::Ptr Download::makeCached(QUrl url, MetaEntryPtr entry, Options options)
|
||||
auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Download::Ptr
|
||||
{
|
||||
Download* dl = new Download();
|
||||
auto* dl = new Download();
|
||||
dl->m_url = url;
|
||||
dl->m_options = options;
|
||||
auto md5Node = new ChecksumValidator(QCryptographicHash::Md5);
|
||||
auto cachedNode = new MetaCacheSink(entry, md5Node);
|
||||
dl->m_sink.reset(cachedNode);
|
||||
dl->m_target_path = entry->getFullPath();
|
||||
return dl;
|
||||
}
|
||||
|
||||
Download::Ptr Download::makeByteArray(QUrl url, QByteArray* output, Options options)
|
||||
auto Download::makeByteArray(QUrl url, QByteArray* output, Options options) -> Download::Ptr
|
||||
{
|
||||
Download* dl = new Download();
|
||||
auto* dl = new Download();
|
||||
dl->m_url = url;
|
||||
dl->m_options = options;
|
||||
dl->m_sink.reset(new ByteArraySink(output));
|
||||
return dl;
|
||||
}
|
||||
|
||||
Download::Ptr Download::makeFile(QUrl url, QString path, Options options)
|
||||
auto Download::makeFile(QUrl url, QString path, Options options) -> Download::Ptr
|
||||
{
|
||||
Download* dl = new Download();
|
||||
auto* dl = new Download();
|
||||
dl->m_url = url;
|
||||
dl->m_options = options;
|
||||
dl->m_sink.reset(new FileSink(path));
|
||||
@ -143,7 +142,7 @@ void Download::sslErrors(const QList<QSslError>& errors)
|
||||
}
|
||||
}
|
||||
|
||||
bool Download::handleRedirect()
|
||||
auto Download::handleRedirect() -> bool
|
||||
{
|
||||
QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl();
|
||||
if (!redirect.isValid()) {
|
||||
@ -230,7 +229,7 @@ void Download::downloadFinished()
|
||||
// make sure we got all the remaining data, if any
|
||||
auto data = m_reply->readAll();
|
||||
if (data.size()) {
|
||||
qDebug() << "Writing extra" << data.size() << "bytes to" << m_target_path;
|
||||
qDebug() << "Writing extra" << data.size() << "bytes";
|
||||
m_state = m_sink->write(data);
|
||||
}
|
||||
|
||||
@ -243,6 +242,7 @@ void Download::downloadFinished()
|
||||
emitFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
m_reply.reset();
|
||||
qDebug() << "Download succeeded:" << m_url.toString();
|
||||
emit succeeded();
|
||||
@ -254,17 +254,17 @@ void Download::downloadReadyRead()
|
||||
auto data = m_reply->readAll();
|
||||
m_state = m_sink->write(data);
|
||||
if (m_state == State::Failed) {
|
||||
qCritical() << "Failed to process response chunk for " << m_target_path;
|
||||
qCritical() << "Failed to process response chunk";
|
||||
}
|
||||
// qDebug() << "Download" << m_url.toString() << "gained" << data.size() << "bytes";
|
||||
} else {
|
||||
qCritical() << "Cannot write to " << m_target_path << ", illegal status" << m_status;
|
||||
qCritical() << "Cannot write download data! illegal status " << m_status;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Net
|
||||
|
||||
bool Net::Download::abort()
|
||||
auto Net::Download::abort() -> bool
|
||||
{
|
||||
if (m_reply) {
|
||||
m_reply->abort();
|
||||
|
@ -15,63 +15,54 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "NetAction.h"
|
||||
#include "HttpMetaCache.h"
|
||||
#include "Validator.h"
|
||||
#include "NetAction.h"
|
||||
#include "Sink.h"
|
||||
#include "Validator.h"
|
||||
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
namespace Net {
|
||||
class Download : public NetAction
|
||||
{
|
||||
class Download : public NetAction {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
typedef shared_qobject_ptr<class Download> Ptr;
|
||||
enum class Option
|
||||
{
|
||||
NoOptions = 0,
|
||||
AcceptLocalFiles = 1
|
||||
};
|
||||
public:
|
||||
using Ptr = shared_qobject_ptr<class Download>;
|
||||
enum class Option { NoOptions = 0, AcceptLocalFiles = 1 };
|
||||
Q_DECLARE_FLAGS(Options, Option)
|
||||
|
||||
protected:
|
||||
protected:
|
||||
explicit Download();
|
||||
public:
|
||||
virtual ~Download(){};
|
||||
static Download::Ptr makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions);
|
||||
static Download::Ptr makeByteArray(QUrl url, QByteArray *output, Options options = Option::NoOptions);
|
||||
static Download::Ptr makeFile(QUrl url, QString path, Options options = Option::NoOptions);
|
||||
|
||||
public:
|
||||
QString getTargetFilepath()
|
||||
{
|
||||
return m_target_path;
|
||||
}
|
||||
void addValidator(Validator * v);
|
||||
bool abort() override;
|
||||
bool canAbort() const override { return true; };
|
||||
public:
|
||||
~Download() override = default;
|
||||
|
||||
private:
|
||||
bool handleRedirect();
|
||||
static auto makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions) -> Download::Ptr;
|
||||
static auto makeByteArray(QUrl url, QByteArray* output, Options options = Option::NoOptions) -> Download::Ptr;
|
||||
static auto makeFile(QUrl url, QString path, Options options = Option::NoOptions) -> Download::Ptr;
|
||||
|
||||
protected slots:
|
||||
public:
|
||||
void addValidator(Validator* v);
|
||||
auto abort() -> bool override;
|
||||
auto canAbort() const -> bool override { return true; };
|
||||
|
||||
private:
|
||||
auto handleRedirect() -> bool;
|
||||
|
||||
protected slots:
|
||||
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
|
||||
void downloadError(QNetworkReply::NetworkError error) override;
|
||||
void sslErrors(const QList<QSslError> & errors);
|
||||
void sslErrors(const QList<QSslError>& errors);
|
||||
void downloadFinished() override;
|
||||
void downloadReadyRead() override;
|
||||
|
||||
public slots:
|
||||
public slots:
|
||||
void executeTask() override;
|
||||
|
||||
private:
|
||||
// FIXME: remove this, it has no business being here.
|
||||
QString m_target_path;
|
||||
private:
|
||||
std::unique_ptr<Sink> m_sink;
|
||||
Options m_options;
|
||||
};
|
||||
}
|
||||
} // namespace Net
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(Net::Download::Options)
|
||||
|
@ -1,7 +1,5 @@
|
||||
#include "FileSink.h"
|
||||
|
||||
#include <QFile>
|
||||
|
||||
#include "FileSystem.h"
|
||||
|
||||
namespace Net {
|
||||
@ -9,44 +7,38 @@ namespace Net {
|
||||
Task::State FileSink::init(QNetworkRequest& request)
|
||||
{
|
||||
auto result = initCache(request);
|
||||
if(result != Task::State::Running)
|
||||
{
|
||||
if (result != Task::State::Running) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// create a new save file and open it for writing
|
||||
if (!FS::ensureFilePathExists(m_filename))
|
||||
{
|
||||
if (!FS::ensureFilePathExists(m_filename)) {
|
||||
qCritical() << "Could not create folder for " + m_filename;
|
||||
return Task::State::Failed;
|
||||
}
|
||||
|
||||
wroteAnyData = false;
|
||||
m_output_file.reset(new QSaveFile(m_filename));
|
||||
if (!m_output_file->open(QIODevice::WriteOnly))
|
||||
{
|
||||
if (!m_output_file->open(QIODevice::WriteOnly)) {
|
||||
qCritical() << "Could not open " + m_filename + " for writing";
|
||||
return Task::State::Failed;
|
||||
}
|
||||
|
||||
if(initAllValidators(request))
|
||||
if (initAllValidators(request))
|
||||
return Task::State::Running;
|
||||
return Task::State::Failed;
|
||||
}
|
||||
|
||||
Task::State FileSink::initCache(QNetworkRequest &)
|
||||
{
|
||||
return Task::State::Running;
|
||||
}
|
||||
|
||||
Task::State FileSink::write(QByteArray& data)
|
||||
{
|
||||
if (!writeAllValidators(data) || m_output_file->write(data) != data.size())
|
||||
{
|
||||
if (!writeAllValidators(data) || m_output_file->write(data) != data.size()) {
|
||||
qCritical() << "Failed writing into " + m_filename;
|
||||
m_output_file->cancelWriting();
|
||||
m_output_file.reset();
|
||||
wroteAnyData = false;
|
||||
return Task::State::Failed;
|
||||
}
|
||||
|
||||
wroteAnyData = true;
|
||||
return Task::State::Running;
|
||||
}
|
||||
@ -64,34 +56,39 @@ Task::State FileSink::finalize(QNetworkReply& reply)
|
||||
QVariant statusCodeV = reply.attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||
bool validStatus = false;
|
||||
int statusCode = statusCodeV.toInt(&validStatus);
|
||||
if(validStatus)
|
||||
{
|
||||
if (validStatus) {
|
||||
// this leaves out 304 Not Modified
|
||||
gotFile = statusCode == 200 || statusCode == 203;
|
||||
}
|
||||
|
||||
// if we wrote any data to the save file, we try to commit the data to the real file.
|
||||
// if it actually got a proper file, we write it even if it was empty
|
||||
if (gotFile || wroteAnyData)
|
||||
{
|
||||
if (gotFile || wroteAnyData) {
|
||||
// ask validators for data consistency
|
||||
// we only do this for actual downloads, not 'your data is still the same' cache hits
|
||||
if(!finalizeAllValidators(reply))
|
||||
if (!finalizeAllValidators(reply))
|
||||
return Task::State::Failed;
|
||||
|
||||
// nothing went wrong...
|
||||
if (!m_output_file->commit())
|
||||
{
|
||||
if (!m_output_file->commit()) {
|
||||
qCritical() << "Failed to commit changes to " << m_filename;
|
||||
m_output_file->cancelWriting();
|
||||
return Task::State::Failed;
|
||||
}
|
||||
}
|
||||
|
||||
// then get rid of the save file
|
||||
m_output_file.reset();
|
||||
|
||||
return finalizeCache(reply);
|
||||
}
|
||||
|
||||
Task::State FileSink::finalizeCache(QNetworkReply &)
|
||||
Task::State FileSink::initCache(QNetworkRequest&)
|
||||
{
|
||||
return Task::State::Running;
|
||||
}
|
||||
|
||||
Task::State FileSink::finalizeCache(QNetworkReply&)
|
||||
{
|
||||
return Task::State::Succeeded;
|
||||
}
|
||||
@ -101,4 +98,4 @@ bool FileSink::hasLocalData()
|
||||
QFileInfo info(m_filename);
|
||||
return info.exists() && info.size() != 0;
|
||||
}
|
||||
}
|
||||
} // namespace Net
|
||||
|
@ -15,29 +15,26 @@
|
||||
|
||||
#include "HttpMetaCache.h"
|
||||
#include "FileSystem.h"
|
||||
#include "Json.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QFile>
|
||||
#include <QDateTime>
|
||||
#include <QCryptographicHash>
|
||||
#include <QDateTime>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
|
||||
QString MetaEntry::getFullPath()
|
||||
auto MetaEntry::getFullPath() -> QString
|
||||
{
|
||||
// FIXME: make local?
|
||||
return FS::PathCombine(basePath, relativePath);
|
||||
}
|
||||
|
||||
HttpMetaCache::HttpMetaCache(QString path) : QObject()
|
||||
HttpMetaCache::HttpMetaCache(QString path) : QObject(), m_index_file(path)
|
||||
{
|
||||
m_index_file = path;
|
||||
saveBatchingTimer.setSingleShot(true);
|
||||
saveBatchingTimer.setTimerType(Qt::VeryCoarseTimer);
|
||||
|
||||
connect(&saveBatchingTimer, SIGNAL(timeout()), SLOT(SaveNow()));
|
||||
}
|
||||
|
||||
@ -47,45 +44,42 @@ HttpMetaCache::~HttpMetaCache()
|
||||
SaveNow();
|
||||
}
|
||||
|
||||
MetaEntryPtr HttpMetaCache::getEntry(QString base, QString resource_path)
|
||||
auto HttpMetaCache::getEntry(QString base, QString resource_path) -> MetaEntryPtr
|
||||
{
|
||||
// no base. no base path. can't store
|
||||
if (!m_entries.contains(base))
|
||||
{
|
||||
if (!m_entries.contains(base)) {
|
||||
// TODO: log problem
|
||||
return MetaEntryPtr();
|
||||
return {};
|
||||
}
|
||||
EntryMap &map = m_entries[base];
|
||||
if (map.entry_list.contains(resource_path))
|
||||
{
|
||||
|
||||
EntryMap& map = m_entries[base];
|
||||
if (map.entry_list.contains(resource_path)) {
|
||||
return map.entry_list[resource_path];
|
||||
}
|
||||
return MetaEntryPtr();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
MetaEntryPtr HttpMetaCache::resolveEntry(QString base, QString resource_path, QString expected_etag)
|
||||
auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString expected_etag) -> MetaEntryPtr
|
||||
{
|
||||
auto entry = getEntry(base, resource_path);
|
||||
// it's not present? generate a default stale entry
|
||||
if (!entry)
|
||||
{
|
||||
if (!entry) {
|
||||
return staleEntry(base, resource_path);
|
||||
}
|
||||
|
||||
auto &selected_base = m_entries[base];
|
||||
auto& selected_base = m_entries[base];
|
||||
QString real_path = FS::PathCombine(selected_base.base_path, resource_path);
|
||||
QFileInfo finfo(real_path);
|
||||
|
||||
// is the file really there? if not -> stale
|
||||
if (!finfo.isFile() || !finfo.isReadable())
|
||||
{
|
||||
if (!finfo.isFile() || !finfo.isReadable()) {
|
||||
// if the file doesn't exist, we disown the entry
|
||||
selected_base.entry_list.remove(resource_path);
|
||||
return staleEntry(base, resource_path);
|
||||
}
|
||||
|
||||
if (!expected_etag.isEmpty() && expected_etag != entry->etag)
|
||||
{
|
||||
if (!expected_etag.isEmpty() && expected_etag != entry->etag) {
|
||||
// if the etag doesn't match expected, we disown the entry
|
||||
selected_base.entry_list.remove(resource_path);
|
||||
return staleEntry(base, resource_path);
|
||||
@ -93,18 +87,15 @@ MetaEntryPtr HttpMetaCache::resolveEntry(QString base, QString resource_path, QS
|
||||
|
||||
// if the file changed, check md5sum
|
||||
qint64 file_last_changed = finfo.lastModified().toUTC().toMSecsSinceEpoch();
|
||||
if (file_last_changed != entry->local_changed_timestamp)
|
||||
{
|
||||
if (file_last_changed != entry->local_changed_timestamp) {
|
||||
QFile input(real_path);
|
||||
input.open(QIODevice::ReadOnly);
|
||||
QString md5sum = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Md5)
|
||||
.toHex()
|
||||
.constData();
|
||||
if (entry->md5sum != md5sum)
|
||||
{
|
||||
QString md5sum = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Md5).toHex().constData();
|
||||
if (entry->md5sum != md5sum) {
|
||||
selected_base.entry_list.remove(resource_path);
|
||||
return staleEntry(base, resource_path);
|
||||
}
|
||||
|
||||
// md5sums matched... keep entry and save the new state to file
|
||||
entry->local_changed_timestamp = file_last_changed;
|
||||
SaveEventually();
|
||||
@ -115,42 +106,42 @@ MetaEntryPtr HttpMetaCache::resolveEntry(QString base, QString resource_path, QS
|
||||
return entry;
|
||||
}
|
||||
|
||||
bool HttpMetaCache::updateEntry(MetaEntryPtr stale_entry)
|
||||
auto HttpMetaCache::updateEntry(MetaEntryPtr stale_entry) -> bool
|
||||
{
|
||||
if (!m_entries.contains(stale_entry->baseId))
|
||||
{
|
||||
qCritical() << "Cannot add entry with unknown base: "
|
||||
<< stale_entry->baseId.toLocal8Bit();
|
||||
if (!m_entries.contains(stale_entry->baseId)) {
|
||||
qCritical() << "Cannot add entry with unknown base: " << stale_entry->baseId.toLocal8Bit();
|
||||
return false;
|
||||
}
|
||||
if (stale_entry->stale)
|
||||
{
|
||||
|
||||
if (stale_entry->stale) {
|
||||
qCritical() << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_entries[stale_entry->baseId].entry_list[stale_entry->relativePath] = stale_entry;
|
||||
SaveEventually();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto HttpMetaCache::evictEntry(MetaEntryPtr entry) -> bool
|
||||
{
|
||||
if (!entry)
|
||||
return false;
|
||||
|
||||
entry->stale = true;
|
||||
SaveEventually();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HttpMetaCache::evictEntry(MetaEntryPtr entry)
|
||||
{
|
||||
if(entry)
|
||||
{
|
||||
entry->stale = true;
|
||||
SaveEventually();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
MetaEntryPtr HttpMetaCache::staleEntry(QString base, QString resource_path)
|
||||
auto HttpMetaCache::staleEntry(QString base, QString resource_path) -> MetaEntryPtr
|
||||
{
|
||||
auto foo = new MetaEntry();
|
||||
foo->baseId = base;
|
||||
foo->basePath = getBasePath(base);
|
||||
foo->relativePath = resource_path;
|
||||
foo->stale = true;
|
||||
|
||||
return MetaEntryPtr(foo);
|
||||
}
|
||||
|
||||
@ -159,24 +150,25 @@ void HttpMetaCache::addBase(QString base, QString base_root)
|
||||
// TODO: report error
|
||||
if (m_entries.contains(base))
|
||||
return;
|
||||
|
||||
// TODO: check if the base path is valid
|
||||
EntryMap foo;
|
||||
foo.base_path = base_root;
|
||||
m_entries[base] = foo;
|
||||
}
|
||||
|
||||
QString HttpMetaCache::getBasePath(QString base)
|
||||
auto HttpMetaCache::getBasePath(QString base) -> QString
|
||||
{
|
||||
if (m_entries.contains(base))
|
||||
{
|
||||
if (m_entries.contains(base)) {
|
||||
return m_entries[base].base_path;
|
||||
}
|
||||
return QString();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void HttpMetaCache::Load()
|
||||
{
|
||||
if(m_index_file.isNull())
|
||||
if (m_index_file.isNull())
|
||||
return;
|
||||
|
||||
QFile index(m_index_file);
|
||||
@ -184,41 +176,35 @@ void HttpMetaCache::Load()
|
||||
return;
|
||||
|
||||
QJsonDocument json = QJsonDocument::fromJson(index.readAll());
|
||||
if (!json.isObject())
|
||||
return;
|
||||
auto root = json.object();
|
||||
|
||||
auto root = Json::requireObject(json, "HttpMetaCache root");
|
||||
|
||||
// check file version first
|
||||
auto version_val = root.value("version");
|
||||
if (!version_val.isString())
|
||||
return;
|
||||
if (version_val.toString() != "1")
|
||||
auto version_val = Json::ensureString(root, "version");
|
||||
if (version_val != "1")
|
||||
return;
|
||||
|
||||
// read the entry array
|
||||
auto entries_val = root.value("entries");
|
||||
if (!entries_val.isArray())
|
||||
return;
|
||||
QJsonArray array = entries_val.toArray();
|
||||
for (auto element : array)
|
||||
{
|
||||
if (!element.isObject())
|
||||
return;
|
||||
auto element_obj = element.toObject();
|
||||
QString base = element_obj.value("base").toString();
|
||||
auto array = Json::ensureArray(root, "entries");
|
||||
for (auto element : array) {
|
||||
auto element_obj = Json::ensureObject(element);
|
||||
auto base = Json::ensureString(element_obj, "base");
|
||||
if (!m_entries.contains(base))
|
||||
continue;
|
||||
auto &entrymap = m_entries[base];
|
||||
|
||||
auto& entrymap = m_entries[base];
|
||||
|
||||
auto foo = new MetaEntry();
|
||||
foo->baseId = base;
|
||||
QString path = foo->relativePath = element_obj.value("path").toString();
|
||||
foo->md5sum = element_obj.value("md5sum").toString();
|
||||
foo->etag = element_obj.value("etag").toString();
|
||||
foo->local_changed_timestamp = element_obj.value("last_changed_timestamp").toDouble();
|
||||
foo->remote_changed_timestamp =
|
||||
element_obj.value("remote_changed_timestamp").toString();
|
||||
foo->relativePath = Json::ensureString(element_obj, "path");
|
||||
foo->md5sum = Json::ensureString(element_obj, "md5sum");
|
||||
foo->etag = Json::ensureString(element_obj, "etag");
|
||||
foo->local_changed_timestamp = Json::ensureDouble(element_obj, "last_changed_timestamp");
|
||||
foo->remote_changed_timestamp = Json::ensureString(element_obj, "remote_changed_timestamp");
|
||||
// presumed innocent until closer examination
|
||||
foo->stale = false;
|
||||
entrymap.entry_list[path] = MetaEntryPtr(foo);
|
||||
|
||||
entrymap.entry_list[foo->relativePath] = MetaEntryPtr(foo);
|
||||
}
|
||||
}
|
||||
|
||||
@ -231,42 +217,36 @@ void HttpMetaCache::SaveEventually()
|
||||
|
||||
void HttpMetaCache::SaveNow()
|
||||
{
|
||||
if(m_index_file.isNull())
|
||||
if (m_index_file.isNull())
|
||||
return;
|
||||
|
||||
QJsonObject toplevel;
|
||||
toplevel.insert("version", QJsonValue(QString("1")));
|
||||
Json::writeString(toplevel, "version", "1");
|
||||
|
||||
QJsonArray entriesArr;
|
||||
for (auto group : m_entries)
|
||||
{
|
||||
for (auto entry : group.entry_list)
|
||||
{
|
||||
for (auto group : m_entries) {
|
||||
for (auto entry : group.entry_list) {
|
||||
// do not save stale entries. they are dead.
|
||||
if(entry->stale)
|
||||
{
|
||||
if (entry->stale) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QJsonObject entryObj;
|
||||
entryObj.insert("base", QJsonValue(entry->baseId));
|
||||
entryObj.insert("path", QJsonValue(entry->relativePath));
|
||||
entryObj.insert("md5sum", QJsonValue(entry->md5sum));
|
||||
entryObj.insert("etag", QJsonValue(entry->etag));
|
||||
entryObj.insert("last_changed_timestamp",
|
||||
QJsonValue(double(entry->local_changed_timestamp)));
|
||||
Json::writeString(entryObj, "base", entry->baseId);
|
||||
Json::writeString(entryObj, "path", entry->relativePath);
|
||||
Json::writeString(entryObj, "md5sum", entry->md5sum);
|
||||
Json::writeString(entryObj, "etag", entry->etag);
|
||||
entryObj.insert("last_changed_timestamp", QJsonValue(double(entry->local_changed_timestamp)));
|
||||
if (!entry->remote_changed_timestamp.isEmpty())
|
||||
entryObj.insert("remote_changed_timestamp",
|
||||
QJsonValue(entry->remote_changed_timestamp));
|
||||
entryObj.insert("remote_changed_timestamp", QJsonValue(entry->remote_changed_timestamp));
|
||||
entriesArr.append(entryObj);
|
||||
}
|
||||
}
|
||||
toplevel.insert("entries", entriesArr);
|
||||
|
||||
QJsonDocument doc(toplevel);
|
||||
try
|
||||
{
|
||||
FS::write(m_index_file, doc.toJson());
|
||||
}
|
||||
catch (const Exception &e)
|
||||
{
|
||||
try {
|
||||
Json::write(toplevel, m_index_file);
|
||||
} catch (const Exception& e) {
|
||||
qWarning() << e.what();
|
||||
}
|
||||
}
|
||||
|
@ -14,109 +14,88 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <QString>
|
||||
#include <QMap>
|
||||
#include <qtimer.h>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <memory>
|
||||
|
||||
class HttpMetaCache;
|
||||
|
||||
class MetaEntry
|
||||
{
|
||||
friend class HttpMetaCache;
|
||||
protected:
|
||||
MetaEntry() {}
|
||||
public:
|
||||
bool isStale()
|
||||
{
|
||||
return stale;
|
||||
}
|
||||
void setStale(bool stale)
|
||||
{
|
||||
this->stale = stale;
|
||||
}
|
||||
QString getFullPath();
|
||||
QString getRemoteChangedTimestamp()
|
||||
{
|
||||
return remote_changed_timestamp;
|
||||
}
|
||||
void setRemoteChangedTimestamp(QString remote_changed_timestamp)
|
||||
{
|
||||
this->remote_changed_timestamp = remote_changed_timestamp;
|
||||
}
|
||||
void setLocalChangedTimestamp(qint64 timestamp)
|
||||
{
|
||||
local_changed_timestamp = timestamp;
|
||||
}
|
||||
QString getETag()
|
||||
{
|
||||
return etag;
|
||||
}
|
||||
void setETag(QString etag)
|
||||
{
|
||||
this->etag = etag;
|
||||
}
|
||||
QString getMD5Sum()
|
||||
{
|
||||
return md5sum;
|
||||
}
|
||||
void setMD5Sum(QString md5sum)
|
||||
{
|
||||
this->md5sum = md5sum;
|
||||
}
|
||||
protected:
|
||||
class MetaEntry {
|
||||
friend class HttpMetaCache;
|
||||
|
||||
protected:
|
||||
MetaEntry() = default;
|
||||
|
||||
public:
|
||||
auto isStale() -> bool { return stale; }
|
||||
void setStale(bool stale) { this->stale = stale; }
|
||||
|
||||
auto getFullPath() -> QString;
|
||||
|
||||
auto getRemoteChangedTimestamp() -> QString { return remote_changed_timestamp; }
|
||||
void setRemoteChangedTimestamp(QString remote_changed_timestamp) { this->remote_changed_timestamp = remote_changed_timestamp; }
|
||||
void setLocalChangedTimestamp(qint64 timestamp) { local_changed_timestamp = timestamp; }
|
||||
|
||||
auto getETag() -> QString { return etag; }
|
||||
void setETag(QString etag) { this->etag = etag; }
|
||||
|
||||
auto getMD5Sum() -> QString { return md5sum; }
|
||||
void setMD5Sum(QString md5sum) { this->md5sum = md5sum; }
|
||||
|
||||
protected:
|
||||
QString baseId;
|
||||
QString basePath;
|
||||
QString relativePath;
|
||||
QString md5sum;
|
||||
QString etag;
|
||||
qint64 local_changed_timestamp = 0;
|
||||
QString remote_changed_timestamp; // QString for now, RFC 2822 encoded time
|
||||
QString remote_changed_timestamp; // QString for now, RFC 2822 encoded time
|
||||
bool stale = true;
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<MetaEntry> MetaEntryPtr;
|
||||
using MetaEntryPtr = std::shared_ptr<MetaEntry>;
|
||||
|
||||
class HttpMetaCache : public QObject
|
||||
{
|
||||
class HttpMetaCache : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
public:
|
||||
// supply path to the cache index file
|
||||
HttpMetaCache(QString path = QString());
|
||||
~HttpMetaCache();
|
||||
~HttpMetaCache() override;
|
||||
|
||||
// get the entry solely from the cache
|
||||
// you probably don't want this, unless you have some specific caching needs.
|
||||
MetaEntryPtr getEntry(QString base, QString resource_path);
|
||||
auto getEntry(QString base, QString resource_path) -> MetaEntryPtr;
|
||||
|
||||
// get the entry from cache and verify that it isn't stale (within reason)
|
||||
MetaEntryPtr resolveEntry(QString base, QString resource_path,
|
||||
QString expected_etag = QString());
|
||||
auto resolveEntry(QString base, QString resource_path, QString expected_etag = QString()) -> MetaEntryPtr;
|
||||
|
||||
// add a previously resolved stale entry
|
||||
bool updateEntry(MetaEntryPtr stale_entry);
|
||||
auto updateEntry(MetaEntryPtr stale_entry) -> bool;
|
||||
|
||||
// evict selected entry from cache
|
||||
bool evictEntry(MetaEntryPtr entry);
|
||||
auto evictEntry(MetaEntryPtr entry) -> bool;
|
||||
|
||||
void addBase(QString base, QString base_root);
|
||||
|
||||
// (re)start a timer that calls SaveNow later.
|
||||
void SaveEventually();
|
||||
void Load();
|
||||
QString getBasePath(QString base);
|
||||
public
|
||||
slots:
|
||||
|
||||
auto getBasePath(QString base) -> QString;
|
||||
|
||||
public slots:
|
||||
void SaveNow();
|
||||
|
||||
private:
|
||||
private:
|
||||
// create a new stale entry, given the parameters
|
||||
MetaEntryPtr staleEntry(QString base, QString resource_path);
|
||||
struct EntryMap
|
||||
{
|
||||
auto staleEntry(QString base, QString resource_path) -> MetaEntryPtr;
|
||||
|
||||
struct EntryMap {
|
||||
QString base_path;
|
||||
QMap<QString, MetaEntryPtr> entry_list;
|
||||
};
|
||||
|
||||
QMap<QString, EntryMap> m_entries;
|
||||
QString m_index_file;
|
||||
QTimer saveBatchingTimer;
|
||||
|
@ -1,10 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
namespace Net
|
||||
{
|
||||
enum class Mode
|
||||
{
|
||||
Offline,
|
||||
Online
|
||||
};
|
||||
namespace Net {
|
||||
enum class Mode { Offline, Online };
|
||||
}
|
||||
|
@ -8,14 +8,15 @@ namespace Net {
|
||||
class Sink {
|
||||
public:
|
||||
Sink() = default;
|
||||
virtual ~Sink(){};
|
||||
virtual ~Sink() = default;
|
||||
|
||||
public:
|
||||
virtual Task::State init(QNetworkRequest& request) = 0;
|
||||
virtual Task::State write(QByteArray& data) = 0;
|
||||
virtual Task::State abort() = 0;
|
||||
virtual Task::State finalize(QNetworkReply& reply) = 0;
|
||||
virtual bool hasLocalData() = 0;
|
||||
virtual auto init(QNetworkRequest& request) -> Task::State = 0;
|
||||
virtual auto write(QByteArray& data) -> Task::State = 0;
|
||||
virtual auto abort() -> Task::State = 0;
|
||||
virtual auto finalize(QNetworkReply& reply) -> Task::State = 0;
|
||||
|
||||
virtual auto hasLocalData() -> bool = 0;
|
||||
|
||||
void addValidator(Validator* validator)
|
||||
{
|
||||
@ -24,7 +25,15 @@ class Sink {
|
||||
}
|
||||
}
|
||||
|
||||
protected: /* methods */
|
||||
protected:
|
||||
bool initAllValidators(QNetworkRequest& request)
|
||||
{
|
||||
for (auto& validator : validators) {
|
||||
if (!validator->init(request))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool finalizeAllValidators(QNetworkReply& reply)
|
||||
{
|
||||
for (auto& validator : validators) {
|
||||
@ -41,14 +50,6 @@ class Sink {
|
||||
}
|
||||
return success;
|
||||
}
|
||||
bool initAllValidators(QNetworkRequest& request)
|
||||
{
|
||||
for (auto& validator : validators) {
|
||||
if (!validator->init(request))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool writeAllValidators(QByteArray& data)
|
||||
{
|
||||
for (auto& validator : validators) {
|
||||
@ -58,7 +59,7 @@ class Sink {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected: /* data */
|
||||
protected:
|
||||
std::vector<std::shared_ptr<Validator>> validators;
|
||||
};
|
||||
} // namespace Net
|
||||
|
Loading…
Reference in New Issue
Block a user