mirror of
https://github.com/nextcloud/desktop.git
synced 2024-10-27 06:45:49 +03:00
Generalize Result<> class, add Optional<>
To make it nicer to use outside of HTTP results.
This commit is contained in:
parent
e7e6b839c0
commit
f502a526fa
6 changed files with 57 additions and 32 deletions
|
@ -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;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue