Generalize Result<> class, add Optional<>

To make it nicer to use outside of HTTP results.
This commit is contained in:
Christian Kamm 2018-11-15 08:32:17 +01:00 committed by Kevin Ottens
parent e7e6b839c0
commit f502a526fa
No known key found for this signature in database
GPG key ID: 074BBBCB8DECC9E2
6 changed files with 57 additions and 32 deletions

View file

@ -14,21 +14,16 @@
#pragma once #pragma once
#include "asserts.h"
namespace OCC { namespace OCC {
/** /**
* A Result of type T, or an Error that contains a code and a string * A Result of type T, or an Error
* */
* The code is an HTTP error code. template <typename T, typename Error>
**/
template <typename T>
class Result class Result
{ {
struct Error
{
QString string;
int code;
};
union { union {
T _result; T _result;
Error _error; Error _error;
@ -38,12 +33,16 @@ class Result
public: public:
Result(T value) Result(T value)
: _result(std::move(value)) : _result(std::move(value))
, _isError(false){}; , _isError(false)
Result(int code, QString str) {
: _error({ std::move(str), code }) }
// TODO: This doesn't work if T and Error are too similar
Result(Error error)
: _error(std::move(error))
, _isError(true) , _isError(true)
{ {
} }
~Result() ~Result()
{ {
if (_isError) if (_isError)
@ -62,14 +61,31 @@ public:
ASSERT(!_isError); ASSERT(!_isError);
return std::move(_result); return std::move(_result);
} }
QString errorMessage() const const Error &error() const &
{ {
ASSERT(_isError); ASSERT(_isError);
return _error.string; return _error;
} }
int errorCode() const Error error() &&
{
ASSERT(_isError);
return std::move(_error);
}
};
namespace detail {
struct OptionalNoErrorData{};
}
template <typename T>
class Optional : public Result<T, detail::OptionalNoErrorData>
{
public:
using Result<T, detail::OptionalNoErrorData>::Result;
Optional()
: Optional(detail::OptionalNoErrorData{})
{ {
return _isError ? _error.code : 0;
} }
}; };

View file

@ -522,10 +522,10 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(
// we need to make a request to the server to know that the original file is deleted on the server // we need to make a request to the server to know that the original file is deleted on the server
_pendingAsyncJobs++; _pendingAsyncJobs++;
auto job = new RequestEtagJob(_discoveryData->_account, originalPath, this); auto job = new RequestEtagJob(_discoveryData->_account, originalPath, this);
connect(job, &RequestEtagJob::finishedWithResult, this, [=](const Result<QString> &etag) mutable { connect(job, &RequestEtagJob::finishedWithResult, this, [=](const HttpResult<QString> &etag) mutable {
_pendingAsyncJobs--; _pendingAsyncJobs--;
QTimer::singleShot(0, _discoveryData, &DiscoveryPhase::scheduleMoreJobs); QTimer::singleShot(0, _discoveryData, &DiscoveryPhase::scheduleMoreJobs);
if (etag.errorCode() != 404 || if (etag || etag.error().code != 404 ||
// Somehow another item claimed this original path, consider as if it existed // Somehow another item claimed this original path, consider as if it existed
_discoveryData->_renamedItems.contains(originalPath)) { _discoveryData->_renamedItems.contains(originalPath)) {
// If the file exist or if there is another error, consider it is a new file. // If the file exist or if there is another error, consider it is a new file.
@ -828,7 +828,7 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo(
if (base.isVirtualFile() && isVfsWithSuffix()) if (base.isVirtualFile() && isVfsWithSuffix())
chopVirtualFileSuffix(serverOriginalPath); chopVirtualFileSuffix(serverOriginalPath);
auto job = new RequestEtagJob(_discoveryData->_account, serverOriginalPath, this); auto job = new RequestEtagJob(_discoveryData->_account, serverOriginalPath, this);
connect(job, &RequestEtagJob::finishedWithResult, this, [=](const Result<QString> &etag) mutable { connect(job, &RequestEtagJob::finishedWithResult, this, [=](const HttpResult<QString> &etag) mutable {
if (!etag || (*etag != base._etag && !item->isDirectory()) || _discoveryData->_renamedItems.contains(originalPath)) { if (!etag || (*etag != base._etag && !item->isDirectory()) || _discoveryData->_renamedItems.contains(originalPath)) {
qCInfo(lcDisco) << "Can't rename because the etag has changed or the directory is gone" << originalPath; qCInfo(lcDisco) << "Can't rename because the etag has changed or the directory is gone" << originalPath;
// Can't be a rename, leave it as a new. // Can't be a rename, leave it as a new.
@ -1220,17 +1220,17 @@ DiscoverySingleDirectoryJob *ProcessDirectoryJob::startAsyncServerQuery()
if (_localQueryDone) if (_localQueryDone)
process(); process();
} else { } else {
if (results.errorCode() == 403) { if (results.error().code == 403) {
// 403 Forbidden can be sent by the server if the file firewall is active. // 403 Forbidden can be sent by the server if the file firewall is active.
// A file or directory should be ignored and sync must continue. See #3490 // A file or directory should be ignored and sync must continue. See #3490
qCWarning(lcDisco, "Directory access Forbidden (File Firewall?)"); qCWarning(lcDisco, "Directory access Forbidden (File Firewall?)");
if (_dirItem) { if (_dirItem) {
_dirItem->_instruction = CSYNC_INSTRUCTION_IGNORE; _dirItem->_instruction = CSYNC_INSTRUCTION_IGNORE;
_dirItem->_errorString = results.errorMessage(); _dirItem->_errorString = results.error().message;
emit finished(); emit finished();
return; return;
} }
} else if (results.errorCode() == 503) { } else if (results.error().code == 503) {
// The server usually replies with the custom "503 Storage not available" // The server usually replies with the custom "503 Storage not available"
// if some path is temporarily unavailable. But in some cases a standard 503 // if some path is temporarily unavailable. But in some cases a standard 503
// is returned too. Thus we can't distinguish the two and will treat any // is returned too. Thus we can't distinguish the two and will treat any
@ -1238,13 +1238,13 @@ DiscoverySingleDirectoryJob *ProcessDirectoryJob::startAsyncServerQuery()
qCWarning(lcDisco(), "Storage was not available!"); qCWarning(lcDisco(), "Storage was not available!");
if (_dirItem) { if (_dirItem) {
_dirItem->_instruction = CSYNC_INSTRUCTION_IGNORE; _dirItem->_instruction = CSYNC_INSTRUCTION_IGNORE;
_dirItem->_errorString = results.errorMessage(); _dirItem->_errorString = results.error().message;
emit finished(); emit finished();
return; return;
} }
} }
emit _discoveryData->fatalError(tr("Server replied with an error while reading directory '%1' : %2") emit _discoveryData->fatalError(tr("Server replied with an error while reading directory '%1' : %2")
.arg(_currentFolder._server, results.errorMessage())); .arg(_currentFolder._server, results.error().message));
} }
}); });
connect(serverJob, &DiscoverySingleDirectoryJob::firstDirectoryPermissions, this, connect(serverJob, &DiscoverySingleDirectoryJob::firstDirectoryPermissions, this,

View file

@ -355,11 +355,11 @@ void DiscoverySingleDirectoryJob::lsJobFinishedWithoutErrorSlot()
if (!_ignoredFirst) { if (!_ignoredFirst) {
// This is a sanity check, if we haven't _ignoredFirst then it means we never received any directoryListingIteratedSlot // This is a sanity check, if we haven't _ignoredFirst then it means we never received any directoryListingIteratedSlot
// which means somehow the server XML was bogus // which means somehow the server XML was bogus
emit finished({ 0, tr("Server error: PROPFIND reply is not XML formatted!") }); emit finished(HttpError{ 0, tr("Server error: PROPFIND reply is not XML formatted!") });
deleteLater(); deleteLater();
return; return;
} else if (!_error.isEmpty()) { } else if (!_error.isEmpty()) {
emit finished({ 0, _error }); emit finished(HttpError{ 0, _error });
deleteLater(); deleteLater();
return; return;
} }
@ -379,7 +379,7 @@ void DiscoverySingleDirectoryJob::lsJobFinishedWithErrorSlot(QNetworkReply *r)
&& !contentType.contains("application/xml; charset=utf-8")) { && !contentType.contains("application/xml; charset=utf-8")) {
msg = tr("Server error: PROPFIND reply is not XML formatted!"); msg = tr("Server error: PROPFIND reply is not XML formatted!");
} }
emit finished({httpCode, msg}); emit finished(HttpError{ httpCode, msg });
deleteLater(); deleteLater();
} }
} }

View file

@ -97,7 +97,7 @@ public:
signals: signals:
void firstDirectoryPermissions(RemotePermissions); void firstDirectoryPermissions(RemotePermissions);
void etag(const QString &); void etag(const QString &);
void finished(const Result<QVector<RemoteInfo>> &result); void finished(const HttpResult<QVector<RemoteInfo>> &result);
private slots: private slots:
void directoryListingIteratedSlot(QString, const QMap<QString, QString> &); void directoryListingIteratedSlot(QString, const QMap<QString, QString> &);
void lsJobFinishedWithoutErrorSlot(); void lsJobFinishedWithoutErrorSlot();

View file

@ -107,7 +107,7 @@ bool RequestEtagJob::finished()
emit etagRetrieved(etag); emit etagRetrieved(etag);
emit finishedWithResult(etag); emit finishedWithResult(etag);
} else { } else {
emit finishedWithResult({ httpCode, errorString() }); emit finishedWithResult(HttpError{ httpCode, errorString() });
} }
return true; return true;
} }

View file

@ -18,7 +18,7 @@
#include "abstractnetworkjob.h" #include "abstractnetworkjob.h"
#include "result.h" #include "common/result.h"
#include <QBuffer> #include <QBuffer>
#include <QUrlQuery> #include <QUrlQuery>
@ -29,6 +29,15 @@ class QJsonObject;
namespace OCC { namespace OCC {
struct HttpError
{
int code; // HTTP error code
QString message;
};
template <typename T>
using HttpResult = Result<T, HttpError>;
/** /**
* @brief The EntityExistsJob class * @brief The EntityExistsJob class
* @ingroup libsync * @ingroup libsync
@ -337,7 +346,7 @@ public:
signals: signals:
void etagRetrieved(const QString &etag); void etagRetrieved(const QString &etag);
void finishedWithResult(const Result<QString> &etag); void finishedWithResult(const HttpResult<QString> &etag);
private slots: private slots:
bool finished() override; bool finished() override;