From 7b54352c3f0fc3d7ad08e7836c45586eb7500f8f Mon Sep 17 00:00:00 2001 From: Phil Hoffmann Date: Sun, 17 Dec 2023 16:39:03 +0100 Subject: [PATCH] Changed service and file api to use enum flags instead of booleans --- src/filesystem/CMakeLists.txt | 1 + src/filesystem/file.cpp | 16 +- src/filesystem/file.h | 9 +- src/filesystem/fileaccess.cpp | 16 +- src/filesystem/fileaccess.h | 13 +- src/filesystem/fileaccesslocal.cpp | 36 ++- src/filesystem/fileaccesslocal.h | 12 +- src/filesystem/fileaccessnextcloud.cpp | 189 +++++------ src/filesystem/fileaccessnextcloud.h | 15 +- src/filesystem/options.h | 18 ++ src/services/CMakeLists.txt | 3 + src/services/nextcloud/nextcloud.cpp | 232 +++----------- src/services/nextcloud/nextcloud.h | 25 +- src/services/nextcloud/nextcloudconnector.cpp | 297 ++++++++++++++++++ src/services/nextcloud/nextcloudconnector.h | 59 ++++ src/services/options.h | 17 + src/services/rest/callbackserver.cpp | 6 +- src/services/rest/restrequest.h | 24 +- src/services/rest/restserviceconnector.cpp | 11 +- src/services/rest/restserviceconnector.h | 11 +- .../rest/restserviceconnectorlocal.cpp | 30 +- src/services/rest/restserviceconnectorlocal.h | 10 +- src/services/spotify/api/albumapi.cpp | 32 +- src/services/spotify/api/albumapi.h | 9 +- src/services/spotify/api/playerapi.cpp | 31 +- src/services/spotify/api/playlistsapi.cpp | 24 +- src/services/spotify/api/playlistsapi.h | 9 +- src/services/spotify/api/tracksapi.cpp | 16 +- src/services/spotify/api/tracksapi.h | 7 +- src/services/spotify/spotify.cpp | 20 +- src/services/spotify/spotify.h | 11 +- .../spotify/spotifyconnectorserver.cpp | 34 +- src/services/spotify/spotifyconnectorserver.h | 12 +- src/services/youtube/pipedconnector.cpp | 26 +- src/services/youtube/pipedconnector.h | 10 +- src/services/youtube/youtube.cpp | 31 +- src/services/youtube/youtube.h | 8 +- src/tools/audio/audiosaveload.cpp | 4 +- .../audio/players/bufferedaudioplayer.cpp | 4 +- .../audio/playlist/resolvingaudioplaylist.cpp | 32 +- .../thumbnails/loaders/fileimageloader.cpp | 2 +- .../thumbnails/loaders/spotifyimageloader.cpp | 8 +- .../thumbnails/loaders/tagimageloader.cpp | 2 +- .../thumbnails/loaders/youtubeimageloader.cpp | 4 +- src/tools/characters/character.cpp | 11 +- src/tools/characters/charactertool.cpp | 9 +- src/tools/maps/map.cpp | 21 +- src/tools/notes/notessaveload.cpp | 2 +- src/tools/shop/baseshoptool.cpp | 4 +- src/tools/shop/itemeditor.cpp | 2 +- src/tools/shop/shopeditor.cpp | 23 +- tests/filesystem/abstractaccesstest.cpp | 14 +- tests/filesystem/testnextcloudaccess.cpp | 2 +- tests/services/rest/testrestrequest.cpp | 28 +- tests/services/youtube/testpipedconnector.cpp | 8 +- tests/services/youtube/testyoutube.cpp | 8 +- tests/testhelper/abstracttest.cpp | 10 +- tests/testhelper/abstracttest.h | 3 +- 58 files changed, 884 insertions(+), 647 deletions(-) create mode 100644 src/filesystem/options.h create mode 100644 src/services/nextcloud/nextcloudconnector.cpp create mode 100644 src/services/nextcloud/nextcloudconnector.h create mode 100644 src/services/options.h diff --git a/src/filesystem/CMakeLists.txt b/src/filesystem/CMakeLists.txt index 3fff5d4b..829c9307 100755 --- a/src/filesystem/CMakeLists.txt +++ b/src/filesystem/CMakeLists.txt @@ -7,6 +7,7 @@ set(SRC_COMMON fileaccess.cpp fileaccessswitcher.h multigethelper.h + options.h cache/filecache.h cache/filecache.cpp cache/filecacheentry.h diff --git a/src/filesystem/file.cpp b/src/filesystem/file.cpp index baeed5ea..f32ee37d 100755 --- a/src/filesystem/file.cpp +++ b/src/filesystem/file.cpp @@ -17,18 +17,18 @@ void File::init(Services::NextCloud *nc) s_nc = nc; } -auto File::getDataAsync(const QString &path, bool allowCache, std::shared_ptr fileAccess) +auto File::getDataAsync(const QString &path, Options options, std::shared_ptr fileAccess) -> QFuture { auto access = getFileAccess(fileAccess); - return access ? access->getDataAsync(path, allowCache) : QFuture(); + return access ? access->getDataAsync(path, options) : QFuture(); } -auto File::getDataAsync(const QStringList &paths, bool allowCache, std::shared_ptr fileAccess) +auto File::getDataAsync(const QStringList &paths, Options options, std::shared_ptr fileAccess) -> QFuture> { auto access = getFileAccess(fileAccess); - return access ? access->getDataAsync(paths, allowCache) : QFuture>(); + return access ? access->getDataAsync(paths, options) : QFuture>(); } auto File::saveAsync(const QString &path, const QByteArray &data, std::shared_ptr fileAccess) @@ -71,18 +71,18 @@ auto File::createDirAsync(const QString &path, std::shared_ptr fileA return access ? access->createDirAsync(path) : QFuture(); } -auto File::checkAsync(const QString &path, bool allowCache, std::shared_ptr fileAccess) +auto File::checkAsync(const QString &path, Options options, std::shared_ptr fileAccess) -> QFuture { auto access = getFileAccess(fileAccess); - return access ? access->checkAsync(path, allowCache) : QFuture(); + return access ? access->checkAsync(path, options) : QFuture(); } -auto File::checkAsync(const QStringList &paths, bool allowCache, std::shared_ptr fileAccess) +auto File::checkAsync(const QStringList &paths, Options options, std::shared_ptr fileAccess) -> QFuture { auto access = getFileAccess(fileAccess); - return access ? access->checkAsync(paths, allowCache) : QFuture(); + return access ? access->checkAsync(paths, options) : QFuture(); } void File::updateFileAccess() diff --git a/src/filesystem/file.h b/src/filesystem/file.h index b5540008..87bb6c90 100755 --- a/src/filesystem/file.h +++ b/src/filesystem/file.h @@ -1,5 +1,6 @@ #pragma once +#include "options.h" #include #include #include @@ -27,10 +28,10 @@ class File public: static void init(Services::NextCloud *nc); - static auto getDataAsync(const QString &path, bool allowCache = true, + static auto getDataAsync(const QString &path, Options options = Option::AllowCache, std::shared_ptr fileAccess = nullptr) -> QFuture; - static auto getDataAsync(const QStringList &paths, bool allowCache = true, + static auto getDataAsync(const QStringList &paths, Options options = Option::AllowCache, std::shared_ptr fileAccess = nullptr) -> QFuture>; static auto saveAsync(const QString &path, const QByteArray &data, std::shared_ptr fileAccess = nullptr) @@ -51,10 +52,10 @@ class File static auto createDirAsync(const QString &path, std::shared_ptr fileAccess = nullptr) -> QFuture; - static auto checkAsync(const QString &path, bool allowCache = true, + static auto checkAsync(const QString &path, Options options = Option::AllowCache, std::shared_ptr fileAccess = nullptr) -> QFuture; - static auto checkAsync(const QStringList &paths, bool allowCache = true, + static auto checkAsync(const QStringList &paths, Options options = Option::AllowCache, std::shared_ptr fileAccess = nullptr) -> QFuture; static void updateFileAccess(); diff --git a/src/filesystem/fileaccess.cpp b/src/filesystem/fileaccess.cpp index 2a1fd56a..2d839787 100644 --- a/src/filesystem/fileaccess.cpp +++ b/src/filesystem/fileaccess.cpp @@ -2,7 +2,7 @@ using namespace Files; -auto FileAccess::multiGetDataAsync(MultiGetHelper &&helper, bool allowCache) +auto FileAccess::multiGetDataAsync(MultiGetHelper &&helper, Options options) -> QFuture> { if (helper.isDone()) @@ -10,17 +10,17 @@ auto FileAccess::multiGetDataAsync(MultiGetHelper &&helper, bool return QtFuture::makeReadyFuture(helper.getResults()); } - auto next = getDataAsync(helper.getNextPath(), allowCache); + auto next = getDataAsync(helper.getNextPath(), options); return next - .then([this, helper = std::move(helper), allowCache](FileDataResult &&result) mutable { + .then([this, helper = std::move(helper), options](FileDataResult &&result) mutable { helper.addResult(std::move(result)); - return multiGetDataAsync(std::move(helper), allowCache); + return multiGetDataAsync(std::move(helper), options); }) .unwrap(); } -auto FileAccess::multiCheckAsync(MultiGetHelper &&helper, bool allowCache) +auto FileAccess::multiCheckAsync(MultiGetHelper &&helper, Options options) -> QFuture { if (helper.isDone()) @@ -28,12 +28,12 @@ auto FileAccess::multiCheckAsync(MultiGetHelper &&helper, bool return QtFuture::makeReadyFuture(FileMultiCheckResult(helper.getResults())); } - auto next = checkAsync(helper.getNextPath(), allowCache); + auto next = checkAsync(helper.getNextPath(), options); return next - .then([this, helper = std::move(helper), allowCache](FileCheckResult &&result) mutable { + .then([this, helper = std::move(helper), options](FileCheckResult &&result) mutable { helper.addResult(std::move(result)); - return multiCheckAsync(std::move(helper), allowCache); + return multiCheckAsync(std::move(helper), options); }) .unwrap(); } diff --git a/src/filesystem/fileaccess.h b/src/filesystem/fileaccess.h index 9a23ccc5..c98b9016 100755 --- a/src/filesystem/fileaccess.h +++ b/src/filesystem/fileaccess.h @@ -1,6 +1,7 @@ #pragma once #include "multigethelper.h" +#include "options.h" #include "results/filecheckresult.h" #include "results/filedataresult.h" #include "results/filelistresult.h" @@ -20,16 +21,16 @@ class FileAccess virtual ~FileAccess() = default; Q_DISABLE_COPY_MOVE(FileAccess); - virtual auto getDataAsync(const QString &path, bool allowCache) -> QFuture = 0; - virtual auto getDataAsync(const QStringList &paths, bool allowCache) -> QFuture> = 0; + virtual auto getDataAsync(const QString &path, Options options) -> QFuture = 0; + virtual auto getDataAsync(const QStringList &paths, Options options) -> QFuture> = 0; virtual auto saveAsync(const QString &path, const QByteArray &data) -> QFuture = 0; virtual auto moveAsync(const QString &oldPath, const QString &newPath) -> QFuture = 0; virtual auto deleteAsync(const QString &path) -> QFuture = 0; virtual auto copyAsync(const QString &path, const QString ©) -> QFuture = 0; virtual auto listAsync(const QString &path, bool files, bool folders) -> QFuture = 0; virtual auto createDirAsync(const QString &path) -> QFuture = 0; - virtual auto checkAsync(const QString &path, bool allowCache) -> QFuture = 0; - virtual auto checkAsync(const QStringList &paths, bool allowCache) -> QFuture = 0; + virtual auto checkAsync(const QString &path, Options options) -> QFuture = 0; + virtual auto checkAsync(const QStringList &paths, Options options) -> QFuture = 0; static auto getInstance() -> std::shared_ptr { @@ -42,9 +43,9 @@ class FileAccess } protected: - auto multiGetDataAsync(MultiGetHelper &&helper, bool allowCache) + auto multiGetDataAsync(MultiGetHelper &&helper, Options options) -> QFuture>; - auto multiCheckAsync(MultiGetHelper &&helper, bool allowCache) -> QFuture; + auto multiCheckAsync(MultiGetHelper &&helper, Options options) -> QFuture; private: inline static std::shared_ptr instance = nullptr; diff --git a/src/filesystem/fileaccesslocal.cpp b/src/filesystem/fileaccesslocal.cpp index 0fd77a1b..f3b31a41 100755 --- a/src/filesystem/fileaccesslocal.cpp +++ b/src/filesystem/fileaccesslocal.cpp @@ -11,10 +11,8 @@ using namespace Files; Q_LOGGING_CATEGORY(gmFileAccessLocal, "gm.files.access.local") /// Read data from one file -auto FileAccessLocal::getData(const QString &path, bool allowCache) -> FileDataResult +auto FileAccessLocal::getData(const QString &path) -> FileDataResult { - Q_UNUSED(allowCache) - QFile f(path); if (f.open(QIODevice::ReadOnly)) @@ -29,27 +27,31 @@ auto FileAccessLocal::getData(const QString &path, bool allowCache) -> FileDataR } /// Read data from one file async -auto FileAccessLocal::getDataAsync(const QString &path, bool allowCache) -> QFuture +auto FileAccessLocal::getDataAsync(const QString &path, Options options) -> QFuture { + Q_UNUSED(options) + qCDebug(gmFileAccessLocal()) << "Getting data from file:" << path << "..."; - return QtConcurrent::run(&FileAccessLocal::getData, path, allowCache).then(&m_context, [](FileDataResult &&result) { + return QtConcurrent::run(&FileAccessLocal::getData, path).then(&m_context, [](FileDataResult &&result) { return std::move(result); }); } /// Read data from multiple files -auto FileAccessLocal::getDataAsync(const QStringList &paths, bool allowCache) -> QFuture> +auto FileAccessLocal::getDataAsync(const QStringList &paths, Options options) -> QFuture> { + Q_UNUSED(options) + qCDebug(gmFileAccessLocal()) << "Getting data from multiple files:" << paths << "..."; - return QtConcurrent::run([paths, allowCache]() { + return QtConcurrent::run([paths]() { std::vector results; results.reserve(paths.size()); foreach (const auto &path, paths) { - results.push_back(getData(path, allowCache)); + results.push_back(getData(path)); } return results; @@ -246,30 +248,32 @@ auto FileAccessLocal::listAsync(const QString &path, bool files, bool folders) - } /// Check if a file exists -auto FileAccessLocal::check(const QString &path, bool allowCache) -> FileCheckResult +auto FileAccessLocal::check(const QString &path) -> FileCheckResult { - Q_UNUSED(allowCache) - QFile const f(path); return FileCheckResult(path, f.exists()); } /// Check if a file exists async -auto FileAccessLocal::checkAsync(const QString &path, bool allowCache) -> QFuture +auto FileAccessLocal::checkAsync(const QString &path, Options options) -> QFuture { - return QtConcurrent::run(&FileAccessLocal::check, path, allowCache).then(&m_context, [](FileCheckResult &&result) { + Q_UNUSED(options) + + return QtConcurrent::run(&FileAccessLocal::check, path).then(&m_context, [](FileCheckResult &&result) { return std::move(result); }); } /// Check which files exist -auto FileAccessLocal::checkAsync(const QStringList &paths, bool allowCache) -> QFuture +auto FileAccessLocal::checkAsync(const QStringList &paths, Options options) -> QFuture { - return QtConcurrent::run([paths, allowCache]() { + Q_UNUSED(options) + + return QtConcurrent::run([paths]() { FileMultiCheckResult result(true); for (const auto &path : paths) { - result.add(check(path, allowCache)); + result.add(check(path)); } return result; }) diff --git a/src/filesystem/fileaccesslocal.h b/src/filesystem/fileaccesslocal.h index 2dcd36e7..50802046 100755 --- a/src/filesystem/fileaccesslocal.h +++ b/src/filesystem/fileaccesslocal.h @@ -14,25 +14,25 @@ class FileAccessLocal : public FileAccess ~FileAccessLocal() override = default; Q_DISABLE_COPY_MOVE(FileAccessLocal) - auto getDataAsync(const QString &path, bool allowCache) -> QFuture override; - auto getDataAsync(const QStringList &paths, bool allowCache) -> QFuture> override; + auto getDataAsync(const QString &path, Options options) -> QFuture override; + auto getDataAsync(const QStringList &paths, Options options) -> QFuture> override; auto saveAsync(const QString &path, const QByteArray &data) -> QFuture override; auto moveAsync(const QString &oldPath, const QString &newPath) -> QFuture override; auto deleteAsync(const QString &path) -> QFuture override; auto copyAsync(const QString &path, const QString ©) -> QFuture override; auto listAsync(const QString &path, bool files, bool folders) -> QFuture override; auto createDirAsync(const QString &path) -> QFuture override; - auto checkAsync(const QString &path, bool allowCache) -> QFuture override; - auto checkAsync(const QStringList &paths, bool allowCache) -> QFuture override; + auto checkAsync(const QString &path, Options options) -> QFuture override; + auto checkAsync(const QStringList &paths, Options options) -> QFuture override; private: - static auto getData(const QString &path, bool allowCache) -> FileDataResult; + static auto getData(const QString &path) -> FileDataResult; static auto createDir(const QDir &dir) -> FileResult; static auto save(const QString &path, const QByteArray &data) -> FileResult; static auto move(const QString &oldPath, const QString &newPath) -> FileResult; static auto copy(const QString &path, const QString ©) -> FileResult; static auto getDirFilter(bool files, bool folders) -> QFlags; - static auto check(const QString &path, bool allowCache) -> FileCheckResult; + static auto check(const QString &path) -> FileCheckResult; // There is an issue in qt >= 6.6 where futures resolved on the same thread as the context object // of their continuation become stuck trying to lock a mutex. diff --git a/src/filesystem/fileaccessnextcloud.cpp b/src/filesystem/fileaccessnextcloud.cpp index 3922a0c1..977d396d 100644 --- a/src/filesystem/fileaccessnextcloud.cpp +++ b/src/filesystem/fileaccessnextcloud.cpp @@ -6,21 +6,20 @@ #include using namespace Files; -using namespace Services; using namespace Qt::Literals::StringLiterals; Q_LOGGING_CATEGORY(gmFileAccessNextCloud, "gm.files.access.nextcloud") -FileAccessNextcloud::FileAccessNextcloud(NextCloud &nextcloud) : m_nc(nextcloud) +FileAccessNextcloud::FileAccessNextcloud(Services::NextCloud &nextcloud) : m_nc(nextcloud) { } -auto FileAccessNextcloud::getDataAsync(const QString &path, bool allowCache) -> QFuture +auto FileAccessNextcloud::getDataAsync(const QString &path, Options options) -> QFuture { qCDebug(gmFileAccessNextCloud()) << "Getting data from file" << path; // Try to get from cache first - if (allowCache) + if (options.testFlag(Option::AllowCache)) { QByteArray data; if (m_cache.tryGetData(path, data)) @@ -30,24 +29,28 @@ auto FileAccessNextcloud::getDataAsync(const QString &path, bool allowCache) -> } // Fetch from web - auto future = m_nc.sendDavRequest("GET", encodePath(path), ""); + auto future = m_nc.sendDavRequest("GET", encodePath(path), {}, {}, + options.testFlag(Option::LowPriority) ? Services::Option::LowPriority + : Services::Option::Authenticated); - return future.then([future, path](QNetworkReply *reply) { - if (replyHasError(reply)) + return future.then([future, path](const Services::RestReply &reply) { + if (reply.hasError()) { - return deleteReplyAndReturn( - FileDataResult(makeAndPrintError(u"Could not get data from %1"_s.arg(path), reply)), reply); + printError(u"Could not get data from %1"_s.arg(path), reply); + } + else + { + qCDebug(gmFileAccessNextCloud()) << "Received data from file" << path; } - qCDebug(gmFileAccessNextCloud()) << "Received data from file" << path; - return deleteReplyAndReturn(FileDataResult(reply->readAll()), reply); + return FileDataResult::fromRestReply(reply); }); } -auto FileAccessNextcloud::getDataAsync(const QStringList &paths, bool allowCache) +auto FileAccessNextcloud::getDataAsync(const QStringList &paths, Options options) -> QFuture> { - return FileAccess::multiGetDataAsync(MultiGetHelper(paths), allowCache); + return FileAccess::multiGetDataAsync(MultiGetHelper(paths), options); } template @@ -67,24 +70,21 @@ auto FileAccessNextcloud::saveAsync(const QString &path, const QByteArray &data) { qCDebug(gmFileAccessNextCloud()) << "Saving file" << path; - auto future = m_nc.sendDavRequest("PUT", encodePath(path), data); + auto future = m_nc.sendDavRequest("PUT", encodePath(path), data, {}, Services::Option::Authenticated); return future - .then([this, future, path, data](QNetworkReply *reply) { - if (replyHasError(reply)) + .then([this, future, path, data](const Services::RestReply &reply) { + if (reply.hasError()) { - if (const auto isDirMissing = reply->error() == QNetworkReply::ContentNotFoundError; isDirMissing) + if (const auto isDirMissing = reply.error() == QNetworkReply::ContentNotFoundError; isDirMissing) { - return deleteReplyAndReturn( - createDirThenContinue( - FileUtils::dirFromPath(path), path, data, - [this](const QString &path, const QByteArray &data) { return saveAsync(path, data); }), - reply); + return createDirThenContinue( + FileUtils::dirFromPath(path), path, data, + [this](const QString &path, const QByteArray &data) { return saveAsync(path, data); }); } - return deleteReplyAndReturn(QtFuture::makeReadyFuture(FileResult( - makeAndPrintError(u"Could not save file %1"_s.arg(path), reply))), - reply); + printError(u"Could not save file %1"_s.arg(path), reply); + return QtFuture::makeReadyFuture(FileResult::fromRestReply(reply)); } if (!m_cache.createOrUpdateEntry(path, data)) @@ -93,7 +93,7 @@ auto FileAccessNextcloud::saveAsync(const QString &path, const QByteArray &data) } qCDebug(gmFileAccessNextCloud()) << "Successfully saved file" << path; - return deleteReplyAndReturn(QtFuture::makeReadyFuture(FileResult(true)), reply); + return QtFuture::makeReadyFuture(FileResult(true)); }) .unwrap(); } @@ -102,27 +102,24 @@ auto FileAccessNextcloud::moveAsync(const QString &oldPath, const QString &newPa { qCDebug(gmFileAccessNextCloud()) << "Moving file" << oldPath << "to" << newPath; - auto future = m_nc.sendDavRequest("MOVE", encodePath(oldPath), QByteArray(), makeMoveHeaders(newPath)); + auto future = m_nc.sendDavRequest("MOVE", encodePath(oldPath), QByteArray(), makeMoveHeaders(newPath), + Services::Option::Authenticated); return future - .then([this, future, oldPath, newPath](QNetworkReply *reply) { - qCDebug(gmFileAccessNextCloud()) << "MOVE Reply:" << reply->error() << reply->errorString(); + .then([this, future, oldPath, newPath](const Services::RestReply &reply) { + qCDebug(gmFileAccessNextCloud()) << "MOVE Reply:" << reply.error() << reply.errorText(); - if (replyHasError(reply)) + if (reply.hasError()) { - if (const auto isDirMissing = reply->error() == QNetworkReply::ContentConflictError; isDirMissing) + if (const auto isDirMissing = reply.error() == QNetworkReply::ContentConflictError; isDirMissing) { - return deleteReplyAndReturn( - createDirThenContinue(FileUtils::dirFromPath(newPath), oldPath, newPath, - [this](const QString &oldPath, const QString &newPath) { - return moveAsync(oldPath, newPath); - }), - reply); + return createDirThenContinue( + FileUtils::dirFromPath(newPath), oldPath, newPath, + [this](const QString &oldPath, const QString &newPath) { return moveAsync(oldPath, newPath); }); } - return deleteReplyAndReturn(QtFuture::makeReadyFuture(FileResult(makeAndPrintError( - u"Could not move file %1 to %2"_s.arg(oldPath, newPath), reply))), - reply); + printError(u"Could not move file %1 to %2"_s.arg(oldPath, newPath), reply); + return QtFuture::makeReadyFuture(FileResult::fromRestReply(reply)); } if (!m_cache.moveEntry(oldPath, newPath)) @@ -131,7 +128,7 @@ auto FileAccessNextcloud::moveAsync(const QString &oldPath, const QString &newPa } qCDebug(gmFileAccessNextCloud()) << "Successfully moved file" << oldPath << "to" << newPath; - return deleteReplyAndReturn(QtFuture::makeReadyFuture(FileResult(true)), reply); + return QtFuture::makeReadyFuture(FileResult(true)); }) .unwrap(); } @@ -140,21 +137,20 @@ auto FileAccessNextcloud::deleteAsync(const QString &path) -> QFuture const auto destinationHeader = std::make_pair(QByteArray("Destination"), copy.toUtf8()); const auto overwriteHeader = std::make_pair(QByteArray("Overwrite"), QByteArray("F")); - auto future = m_nc.sendDavRequest("COPY", encodePath(path), QByteArray(), {destinationHeader, overwriteHeader}); + auto future = m_nc.sendDavRequest("COPY", encodePath(path), {}, {destinationHeader, overwriteHeader}, + Services::Option::Authenticated); return future - .then([this, future, path, copy](QNetworkReply *reply) { - if (replyHasError(reply)) + .then([this, future, path, copy](const Services::RestReply &reply) { + if (reply.hasError()) { - if (const auto isDirMissing = reply->error() == QNetworkReply::ContentConflictError; isDirMissing) + if (const auto isDirMissing = reply.error() == QNetworkReply::ContentConflictError; isDirMissing) { - return deleteReplyAndReturn( - createDirThenContinue( - FileUtils::dirFromPath(copy), path, copy, - [this](const QString &path, const QString ©) { return copyAsync(path, copy); }), - reply); + return createDirThenContinue( + FileUtils::dirFromPath(copy), path, copy, + [this](const QString &path, const QString ©) { return copyAsync(path, copy); }); } - return deleteReplyAndReturn(QtFuture::makeReadyFuture(FileResult(makeAndPrintError( - u"Could not copy %1 to %2: %3 %4"_s.arg(path), reply))), - reply); + printError(u"Could not copy %1 to %2: %3 %4"_s.arg(path), reply); + return QtFuture::makeReadyFuture(FileResult::fromRestReply(reply)); } if (!m_cache.copyEntry(path, copy)) @@ -193,7 +187,7 @@ auto FileAccessNextcloud::copyAsync(const QString &path, const QString ©) -> } qCDebug(gmFileAccessNextCloud()) << "Successfully deleted file" << path; - return deleteReplyAndReturn(QtFuture::makeReadyFuture(FileResult(true)), reply); + return QtFuture::makeReadyFuture(FileResult(true)); }) .unwrap(); } @@ -204,18 +198,17 @@ auto FileAccessNextcloud::listAsync(const QString &path, bool files, bool folder << (folders ? (files ? "files and folders" : "folders") : "files") << "in path" << path << "..."; - auto future = m_nc.sendDavRequest("PROPFIND", encodePath(path), QByteArray()); + auto future = m_nc.sendDavRequest("PROPFIND", encodePath(path), {}, {}, Services::Option::Authenticated); - return future.then([future, path, files, folders](QNetworkReply *reply) { - if (replyHasError(reply)) + return future.then([future, path, files, folders](const Services::RestReply &reply) { + if (reply.hasError()) { - return deleteReplyAndReturn( - FileListResult(path, makeAndPrintError(u"Could not list content of folder %1"_s.arg(path), reply)), - reply); + printError(u"Could not list content of folder %1"_s.arg(path), reply); + return FileListResult(path, reply.errorText()); } qCDebug(gmFileAccessNextCloud()) << "Successfully received content of" << path; - return deleteReplyAndReturn(parseListResponse(reply->readAll(), path, files, folders), reply); + return parseListResponse(reply.data(), path, files, folders); }); } @@ -278,53 +271,51 @@ auto FileAccessNextcloud::createDirAsync(const QString &path) -> QFuture QFuture +auto FileAccessNextcloud::checkAsync(const QString &path, Options options) -> QFuture { qCDebug(gmFileAccessNextCloud()) << "Checking if file" << path << "exists ..."; // Check cache first - if (allowCache && m_cache.checkEntry(path)) + if (options.testFlag(Option::AllowCache) && m_cache.checkEntry(path)) { return QtFuture::makeReadyFuture(FileCheckResult(path, true)); } // If file is not in cache or cache is outdated, fetch from web - auto future = m_nc.sendDavRequest("PROPFIND", encodePath(path), QByteArray()); + auto future = m_nc.sendDavRequest("PROPFIND", encodePath(path), QByteArray(), {}, Services::Option::Authenticated); - return future.then([future, path](QNetworkReply *reply) { - const auto doesExist = reply->error() != QNetworkReply::ContentNotFoundError; + return future.then([future, path](const Services::RestReply &reply) { + const auto doesExist = reply.error() != QNetworkReply::ContentNotFoundError; - if (const auto hasError = replyHasError(reply) && doesExist; hasError) + if (const auto hasError = reply.hasError() && doesExist; hasError) { - return deleteReplyAndReturn( - FileCheckResult(path, makeAndPrintError(u"Could not check if file %1 exists"_s.arg(path), reply)), - reply); + printError(u"Could not check if file %1 exists"_s.arg(path), reply); + return FileCheckResult(path, reply.errorText()); } - qCDebug(gmFileAccessNextCloud()) << "PROPFIND:" << reply->readAll(); - + qCDebug(gmFileAccessNextCloud()) << "PROPFIND:" << reply.data(); qCDebug(gmFileAccessNextCloud()) << "Successfully checked if file" << path << "exists:" << doesExist; - return deleteReplyAndReturn(FileCheckResult(path, doesExist), reply); + return FileCheckResult(path, doesExist); }); } -auto FileAccessNextcloud::checkAsync(const QStringList &paths, bool allowCache) -> QFuture +auto FileAccessNextcloud::checkAsync(const QStringList &paths, Options options) -> QFuture { - return FileAccess::multiCheckAsync(MultiGetHelper(paths), allowCache); + return FileAccess::multiCheckAsync(MultiGetHelper(paths), options); } auto FileAccessNextcloud::encodePath(const QString &data) -> QByteArray @@ -332,21 +323,9 @@ auto FileAccessNextcloud::encodePath(const QString &data) -> QByteArray return QUrl::toPercentEncoding(data, "/"); } -auto FileAccessNextcloud::replyHasError(const QNetworkReply *reply) -> bool -{ - return reply->error() != QNetworkReply::NoError; -} - -auto FileAccessNextcloud::makeAndPrintError(const QString &errorMessage, const QNetworkReply *reply) -> QString +void FileAccessNextcloud::printError(const QString &errorMessage, const Services::RestReply &reply) { - auto result = u"%1: %2"_s.arg(errorMessage, replyErrorToString(reply)); - qCWarning(gmFileAccessNextCloud()) << "Warning:" << result; - return result; -} - -auto FileAccessNextcloud::replyErrorToString(const QNetworkReply *reply) -> QString -{ - return u"%1 (%2)"_s.arg(QString::number(reply->error()), reply->errorString()); + qCWarning(gmFileAccessNextCloud()) << "Warning:" << errorMessage << reply.error() << ":" << reply.errorText(); } auto FileAccessNextcloud::makeMoveHeaders(const QString &newPath) -> QList> @@ -355,9 +334,3 @@ auto FileAccessNextcloud::makeMoveHeaders(const QString &newPath) -> QList auto FileAccessNextcloud::deleteReplyAndReturn(T &&value, QNetworkReply *reply) -> T -{ - QScopedPointer scopedReply(reply); - return std::forward(value); -} diff --git a/src/filesystem/fileaccessnextcloud.h b/src/filesystem/fileaccessnextcloud.h index 37bf28de..48d10b5e 100644 --- a/src/filesystem/fileaccessnextcloud.h +++ b/src/filesystem/fileaccessnextcloud.h @@ -3,7 +3,6 @@ #include "cache/filecache.h" #include "fileaccess.h" #include "nextcloud/nextcloud.h" -#include #include namespace Files @@ -16,29 +15,25 @@ class FileAccessNextcloud : public FileAccess ~FileAccessNextcloud() override = default; Q_DISABLE_COPY_MOVE(FileAccessNextcloud) - auto getDataAsync(const QString &path, bool allowCache) -> QFuture override; - auto getDataAsync(const QStringList &paths, bool allowCache) -> QFuture> override; + auto getDataAsync(const QString &path, Options options) -> QFuture override; + auto getDataAsync(const QStringList &paths, Options options) -> QFuture> override; auto saveAsync(const QString &path, const QByteArray &data) -> QFuture override; auto moveAsync(const QString &oldPath, const QString &newPath) -> QFuture override; auto deleteAsync(const QString &path) -> QFuture override; auto copyAsync(const QString &path, const QString ©) -> QFuture override; auto listAsync(const QString &path, bool files, bool folders) -> QFuture override; auto createDirAsync(const QString &path) -> QFuture override; - auto checkAsync(const QString &path, bool allowCache) -> QFuture override; - auto checkAsync(const QStringList &paths, bool allowCache) -> QFuture override; + auto checkAsync(const QString &path, Options options) -> QFuture override; + auto checkAsync(const QStringList &paths, Options options) -> QFuture override; private: Services::NextCloud &m_nc; FileCache m_cache; static auto encodePath(const QString &data) -> QByteArray; - static auto replyHasError(const QNetworkReply *reply) -> bool; - static auto makeAndPrintError(const QString &errorMessage, const QNetworkReply *reply) -> QString; - static auto replyErrorToString(const QNetworkReply *reply) -> QString; + static void printError(const QString &errorMessage, const Services::RestReply &reply); static auto makeMoveHeaders(const QString &newPath) -> QList>; - template static auto deleteReplyAndReturn(T &&value, QNetworkReply *reply) -> T; - template auto createDirThenContinue(const QString &dir, const T1 &arg1, const T2 &arg2, const std::function(const T1 &, const T2 &)> &func) diff --git a/src/filesystem/options.h b/src/filesystem/options.h new file mode 100644 index 00000000..dce901bc --- /dev/null +++ b/src/filesystem/options.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace Files +{ + +enum class Option +{ + None = 0, + AllowCache = 1, + LowPriority = 2 +}; +Q_DECLARE_FLAGS(Options, Files::Option) + +} // namespace Files + +Q_DECLARE_OPERATORS_FOR_FLAGS(Files::Options) diff --git a/src/services/CMakeLists.txt b/src/services/CMakeLists.txt index 2d7ddbb4..32fc7cae 100755 --- a/src/services/CMakeLists.txt +++ b/src/services/CMakeLists.txt @@ -4,6 +4,7 @@ set(SRC_COMMON service.h service.cpp servicestatus.h + options.h ) set(SRC_REST @@ -68,6 +69,8 @@ set(SRC_SPOTIFY set(SRC_NEXTCLOUD nextcloud/nextcloud.h nextcloud/nextcloud.cpp + nextcloud/nextcloudconnector.h + nextcloud/nextcloudconnector.cpp ) set(SRC_GOOGLEDRIVE diff --git a/src/services/nextcloud/nextcloud.cpp b/src/services/nextcloud/nextcloud.cpp index c06937b4..4658e9f8 100644 --- a/src/services/nextcloud/nextcloud.cpp +++ b/src/services/nextcloud/nextcloud.cpp @@ -1,56 +1,43 @@ #include "nextcloud.h" #include "settings/settingsmanager.h" -#include "utils/networkutils.h" -#include "utils/stringutils.h" -#include -#include -#include #include +#include +#include #include using namespace Qt::Literals::StringLiterals; using namespace Services; using namespace Common::Settings; -constexpr auto AUTH_URL = "/index.php/login/v2"; constexpr auto DAV_ENDPOINT = "/remote.php/dav/files"; -constexpr auto AUTH_POLL_DELAY = 3000; -constexpr auto MAX_AUTH_POLLS = 20; Q_LOGGING_CATEGORY(gmNextCloud, "gm.service.nextcloud") -NextCloud::NextCloud(const QQmlEngine &engine, QObject *parent) : NextCloud(*engine.networkAccessManager(), parent) +NextCloud::NextCloud(const QQmlEngine &engine, QObject *parent) : NextCloud(engine.networkAccessManager(), parent) { } -NextCloud::NextCloud(QNetworkAccessManager &networkManager, QObject *parent) +NextCloud::NextCloud(QNetworkAccessManager *networkManager, QObject *parent) : NextCloud(u"NextCloud"_s, networkManager, parent) { } -NextCloud::NextCloud(const QString &serviceName, QNetworkAccessManager &networkManager, QObject *parent) - : Service(serviceName, parent), m_networkManager(networkManager) +NextCloud::NextCloud(const QString &serviceName, QNetworkAccessManager *networkManager, QObject *parent) + : Service(serviceName, parent), m_connector(this) { + m_connector.setNetworkManager(networkManager); + connect(&m_connector, &RESTServiceConnector::statusChanged, this, &NextCloud::updateStatus); + connect(&m_connector, &NextCloudConnector::stateChanged, this, &NextCloud::onConnectorStateChanged); + if (!connected()) return; updateStatus(Status::Type::Success, tr("Connected")); loginName(SettingsManager::instance()->get(u"loginName"_s, u""_s, serviceName)); serverUrl(SettingsManager::getServerUrl(serviceName, false)); - m_loggingIn = true; - - m_appPassword = SettingsManager::getPassword(loginName(), serviceName); - - if (loginName().isEmpty() || m_appPassword.isEmpty() || serverUrl().isEmpty()) - { - connected(false); - } - else - { - qCDebug(gmNextCloud()) << "Connected to Nextcloud as user" << loginName() << "on server" << serverUrl(); - emit loggedIn(); - } - m_loggingIn = false; + QTimer::singleShot(0, this, [this]() { + if (m_connector.state() == NextCloudConnector::State::Connected) emit loggedIn(); + }); } auto NextCloud::qmlInstance(QQmlEngine *engine) -> NextCloud * @@ -64,27 +51,34 @@ auto NextCloud::qmlInstance(QQmlEngine *engine) -> NextCloud * } auto NextCloud::sendDavRequest(const QByteArray &method, const QString &path, const QByteArray &data, - const QList> &headers) -> QFuture + const QList> &headers, Options options) + -> QFuture { - if (!connected()) connectService(); - - // In case we are still logging in, delay this request - if (m_loggingIn) + if (QThread::currentThread() != this->thread()) { - qCDebug(gmNextCloud()) << "Delaying DAV request, because we are waiting for authentication ..."; - - return QtFuture::connect(this, &NextCloud::loggedIn) - .then([this, method, path, data]() { return sendDavRequest(method, path, data); }) - .unwrap(); + QPromise promise; + auto future = promise.future(); + + QMetaObject::invokeMethod( + this, + [this, method, path, data, headers, options, promise = std::move(promise)]() mutable { + sendDavRequest(method, path, data, headers, options) + .then([promise = std::move(promise)](RestReply &&reply) mutable { + promise.addResult(std::move(reply)); + promise.finish(); + }); + }, + Qt::ConnectionType::QueuedConnection); + return future; } + if (!connected()) connectService(); + auto url = getPathUrl(path); auto request = QNetworkRequest(QUrl(url)); qCDebug(gmNextCloud()) << "Sending DAV request (" << method << ") to" << url; - request.setRawHeader("Authorization", NetworkUtils::basicAuthHeader(loginName(), m_appPassword)); - if (!headers.isEmpty()) { for (const auto &[key, value] : headers) @@ -100,166 +94,42 @@ auto NextCloud::sendDavRequest(const QByteArray &method, const QString &path, co } } - auto *reply = m_networkManager.sendCustomRequest(request, method, data); - return QtFuture::connect(reply, &QNetworkReply::finished).then([reply]() { return reply; }); + return m_connector.customRequest(request, method, data, Option::Authenticated | options); } auto NextCloud::getPathUrl(const QString &path) const -> QString { const auto seperator = path.startsWith('/') ? u""_s : u"/"_s; - return u"%1%2/%3%4%5"_s.arg(serverUrl(), DAV_ENDPOINT, loginName(), seperator, path); + // If loginName is empty (because we are not connected yet), add a placeholder. + // The name will be inserted in the connector during sendRequest() + return u"%1%2/%3%4%5"_s.arg(serverUrl(), DAV_ENDPOINT, loginName().isEmpty() ? "%1"_L1 : loginName(), seperator, + path); } void NextCloud::connectService() { - qCDebug(gmNextCloud()) << "Logging in ..."; - - if (m_loggingIn) - { - qCDebug(gmNextCloud()) << "Login already in progress ..."; - updateStatus(Status::Type::Success, tr("Connected")); - return; - } - - if (connected()) - { - qCDebug(gmNextCloud()) << "Already connected to a nextcloud instance."; - } - else - { - m_loggingIn = true; - startLoginFlow(); - } + m_connector.grantAccess(); } void NextCloud::disconnectService() { - qCDebug(gmNextCloud()) << "Logout() ..."; - updateStatus(Status::Type::Info, tr("Logging out ...")); - - SettingsManager::setPassword(loginName(), ""_L1, serviceName()); - - connected(false); -} - -/** - * @brief Grant access through the login flow v2: - * https://docs.nextcloud.com/server/18/developer_manual/client_apis/LoginFlow/index.html#login-flow-v2 - */ -void NextCloud::startLoginFlow() -{ - qCDebug(gmNextCloud()) << "Starting login flow v2 ..."; - - serverUrl(SettingsManager::getServerUrl(serviceName(), false)); - - if (serverUrl().isEmpty()) - { - updateStatus(Status::Type::Error, tr("Error: Server URL is empty.")); - m_loggingIn = false; - return; - } - - updateStatus(Status::Type::Info, tr("Connecting ...")); - auto authUrl = serverUrl() + AUTH_URL; - - qCDebug(gmNextCloud()) << "Server URL:" << serverUrl(); - qCDebug(gmNextCloud()) << "Auth URL:" << authUrl; - - auto request = QNetworkRequest(QUrl(authUrl)); - request.setRawHeader("User-Agent", "GM-Companion"); - - auto *reply = m_networkManager.post(request, ""); - - connect(reply, &QNetworkReply::finished, this, [this, reply]() { - auto data = QJsonDocument::fromJson(reply->readAll()); - - if (reply->error() != QNetworkReply::NoError) - { - qCWarning(gmNextCloud()) << "Error:" << reply->error() << reply->errorString(); - updateStatus(Status::Type::Error, reply->errorString()); - m_loggingIn = false; - } - else - { - auto poll = data.object()["poll"_L1].toObject(); - auto token = poll["token"_L1].toString(); - auto endpoint = poll["endpoint"_L1].toString(); - auto login = data.object()["login"_L1].toString(); - - updateStatus(Status::Type::Info, tr("Waiting for login ...")); - QDesktopServices::openUrl(QUrl(login)); - pollAuthPoint(QUrl(endpoint), token); - } - - reply->deleteLater(); - }); -} - -/** - * @brief Poll the auth point to receive loginName and appPassword - */ -void NextCloud::pollAuthPoint(const QUrl &url, const QString &token) -{ - qCDebug(gmNextCloud()) << "Polling auth point ..."; - - const QByteArray data = "token=" + token.toUtf8(); - auto *reply = m_networkManager.post(QNetworkRequest(url), data); - m_authPolls++; - - qCDebug(gmNextCloud()) << "URL:" << url.toString(); - qCDebug(gmNextCloud()) << "Token:" << StringUtils::censor(token); - - connect(reply, &QNetworkReply::finished, this, - [this, url, token, reply]() { handleAuthPointReply(reply, url, token); }); + m_connector.disconnectService(); } -void NextCloud::handleAuthPointReply(QNetworkReply *reply, const QUrl &url, const QString &token) +void NextCloud::onConnectorStateChanged(NextCloudConnector::State state) { - if (!reply) return; + qCDebug(gmNextCloud()) << "Connector state changed:" << static_cast(state); - // Polling endpoint returns 404 until authentication is done - if (reply->error() == QNetworkReply::ContentNotFoundError) + switch (state) { - handleAuthPointNotFound(url, token); - } - else - { - handleAuthPointSuccess(*reply); - } - - reply->deleteLater(); -} - -void NextCloud::handleAuthPointNotFound(const QUrl &url, const QString &token) -{ - if ((m_authPolls < MAX_AUTH_POLLS) && m_loggingIn) - { - qCDebug(gmNextCloud()) << "Finished poll" << m_authPolls << "/" << MAX_AUTH_POLLS - << "waiting and polling again ..."; - - QTimer::singleShot(AUTH_POLL_DELAY, this, [this, url, token]() { pollAuthPoint(url, token); }); - } - else - { - qCWarning(gmNextCloud()) << "Timeout: Max polls (" << MAX_AUTH_POLLS << ") reached!"; - updateStatus(Status::Type::Error, tr("Login timed out, please try again.")); - m_loggingIn = false; + case NextCloudConnector::State::Connected: + connected(true); + emit loggedIn(); + break; + case NextCloudConnector::State::Disconnected: + connected(false); + break; + default: + break; } } - -void NextCloud::handleAuthPointSuccess(QNetworkReply &reply) -{ - const auto content = QJsonDocument::fromJson(reply.readAll()).object(); - loginName(content["loginName"_L1].toString()); - m_appPassword = content["appPassword"_L1].toString(); - - qCDebug(gmNextCloud()) << "Logged in successfully!"; - qCDebug(gmNextCloud()) << "LoginName:" << loginName(); - qCDebug(gmNextCloud()) << "AppPassword:" << StringUtils::censor(m_appPassword); - - SettingsManager::instance()->set("loginName"_L1, loginName(), serviceName()); - SettingsManager::setPassword(loginName(), m_appPassword, serviceName()); - connected(true); - m_loggingIn = false; - emit loggedIn(); -} diff --git a/src/services/nextcloud/nextcloud.h b/src/services/nextcloud/nextcloud.h index cf3e3f64..48b8a0d0 100644 --- a/src/services/nextcloud/nextcloud.h +++ b/src/services/nextcloud/nextcloud.h @@ -1,12 +1,12 @@ #pragma once +#include "../options.h" +#include "nextcloudconnector.h" #include "qmlsingletonfactory.h" #include "service.h" #include "thirdparty/propertyhelper/PropertyHelper.h" #include #include -#include -#include #include #include #include @@ -22,13 +22,13 @@ class NextCloud : public Services::Service public: explicit NextCloud(const QQmlEngine &engine, QObject *parent); - explicit NextCloud(QNetworkAccessManager &networkManager, QObject *parent); - explicit NextCloud(const QString &serviceName, QNetworkAccessManager &networkManager, QObject *parent); + explicit NextCloud(QNetworkAccessManager *networkManager, QObject *parent); + explicit NextCloud(const QString &serviceName, QNetworkAccessManager *networkManager, QObject *parent); static auto qmlInstance(QQmlEngine *engine) -> NextCloud *; auto sendDavRequest(const QByteArray &method, const QString &path, const QByteArray &data, - const QList> &headers = {}) -> QFuture; + const QList> &headers, Options options) -> QFuture; [[nodiscard]] auto getPathUrl(const QString &path) const -> QString; @@ -42,19 +42,12 @@ public slots: signals: void loggedIn(); +private slots: + void onConnectorStateChanged(NextCloudConnector::State state); + private: inline static NextCloud *s_qmlInstance = nullptr; - QNetworkAccessManager &m_networkManager; - int m_authPolls = 0; - bool m_loggingIn = false; - - QString m_appPassword = QLatin1String(""); - - void startLoginFlow(); - void pollAuthPoint(const QUrl &url, const QString &token); - void handleAuthPointReply(QNetworkReply *reply, const QUrl &url, const QString &token); - void handleAuthPointNotFound(const QUrl &url, const QString &token); - void handleAuthPointSuccess(QNetworkReply &reply); + NextCloudConnector m_connector; }; } // namespace Services diff --git a/src/services/nextcloud/nextcloudconnector.cpp b/src/services/nextcloud/nextcloudconnector.cpp new file mode 100644 index 00000000..53c99df5 --- /dev/null +++ b/src/services/nextcloud/nextcloudconnector.cpp @@ -0,0 +1,297 @@ +#include "nextcloudconnector.h" +#include "exceptions/notimplementedexception.h" +#include "nextcloud.h" +#include "settings/settingsmanager.h" +#include "utils/networkutils.h" +#include "utils/stringutils.h" +#include +#include +#include +#include +#include +#include + +using namespace Qt::Literals::StringLiterals; +using namespace Services; +using namespace Common::Settings; + +constexpr auto MAX_CONCURRENT_REQUESTS = 3; +constexpr auto AUTH_URL = "/index.php/login/v2"; +constexpr auto AUTH_POLL_DELAY = 3000; +constexpr auto MAX_AUTH_POLLS = 20; +constexpr auto TOKEN_VALIDITY_TIME = std::chrono::hours(8766); // 1 year + +Q_LOGGING_CATEGORY(gmNcConnector, "gm.service.nextcloud.connector") + +NextCloudConnector::NextCloudConnector(NextCloud *nc, QObject *parent) + : RESTServiceConnector{nullptr, gmNcConnector(), {}, parent}, m_nc(nc) +{ + setMaxConcurrentRequests(MAX_CONCURRENT_REQUESTS); + updateTokenExpireTime(TOKEN_VALIDITY_TIME); + + connect(this, &NextCloudConnector::stateChanged, this, &NextCloudConnector::onStateChanged); + state(State::Connecting); + + QTimer::singleShot(0, this, [this]() { + m_appPassword = SettingsManager::getPassword(m_nc->loginName(), m_nc->serviceName()); + + if (m_nc->loginName().isEmpty() || m_appPassword.isEmpty() || m_nc->serverUrl().isEmpty()) + { + state(State::Disconnected); + } + else + { + qCDebug(gmNcConnector()) << "Connected to Nextcloud as user" << m_nc->loginName() << "on server" + << m_nc->serverUrl(); + state(State::Connected); + } + }); +} + +void NextCloudConnector::grantAccess() +{ + qCDebug(gmNcConnector()) << "Logging in ..."; + + if (state() == State::Connecting) + { + qCDebug(gmNcConnector()) << "Login already in progress ..."; + setStatus(Status::Type::Success, tr("Connected")); + return; + } + + if (isAccessGranted()) + { + qCDebug(gmNcConnector()) << "Already connected to a nextcloud instance."; + } + else + { + state(State::Connecting); + startLoginFlow(); + } +} + +void NextCloudConnector::disconnectService() +{ + qCDebug(gmNcConnector()) << "Logout() ..."; + setStatus(Status::Type::Info, tr("Logging out ...")); + + SettingsManager::setPassword(m_nc->loginName(), ""_L1, m_nc->serviceName()); + state(State::Disconnected); +} + +auto NextCloudConnector::isAccessGranted() const -> bool +{ + return state() == State::Connected; +} + +auto NextCloudConnector::get(const QNetworkRequest &request, Options options) -> QFuture +{ + Q_UNUSED(request) + Q_UNUSED(options) + throw NotImplementedException(); +} + +auto NextCloudConnector::put(QNetworkRequest request, const QByteArray &data, Options options) -> QFuture +{ + Q_UNUSED(request) + Q_UNUSED(data) + Q_UNUSED(options) + throw NotImplementedException(); +} + +auto NextCloudConnector::post(QNetworkRequest request, const QByteArray &data, Options options) -> QFuture +{ + Q_UNUSED(request) + Q_UNUSED(data) + Q_UNUSED(options) + throw NotImplementedException(); +} + +auto NextCloudConnector::customRequest(const QNetworkRequest &request, const QByteArray &verb, const QByteArray &data, + Options options) -> QFuture +{ + QPromise promise; + promise.start(); + + RestRequest container(request, RestRequest::Type::CUSTOM, options, data, verb); + return enqueueRequest(std::move(container), std::move(promise)); +} + +void NextCloudConnector::sendRequest(RestRequest &&container, QPromise &&promise) +{ + auto request = container.request(); + + // insert login name if necessary + if (const auto path = request.url().path().replace("/%251/"_L1, "/%1/"_L1); path.contains("/%1/"_L1)) + { + auto url = request.url(); + url.setPath(path.arg(m_nc->loginName()), QUrl::TolerantMode); + request.setUrl(url); + } + + if (container.options().testFlag(Option::Authenticated)) + { + request.setRawHeader("Authorization", NetworkUtils::basicAuthHeader(m_nc->loginName(), m_appPassword)); + } + + qCDebug(gmNcConnector) << "Sending" << container.verb() << "Request to URL" << request.url(); + auto *reply = networkManager()->sendCustomRequest(request, container.verb(), container.data()); + + auto id = container.id(); + QtFuture::connect(reply, &QNetworkReply::finished).then([this, reply, id]() mutable { + onReplyReceived(id, reply->error(), reply->errorString(), reply->readAll(), reply->rawHeaderPairs()); + reply->deleteLater(); + }); + + markRequestActive(std::move(container), std::move(promise)); +} + +void NextCloudConnector::refreshAccessToken(bool updateAuthentication) +{ + // nop + Q_UNUSED(updateAuthentication) +} + +auto NextCloudConnector::getAccessToken() -> QString +{ + return m_appPassword; +} + +void NextCloudConnector::onStateChanged(State state) +{ + switch (state) + { + case NextCloudConnector::State::Connected: + emit isConnectedChanged(true); + emit accessGranted(); + dequeueRequests(); + break; + case NextCloudConnector::State::Disconnected: + emit isConnectedChanged(false); + break; + default: + break; + } +} + +/** + * @brief Grant access through the login flow v2: + * https://docs.nextcloud.com/server/18/developer_manual/client_apis/LoginFlow/index.html#login-flow-v2 + */ +void NextCloudConnector::startLoginFlow() +{ + qCDebug(gmNcConnector()) << "Starting login flow v2 ..."; + + m_nc->serverUrl(SettingsManager::getServerUrl(m_nc->serviceName(), false)); + + if (m_nc->serverUrl().isEmpty()) + { + setStatus(Status::Type::Error, tr("Error: Server URL is empty.")); + state(State::Disconnected); + return; + } + + setStatus(Status::Type::Info, tr("Connecting ...")); + state(State::Connecting); + auto authUrl = m_nc->serverUrl() + AUTH_URL; + + qCDebug(gmNcConnector()) << "Server URL:" << m_nc->serverUrl(); + qCDebug(gmNcConnector()) << "Auth URL:" << authUrl; + + auto request = QNetworkRequest(QUrl(authUrl)); + request.setRawHeader("User-Agent", "GM-Companion"); + + auto *reply = networkManager()->post(request, ""); + + connect(reply, &QNetworkReply::finished, this, [this, reply]() { + auto data = QJsonDocument::fromJson(reply->readAll()); + + if (reply->error() != QNetworkReply::NoError) + { + qCWarning(gmNcConnector()) << "Error:" << reply->error() << reply->errorString(); + setStatus(Status::Type::Error, reply->errorString()); + state(State::Disconnected); + } + else + { + auto poll = data["poll"_L1].toObject(); + auto token = poll["token"_L1].toString(); + auto endpoint = poll["endpoint"_L1].toString(); + auto login = data["login"_L1].toString(); + + setStatus(Status::Type::Info, tr("Waiting for login ...")); + QDesktopServices::openUrl(QUrl(login)); + pollAuthPoint(QUrl(endpoint), token); + } + + reply->deleteLater(); + }); +} + +/** + * @brief Poll the auth point to receive loginName and appPassword + */ +void NextCloudConnector::pollAuthPoint(const QUrl &url, const QString &token) +{ + qCDebug(gmNcConnector()) << "Polling auth point ..."; + + const QByteArray data = "token=" + token.toUtf8(); + auto *reply = networkManager()->post(QNetworkRequest(url), data); + m_authPolls++; + + qCDebug(gmNcConnector()) << "URL:" << url.toString(); + qCDebug(gmNcConnector()) << "Token:" << StringUtils::censor(token); + + connect(reply, &QNetworkReply::finished, this, + [this, url, token, reply]() { handleAuthPointReply(reply, url, token); }); +} + +void NextCloudConnector::handleAuthPointReply(QNetworkReply *reply, const QUrl &url, const QString &token) +{ + if (!reply) return; + + // Polling endpoint returns 404 until authentication is done + if (reply->error() == QNetworkReply::ContentNotFoundError) + { + handleAuthPointNotFound(url, token); + } + else + { + handleAuthPointSuccess(*reply); + } + + reply->deleteLater(); +} + +void NextCloudConnector::handleAuthPointNotFound(const QUrl &url, const QString &token) +{ + if ((m_authPolls < MAX_AUTH_POLLS) && state() == State::Connecting) + { + qCDebug(gmNcConnector()) << "Finished poll" << m_authPolls << "/" << MAX_AUTH_POLLS + << "waiting and polling again ..."; + + QTimer::singleShot(AUTH_POLL_DELAY, this, [this, url, token]() { pollAuthPoint(url, token); }); + } + else + { + qCWarning(gmNcConnector()) << "Timeout: Max polls (" << MAX_AUTH_POLLS << ") reached!"; + setStatus(Status::Type::Error, tr("Login timed out, please try again.")); + state(State::Disconnected); + } +} + +void NextCloudConnector::handleAuthPointSuccess(QNetworkReply &reply) +{ + const auto content = QJsonDocument::fromJson(reply.readAll()).object(); + m_nc->loginName(content["loginName"_L1].toString()); + m_appPassword = content["appPassword"_L1].toString(); + + qCDebug(gmNcConnector()) << "Logged in successfully!"; + qCDebug(gmNcConnector()) << "LoginName:" << m_nc->loginName(); + qCDebug(gmNcConnector()) << "AppPassword:" << StringUtils::censor(m_appPassword); + + SettingsManager::instance()->set("loginName"_L1, m_nc->loginName(), m_nc->serviceName()); + SettingsManager::setPassword(m_nc->loginName(), m_appPassword, m_nc->serviceName()); + + state(State::Connected); +} diff --git a/src/services/nextcloud/nextcloudconnector.h b/src/services/nextcloud/nextcloudconnector.h new file mode 100644 index 00000000..4987d6fa --- /dev/null +++ b/src/services/nextcloud/nextcloudconnector.h @@ -0,0 +1,59 @@ +#pragma once + +#include "rest/restserviceconnector.h" +#include "thirdparty/propertyhelper/PropertyHelper.h" +#include +#include + +namespace Services +{ + +class NextCloud; + +class NextCloudConnector : public RESTServiceConnector +{ + Q_OBJECT + +public: + enum class State + { + Connected, + Connecting, + Disconnected + }; + + explicit NextCloudConnector(NextCloud *nc, QObject *parent = nullptr); + + void grantAccess() override; + void disconnectService() override; + [[nodiscard]] auto isAccessGranted() const -> bool override; + + auto get(const QNetworkRequest &request, Options options) -> QFuture override; + auto put(QNetworkRequest request, const QByteArray &data, Options options) -> QFuture override; + auto post(QNetworkRequest request, const QByteArray &data, Options options) -> QFuture override; + auto customRequest(const QNetworkRequest &request, const QByteArray &verb, const QByteArray &data, + Options options) -> QFuture override; + + AUTO_PROPERTY_VAL2(NextCloudConnector::State, state, State::Disconnected) +protected: + void sendRequest(RestRequest &&request, QPromise &&reply) override; + void refreshAccessToken(bool updateAuthentication = false) override; + [[nodiscard]] auto getAccessToken() -> QString override; + +private slots: + void onStateChanged(State state); + +private: + void startLoginFlow(); + void pollAuthPoint(const QUrl &url, const QString &token); + void handleAuthPointReply(QNetworkReply *reply, const QUrl &url, const QString &token); + void handleAuthPointNotFound(const QUrl &url, const QString &token); + void handleAuthPointSuccess(QNetworkReply &reply); + + int m_authPolls = 0; + QString m_appPassword = QLatin1String(""); + + QPointer m_nc = nullptr; +}; + +} // namespace Services diff --git a/src/services/options.h b/src/services/options.h new file mode 100644 index 00000000..99123b23 --- /dev/null +++ b/src/services/options.h @@ -0,0 +1,17 @@ +#pragma once + +namespace Services +{ + +enum class Option +{ + None = 0, + Authenticated = 1, + LowPriority = 2 +}; + +Q_DECLARE_FLAGS(Options, Services::Option) + +} // namespace Services + +Q_DECLARE_OPERATORS_FOR_FLAGS(Services::Options) diff --git a/src/services/rest/callbackserver.cpp b/src/services/rest/callbackserver.cpp index fed64e5e..39114429 100644 --- a/src/services/rest/callbackserver.cpp +++ b/src/services/rest/callbackserver.cpp @@ -64,10 +64,10 @@ auto CallbackServer::parseQueryParams(const QByteArray &data) -> QMap> tokens; - QUrlQuery query(getTokenUrl); + QUrlQuery const query(getTokenUrl); tokens = query.queryItems(); QMap queryParams; @@ -128,7 +128,7 @@ void CallbackServer::onBytesReady() void CallbackServer::closeServer(QTcpSocket *socket, bool hasParameters) { qCDebug(gmCallbackServer()) << "closeServer: Initiating"; - int port = m_server.serverPort(); + int const port = m_server.serverPort(); if (socket) { diff --git a/src/services/rest/restrequest.h b/src/services/rest/restrequest.h index a49128b5..b8f48f8a 100644 --- a/src/services/rest/restrequest.h +++ b/src/services/rest/restrequest.h @@ -1,5 +1,6 @@ #pragma once +#include "../options.h" #include #include @@ -17,15 +18,18 @@ class RestRequest CUSTOM }; - explicit RestRequest(const QNetworkRequest &request, Type type, QByteArray data = {}, QByteArray verb = {}) - : RestRequest(-1, request, type, std::move(data), std::move(verb)) + explicit RestRequest(const QNetworkRequest &request, Type type, Options options, QByteArray data = {}, + QByteArray verb = {}) + : RestRequest(-1, request, type, options, std::move(data), std::move(verb)) { } - explicit RestRequest(int id, const QNetworkRequest &request, Type type, QByteArray data, QByteArray verb) - : m_id(id), m_type(type), m_request(request), m_data(std::move(data)), m_verb(std::move(verb)) + explicit RestRequest(int id, const QNetworkRequest &request, Type type, Options options, QByteArray data, + QByteArray verb) + : m_id(id), m_type(type), m_options(options), m_request(request), m_data(std::move(data)), + m_verb(std::move(verb)) { - if (verb.isEmpty()) + if (m_verb.isEmpty()) { switch (type) { @@ -73,22 +77,22 @@ class RestRequest return m_verb; } - [[nodiscard]] auto isAuthRequired() const -> bool + [[nodiscard]] auto options() const -> Options { - return m_isAuthRequired; + return m_options; } - void isAuthRequired(int required) + void options(Options options) { - m_isAuthRequired = required; + m_options = options; } private: int m_id = -1; Type m_type; + Options m_options; QNetworkRequest m_request; QByteArray m_data; QByteArray m_verb; - bool m_isAuthRequired = true; }; } // namespace Services diff --git a/src/services/rest/restserviceconnector.cpp b/src/services/rest/restserviceconnector.cpp index bbb4cbe6..c621762d 100644 --- a/src/services/rest/restserviceconnector.cpp +++ b/src/services/rest/restserviceconnector.cpp @@ -119,6 +119,8 @@ void RESTServiceConnector::dequeueRequests() sendRequest(std::move(request), std::move(reply)); } + if (!reason.isEmpty()) qCDebug(m_loggingCategory) << "Could not dequeue high priority requests:" << reason; + while (canSendRequest(reason) && !m_requestQueueLowPriority.empty()) { auto [reply, request] = std::move(m_requestQueueLowPriority.front()); @@ -126,6 +128,8 @@ void RESTServiceConnector::dequeueRequests() sendRequest(std::move(request), std::move(reply)); } + + if (!reason.isEmpty()) qCDebug(m_loggingCategory) << "Could not dequeue low priority requests:" << reason; } auto RESTServiceConnector::canSendRequest(QString &reason) -> bool @@ -158,14 +162,13 @@ auto RESTServiceConnector::canSendRequest(QString &reason) -> bool return true; } -auto RESTServiceConnector::enqueueRequest(RestRequest &&request, QPromise &&reply, bool lowPriority) - -> QFuture +auto RESTServiceConnector::enqueueRequest(RestRequest &&request, QPromise &&reply) -> QFuture { auto future = reply.future(); request.id(m_nextQueueId); m_nextQueueId++; - if (lowPriority) + if (request.options().testFlag(Option::LowPriority)) { m_requestQueueLowPriority.emplace(std::move(reply), std::move(request)); } @@ -249,7 +252,7 @@ void RESTServiceConnector::handleRateLimit(std::pair, RestRe if (header.first == "Retry-After") { // try to interpret retry-after as seconds first - bool ok; + bool ok = false; timeout = std::chrono::seconds(header.second.toInt(&ok)); // if that did not work, it is probably a datetime string diff --git a/src/services/rest/restserviceconnector.h b/src/services/rest/restserviceconnector.h index ce2dd3d7..c14c616c 100644 --- a/src/services/rest/restserviceconnector.h +++ b/src/services/rest/restserviceconnector.h @@ -1,5 +1,6 @@ #pragma once +#include "../options.h" #include "restreply.h" #include "restrequest.h" #include "servicestatus.h" @@ -29,11 +30,11 @@ class RESTServiceConnector : public QObject virtual void disconnectService() = 0; [[nodiscard]] virtual auto isAccessGranted() const -> bool = 0; - virtual auto get(const QNetworkRequest &request, bool isAuthRequired, bool lowPriority) -> QFuture = 0; - virtual auto put(QNetworkRequest request, const QByteArray &data, bool lowPriority) -> QFuture = 0; - virtual auto post(QNetworkRequest request, const QByteArray &data, bool lowPriority) -> QFuture = 0; + virtual auto get(const QNetworkRequest &request, Options options) -> QFuture = 0; + virtual auto put(QNetworkRequest request, const QByteArray &data, Options options) -> QFuture = 0; + virtual auto post(QNetworkRequest request, const QByteArray &data, Options options) -> QFuture = 0; virtual auto customRequest(const QNetworkRequest &req, const QByteArray &verb, const QByteArray &data, - bool isAuthRequired, bool lowPriority) -> QFuture = 0; + Options options) -> QFuture = 0; [[nodiscard]] auto networkManager() const -> QNetworkAccessManager *; void setNetworkManager(QNetworkAccessManager *networkManager); @@ -56,7 +57,7 @@ protected slots: void dequeueRequests(); [[nodiscard]] auto canSendRequest(QString &reason) -> bool; - auto enqueueRequest(RestRequest &&request, QPromise &&reply, bool lowPriority) -> QFuture; + auto enqueueRequest(RestRequest &&request, QPromise &&reply) -> QFuture; auto markRequestActive(RestRequest &&request, QPromise &&reply) -> QFuture; virtual void sendRequest(RestRequest &&request, QPromise &&reply) = 0; diff --git a/src/services/rest/restserviceconnectorlocal.cpp b/src/services/rest/restserviceconnectorlocal.cpp index 8d51dba2..fc913967 100644 --- a/src/services/rest/restserviceconnectorlocal.cpp +++ b/src/services/rest/restserviceconnectorlocal.cpp @@ -96,7 +96,7 @@ void RESTServiceConnectorLocal::sendRequest(RestRequest &&container, QPromiserefresh(); } -auto RESTServiceConnectorLocal::get(const QNetworkRequest &request, bool isAuthRequired, bool lowPriority) - -> QFuture +auto RESTServiceConnectorLocal::get(const QNetworkRequest &request, Options options) -> QFuture { QPromise promise; promise.start(); - RestRequest container(request, RestRequest::Type::GET); - container.isAuthRequired(isAuthRequired); - return enqueueRequest(std::move(container), std::move(promise), lowPriority); + RestRequest container(request, RestRequest::Type::GET, options); + return enqueueRequest(std::move(container), std::move(promise)); } -auto RESTServiceConnectorLocal::put(QNetworkRequest request, const QByteArray &data, bool lowPriority) +auto RESTServiceConnectorLocal::put(QNetworkRequest request, const QByteArray &data, Options options) -> QFuture { QPromise promise; promise.start(); - RestRequest container(request, RestRequest::Type::PUT, data); - return enqueueRequest(std::move(container), std::move(promise), lowPriority); + RestRequest container(request, RestRequest::Type::PUT, options, data); + return enqueueRequest(std::move(container), std::move(promise)); } -auto RESTServiceConnectorLocal::post(QNetworkRequest request, const QByteArray &data, bool lowPriority) +auto RESTServiceConnectorLocal::post(QNetworkRequest request, const QByteArray &data, Options options) -> QFuture { QPromise promise; promise.start(); - RestRequest container(request, RestRequest::Type::POST, data); - return enqueueRequest(std::move(container), std::move(promise), lowPriority); + RestRequest container(request, RestRequest::Type::POST, options, data); + return enqueueRequest(std::move(container), std::move(promise)); } auto RESTServiceConnectorLocal::customRequest(const QNetworkRequest &request, const QByteArray &verb, - const QByteArray &data, bool isAuthRequired, bool lowPriority) - -> QFuture + const QByteArray &data, Options options) -> QFuture { QPromise promise; promise.start(); - RestRequest container(request, RestRequest::Type::CUSTOM, data, verb); - container.isAuthRequired(isAuthRequired); - return enqueueRequest(std::move(container), std::move(promise), lowPriority); + RestRequest container(request, RestRequest::Type::CUSTOM, options, data, verb); + return enqueueRequest(std::move(container), std::move(promise)); } auto RESTServiceConnectorLocal::makeRequestor() -> O2Requestor * diff --git a/src/services/rest/restserviceconnectorlocal.h b/src/services/rest/restserviceconnectorlocal.h index 74ca7caa..149f244e 100644 --- a/src/services/rest/restserviceconnectorlocal.h +++ b/src/services/rest/restserviceconnectorlocal.h @@ -36,11 +36,11 @@ class RESTServiceConnectorLocal : public RESTServiceConnector void disconnectService() override; [[nodiscard]] auto isAccessGranted() const -> bool override; - auto get(const QNetworkRequest &request, bool isAuthRequired, bool lowPriority) -> QFuture override; - auto put(QNetworkRequest request, const QByteArray &data, bool lowPriority) -> QFuture override; - auto post(QNetworkRequest request, const QByteArray &data, bool lowPriority) -> QFuture override; - auto customRequest(const QNetworkRequest &req, const QByteArray &verb, const QByteArray &data, bool isAuthRequired, - bool lowPriority) -> QFuture override; + auto get(const QNetworkRequest &request, Options options) -> QFuture override; + auto put(QNetworkRequest request, const QByteArray &data, Options options) -> QFuture override; + auto post(QNetworkRequest request, const QByteArray &data, Options options) -> QFuture override; + auto customRequest(const QNetworkRequest &req, const QByteArray &verb, const QByteArray &data, Options options) + -> QFuture override; protected: O2 *m_o2 = nullptr; diff --git a/src/services/spotify/api/albumapi.cpp b/src/services/spotify/api/albumapi.cpp index 69f73912..3c0fa32f 100644 --- a/src/services/spotify/api/albumapi.cpp +++ b/src/services/spotify/api/albumapi.cpp @@ -16,7 +16,7 @@ AlbumAPI::AlbumAPI(Spotify *parent) : m_spotify(parent) { } -auto AlbumAPI::getAlbum(const QString &id, bool lowPriority) -> QFuture +auto AlbumAPI::getAlbum(const QString &id, Options options) -> QFuture { if (id.isEmpty()) { @@ -30,7 +30,7 @@ auto AlbumAPI::getAlbum(const QString &id, bool lowPriority) -> QFuture QFutureget(NetworkUtils::makeJsonRequest(url), true, lowPriority).then(callback).unwrap(); + return m_spotify->get(NetworkUtils::makeJsonRequest(url), Option::Authenticated | options).then(callback).unwrap(); } -auto AlbumAPI::getAlbumTracks(const QString &id, bool lowPriority) -> QFuture +auto AlbumAPI::getAlbumTracks(const QString &id, Options options) -> QFuture { if (id.isEmpty()) { @@ -65,7 +65,7 @@ auto AlbumAPI::getAlbumTracks(const QString &id, bool lowPriority) -> QFuture QFuture { + auto callback = [this, options](const RestReply &reply) -> QFuture { if (reply.hasError()) { qCWarning(gmSpotifyAlbums()) << reply.errorText(); @@ -75,21 +75,21 @@ auto AlbumAPI::getAlbumTracks(const QString &id, bool lowPriority) -> QFutureget(NetworkUtils::makeJsonRequest(url), true, lowPriority).then(callback).unwrap(); + return m_spotify->get(NetworkUtils::makeJsonRequest(url), Option::Authenticated | options).then(callback).unwrap(); } -auto AlbumAPI::getAlbumTracks(SpotifyAlbum &&album, bool lowPriority) -> QFuture +auto AlbumAPI::getAlbumTracks(SpotifyAlbum &&album, Options options) -> QFuture { if (album.tracks.next.isEmpty()) return QtFuture::makeReadyFuture(album); const QUrl url(album.tracks.next); - const auto callback = [this, lowPriority, album = std::move(album)](const RestReply &reply) mutable { + const auto callback = [this, options, album = std::move(album)](const RestReply &reply) mutable { if (reply.hasError()) { qCWarning(gmSpotifyAlbums()) << reply.errorText(); @@ -99,19 +99,19 @@ auto AlbumAPI::getAlbumTracks(SpotifyAlbum &&album, bool lowPriority) -> QFuture album.tracks.append(SpotifyTrackList::fromJson(reply.data())); if (!album.tracks.next.isEmpty()) { - return getAlbumTracks(std::move(album), lowPriority); + return getAlbumTracks(std::move(album), options); } return QtFuture::makeReadyFuture(album); }; - return m_spotify->get(NetworkUtils::makeJsonRequest(url), true, lowPriority).then(callback).unwrap(); + return m_spotify->get(NetworkUtils::makeJsonRequest(url), Option::Authenticated | options).then(callback).unwrap(); } -auto AlbumAPI::getAlbumTracks(SpotifyTrackList &&tracklist, bool lowPriority) -> QFuture +auto AlbumAPI::getAlbumTracks(SpotifyTrackList &&tracklist, Options options) -> QFuture { const QUrl url(tracklist.next); - const auto callback = [this, tracklist = std::move(tracklist), lowPriority](const RestReply &reply) mutable { + const auto callback = [this, tracklist = std::move(tracklist), options](const RestReply &reply) mutable { if (reply.hasError()) { qCWarning(gmSpotifyAlbums()) << reply.errorText(); @@ -121,10 +121,10 @@ auto AlbumAPI::getAlbumTracks(SpotifyTrackList &&tracklist, bool lowPriority) -> tracklist.append(SpotifyTrackList::fromJson(reply.data())); if (!tracklist.next.isEmpty()) { - return getAlbumTracks(std::move(tracklist), lowPriority); + return getAlbumTracks(std::move(tracklist), options); } return QtFuture::makeReadyFuture(tracklist); }; - return m_spotify->get(NetworkUtils::makeJsonRequest(url), true, lowPriority).then(callback).unwrap(); + return m_spotify->get(NetworkUtils::makeJsonRequest(url), Option::Authenticated | options).then(callback).unwrap(); } diff --git a/src/services/spotify/api/albumapi.h b/src/services/spotify/api/albumapi.h index a220426b..168a3dfd 100644 --- a/src/services/spotify/api/albumapi.h +++ b/src/services/spotify/api/albumapi.h @@ -1,5 +1,6 @@ #pragma once +#include "../../options.h" #include "../data/spotifyalbum.h" #include #include @@ -14,11 +15,11 @@ class AlbumAPI friend Spotify; public: - [[nodiscard]] auto getAlbum(const QString &id, bool lowPriority) -> QFuture; + [[nodiscard]] auto getAlbum(const QString &id, Options options) -> QFuture; - [[nodiscard]] auto getAlbumTracks(const QString &id, bool lowPriority) -> QFuture; - [[nodiscard]] auto getAlbumTracks(SpotifyAlbum &&album, bool lowPriority) -> QFuture; - [[nodiscard]] auto getAlbumTracks(SpotifyTrackList &&tracklist, bool lowPriority) -> QFuture; + [[nodiscard]] auto getAlbumTracks(const QString &id, Options options) -> QFuture; + [[nodiscard]] auto getAlbumTracks(SpotifyAlbum &&album, Options options) -> QFuture; + [[nodiscard]] auto getAlbumTracks(SpotifyTrackList &&tracklist, Options options) -> QFuture; private: explicit AlbumAPI(Spotify *parent); diff --git a/src/services/spotify/api/playerapi.cpp b/src/services/spotify/api/playerapi.cpp index c7e46a57..acb428a8 100644 --- a/src/services/spotify/api/playerapi.cpp +++ b/src/services/spotify/api/playerapi.cpp @@ -21,7 +21,7 @@ PlayerAPI::PlayerAPI(Spotify *parent) : m_spotify(parent) auto PlayerAPI::play() const -> QFuture { - return m_spotify->put(QUrl(u"https://api.spotify.com/v1/me/player/play"_s), {}, false); + return m_spotify->put(QUrl(u"https://api.spotify.com/v1/me/player/play"_s), {}, Option::Authenticated); } auto PlayerAPI::play(const QString &deviceId, const QJsonObject &body) const -> QFuture @@ -36,7 +36,7 @@ auto PlayerAPI::play(const QString &deviceId, const QJsonObject &body) const -> const QJsonDocument content(body); return m_spotify->put(NetworkUtils::makeJsonRequest(url), content.toJson(QJsonDocument::JsonFormat::Compact), - false); + Option::Authenticated); } auto PlayerAPI::play(const QString &uri) const -> QFuture @@ -99,7 +99,7 @@ auto PlayerAPI::pause(const QString &deviceId) const -> QFuture url.setQuery(query); } - return m_spotify->put(NetworkUtils::makeJsonRequest(url), {}, false); + return m_spotify->put(NetworkUtils::makeJsonRequest(url), {}, Option::Authenticated); } auto PlayerAPI::next() const -> QFuture @@ -117,7 +117,7 @@ auto PlayerAPI::next(const QString &deviceId) const -> QFuture url.setQuery(query); } - return m_spotify->post(NetworkUtils::makeJsonRequest(url), {}, false); + return m_spotify->post(NetworkUtils::makeJsonRequest(url), {}, Option::Authenticated); } auto PlayerAPI::previous() const -> QFuture @@ -135,7 +135,7 @@ auto PlayerAPI::previous(const QString &deviceId) const -> QFuture url.setQuery(query); } - return m_spotify->post(NetworkUtils::makeJsonRequest(url), {}, false); + return m_spotify->post(NetworkUtils::makeJsonRequest(url), {}, Option::Authenticated); } auto PlayerAPI::seek(int positionMs) const -> QFuture @@ -155,7 +155,7 @@ auto PlayerAPI::seek(int positionMs, const QString &deviceId) const -> QFutureput(NetworkUtils::makeJsonRequest(url), {}, false); + return m_spotify->put(NetworkUtils::makeJsonRequest(url), {}, Option::Authenticated); } auto PlayerAPI::getState() -> QFuture @@ -190,7 +190,7 @@ auto PlayerAPI::getState(const QStringList &additionalTypes, const QString &mark return QtFuture::makeReadyFuture(SpotifyPlaybackState::fromJson(QJsonDocument::fromJson(reply.data()))); }; - return m_spotify->get(NetworkUtils::makeJsonRequest(url), true, false).then(callback).unwrap(); + return m_spotify->get(NetworkUtils::makeJsonRequest(url), Option::Authenticated).then(callback).unwrap(); } auto PlayerAPI::getCurrentlyPlaying() -> QFuture @@ -227,7 +227,7 @@ auto PlayerAPI::getCurrentlyPlaying(const QStringList &additionalTypes, const QS return QtFuture::makeReadyFuture(SpotifyCurrentTrack::fromJson(reply.data())); }; - return m_spotify->get(NetworkUtils::makeJsonRequest(url), true, false).then(callback).unwrap(); + return m_spotify->get(NetworkUtils::makeJsonRequest(url), Option::Authenticated).then(callback).unwrap(); } auto PlayerAPI::transfer(const QStringList &deviceIds) const -> QFuture @@ -242,7 +242,8 @@ auto PlayerAPI::transfer(const QStringList &deviceIds, bool play) const -> QFutu const QJsonObject body{{u"device_ids"_s, QJsonArray::fromStringList(deviceIds)}, {u"play"_s, play}}; const auto doc = QJsonDocument(body); - return m_spotify->put(NetworkUtils::makeJsonRequest(url), doc.toJson(QJsonDocument::Compact), false); + return m_spotify->put(NetworkUtils::makeJsonRequest(url), doc.toJson(QJsonDocument::Compact), + Option::Authenticated); } auto PlayerAPI::devices() -> QFuture @@ -255,7 +256,7 @@ auto PlayerAPI::devices() -> QFuture return SpotifyDevice::fromJson(devices); }; - return m_spotify->get(NetworkUtils::makeJsonRequest(url), true, false).then(callback); + return m_spotify->get(NetworkUtils::makeJsonRequest(url), Option::Authenticated).then(callback); } auto PlayerAPI::repeat(SpotifyRepeatMode mode) const -> QFuture @@ -290,7 +291,7 @@ auto PlayerAPI::repeat(const QString &state, const QString &deviceId) const -> Q } url.setQuery(query); - return m_spotify->put(NetworkUtils::makeJsonRequest(url), {}, false); + return m_spotify->put(NetworkUtils::makeJsonRequest(url), {}, Option::Authenticated); } auto PlayerAPI::volume(int volumePercent) const -> QFuture @@ -309,7 +310,7 @@ auto PlayerAPI::volume(int volumePercent, const QString &deviceId) const -> QFut } url.setQuery(query); - return m_spotify->put(NetworkUtils::makeJsonRequest(url), {}, false); + return m_spotify->put(NetworkUtils::makeJsonRequest(url), {}, Option::Authenticated); } auto PlayerAPI::shuffle(bool state) const -> QFuture @@ -328,7 +329,7 @@ auto PlayerAPI::shuffle(bool state, const QString &deviceId) const -> QFutureput(NetworkUtils::makeJsonRequest(url), {}, false); + return m_spotify->put(NetworkUtils::makeJsonRequest(url), {}, Option::Authenticated); } auto PlayerAPI::getRecentlyPlayed(int limit) const -> QFuture @@ -363,7 +364,7 @@ auto PlayerAPI::getRecentlyPlayed(const QDateTime &after, const QDateTime &befor query.addQueryItem(u"limit"_s, QString::number(std::clamp(limit, 0, MAX_RECENTLY_PLAYED))); url.setQuery(query); - return m_spotify->get(NetworkUtils::makeJsonRequest(url), true, true); + return m_spotify->get(NetworkUtils::makeJsonRequest(url), Option::Authenticated | Option::LowPriority); } auto PlayerAPI::addToQueue(const QString &uri) const -> QFuture @@ -389,5 +390,5 @@ auto PlayerAPI::addToQueue(const QString &uri, const QString &deviceId) const -> } url.setQuery(query); - return m_spotify->post(NetworkUtils::makeJsonRequest(url), {}, false); + return m_spotify->post(NetworkUtils::makeJsonRequest(url), {}, Option::Authenticated); } diff --git a/src/services/spotify/api/playlistsapi.cpp b/src/services/spotify/api/playlistsapi.cpp index 4e1857ae..59ddcacd 100644 --- a/src/services/spotify/api/playlistsapi.cpp +++ b/src/services/spotify/api/playlistsapi.cpp @@ -17,7 +17,7 @@ PlaylistsAPI::PlaylistsAPI(Spotify *parent) : m_spotify(parent) { } -auto PlaylistsAPI::getPlaylist(const QString &id, bool lowPriority) -> QFuture +auto PlaylistsAPI::getPlaylist(const QString &id, Options options) -> QFuture { if (id.isEmpty()) { @@ -40,10 +40,10 @@ auto PlaylistsAPI::getPlaylist(const QString &id, bool lowPriority) -> QFutureget(NetworkUtils::makeJsonRequest(url), true, lowPriority).then(callback).unwrap(); + return m_spotify->get(NetworkUtils::makeJsonRequest(url), Option::Authenticated | options).then(callback).unwrap(); } -auto PlaylistsAPI::getPlaylistTracks(const QString &id, bool lowPriority) -> QFuture +auto PlaylistsAPI::getPlaylistTracks(const QString &id, Options options) -> QFuture { if (id.isEmpty()) { @@ -58,7 +58,7 @@ auto PlaylistsAPI::getPlaylistTracks(const QString &id, bool lowPriority) -> QFu query.addQueryItem(u"market"_s, u"from_token"_s); url.setQuery(query); - const auto callback = [this, lowPriority](const RestReply &reply) -> QFuture { + const auto callback = [this, options](const RestReply &reply) -> QFuture { if (reply.hasError()) { qCWarning(gmSpotifyPlaylists()) << reply.errorText(); @@ -68,19 +68,19 @@ auto PlaylistsAPI::getPlaylistTracks(const QString &id, bool lowPriority) -> QFu auto tracklist = SpotifyTrackList::fromJson(reply.data()); if (!tracklist.next.isEmpty()) { - return getPlaylistTracks(std::move(tracklist), lowPriority); + return getPlaylistTracks(std::move(tracklist), options); } return QtFuture::makeReadyFuture(tracklist); }; - return m_spotify->get(NetworkUtils::makeJsonRequest(url), true, lowPriority).then(callback).unwrap(); + return m_spotify->get(NetworkUtils::makeJsonRequest(url), Option::Authenticated | options).then(callback).unwrap(); } -auto PlaylistsAPI::getPlaylistTracks(SpotifyTrackList &&tracklist, bool lowPriority) -> QFuture +auto PlaylistsAPI::getPlaylistTracks(SpotifyTrackList &&tracklist, Options options) -> QFuture { const QUrl url(tracklist.next); - const auto callback = [this, lowPriority, tracklist = std::move(tracklist)](const RestReply &reply) mutable { + const auto callback = [this, options, tracklist = std::move(tracklist)](const RestReply &reply) mutable { if (reply.hasError()) { qCWarning(gmSpotifyPlaylists()) << reply.errorText(); @@ -90,15 +90,15 @@ auto PlaylistsAPI::getPlaylistTracks(SpotifyTrackList &&tracklist, bool lowPrior tracklist.append(SpotifyTrackList::fromJson(reply.data())); if (!tracklist.next.isEmpty()) { - return getPlaylistTracks(std::move(tracklist), lowPriority); + return getPlaylistTracks(std::move(tracklist), options); } return QtFuture::makeReadyFuture(tracklist); }; - return m_spotify->get(NetworkUtils::makeJsonRequest(url), true, lowPriority).then(callback).unwrap(); + return m_spotify->get(NetworkUtils::makeJsonRequest(url), Option::Authenticated | options).then(callback).unwrap(); } -auto PlaylistsAPI::updatePlaylist(const PlaylistConfig &config, bool lowPriority) const -> QFuture +auto PlaylistsAPI::updatePlaylist(const PlaylistConfig &config, Options options) const -> QFuture { QUrl url(u"https://api.spotify.com/v1/playlists/%1"_s.arg(config.id)); @@ -115,5 +115,5 @@ auto PlaylistsAPI::updatePlaylist(const PlaylistConfig &config, bool lowPriority const QJsonDocument content(body); return m_spotify->put(NetworkUtils::makeJsonRequest(url), content.toJson(QJsonDocument::JsonFormat::Compact), - lowPriority); + options); } diff --git a/src/services/spotify/api/playlistsapi.h b/src/services/spotify/api/playlistsapi.h index 22a05fb6..f7c44348 100644 --- a/src/services/spotify/api/playlistsapi.h +++ b/src/services/spotify/api/playlistsapi.h @@ -1,5 +1,6 @@ #pragma once +#include "../../options.h" #include "../data/spotifyplaylist.h" #include "../data/spotifytracklist.h" #include "rest/restreply.h" @@ -26,13 +27,13 @@ class PlaylistsAPI friend Spotify; public: - [[nodiscard]] auto getPlaylist(const QString &id, bool lowPriority) -> QFuture; + [[nodiscard]] auto getPlaylist(const QString &id, Options options) -> QFuture; - [[nodiscard]] auto getPlaylistTracks(const QString &id, bool lowPriority) -> QFuture; + [[nodiscard]] auto getPlaylistTracks(const QString &id, Options options) -> QFuture; - [[nodiscard]] auto getPlaylistTracks(SpotifyTrackList &&tracks, bool lowPriority) -> QFuture; + [[nodiscard]] auto getPlaylistTracks(SpotifyTrackList &&tracks, Options options) -> QFuture; - [[nodiscard]] auto updatePlaylist(const PlaylistConfig &config, bool lowPriority) const -> QFuture; + [[nodiscard]] auto updatePlaylist(const PlaylistConfig &config, Options options) const -> QFuture; private: explicit PlaylistsAPI(Spotify *parent); diff --git a/src/services/spotify/api/tracksapi.cpp b/src/services/spotify/api/tracksapi.cpp index 9726a0a9..912c9b28 100644 --- a/src/services/spotify/api/tracksapi.cpp +++ b/src/services/spotify/api/tracksapi.cpp @@ -15,7 +15,7 @@ TracksAPI::TracksAPI(Spotify *parent) : m_spotify(parent) { } -auto TracksAPI::getTrack(const QString &id, bool lowPriority) -> QFuture +auto TracksAPI::getTrack(const QString &id, Options options) -> QFuture { if (id.isEmpty()) { @@ -39,10 +39,10 @@ auto TracksAPI::getTrack(const QString &id, bool lowPriority) -> QFutureget(NetworkUtils::makeJsonRequest(url), true, lowPriority).then(callback).unwrap(); + return m_spotify->get(NetworkUtils::makeJsonRequest(url), Option::Authenticated | options).then(callback).unwrap(); } -auto TracksAPI::getTracks(const QStringList &ids, bool lowPriority) -> QFuture> +auto TracksAPI::getTracks(const QStringList &ids, Options options) -> QFuture> { if (ids.isEmpty()) { @@ -50,10 +50,10 @@ auto TracksAPI::getTracks(const QStringList &ids, bool lowPriority) -> QFuture &&previous, bool lowPriority) +auto TracksAPI::getTracks(const QStringList &ids, std::vector &&previous, Options options) -> QFuture> { auto batch = getNextBatch(ids, previous); @@ -66,7 +66,7 @@ auto TracksAPI::getTracks(const QStringList &ids, std::vector &&pr query.addQueryItem(u"market"_s, u"from_token"_s); url.setQuery(query); - const auto callback = [this, ids, lowPriority, previous = std::move(previous)](const RestReply &reply) mutable { + const auto callback = [this, ids, options, previous = std::move(previous)](const RestReply &reply) mutable { if (reply.hasError()) { qCWarning(gmSpotifyTracks()) << "getTracks():" << reply.errorText(); @@ -82,13 +82,13 @@ auto TracksAPI::getTracks(const QStringList &ids, std::vector &&pr if (ids.length() > previous.size()) { - return getTracks(ids, std::move(previous), lowPriority); + return getTracks(ids, std::move(previous), options); } return QtFuture::makeReadyFuture(previous); }; - return m_spotify->get(NetworkUtils::makeJsonRequest(url), true, lowPriority).then(callback).unwrap(); + return m_spotify->get(NetworkUtils::makeJsonRequest(url), Option::Authenticated | options).then(callback).unwrap(); } auto TracksAPI::getNextBatch(const QStringList &ids, const std::vector &previous) -> QStringList diff --git a/src/services/spotify/api/tracksapi.h b/src/services/spotify/api/tracksapi.h index 2de1eda2..df8617dd 100644 --- a/src/services/spotify/api/tracksapi.h +++ b/src/services/spotify/api/tracksapi.h @@ -1,5 +1,6 @@ #pragma once +#include "../../options.h" #include "../data/spotifytrack.h" #include #include @@ -15,15 +16,15 @@ class TracksAPI friend Spotify; public: - [[nodiscard]] auto getTrack(const QString &id, bool lowPriority) -> QFuture; + [[nodiscard]] auto getTrack(const QString &id, Options options) -> QFuture; - [[nodiscard]] auto getTracks(const QStringList &ids, bool lowPriority) -> QFuture>; + [[nodiscard]] auto getTracks(const QStringList &ids, Options options) -> QFuture>; private: explicit TracksAPI(Spotify *parent); const QPointer m_spotify = nullptr; - [[nodiscard]] auto getTracks(const QStringList &ids, std::vector &&previous, bool lowPriority) + [[nodiscard]] auto getTracks(const QStringList &ids, std::vector &&previous, Options options) -> QFuture>; [[nodiscard]] static auto getNextBatch(const QStringList &ids, const std::vector &previous) -> QStringList; diff --git a/src/services/spotify/spotify.cpp b/src/services/spotify/spotify.cpp index c1f5e831..093be668 100644 --- a/src/services/spotify/spotify.cpp +++ b/src/services/spotify/spotify.cpp @@ -94,7 +94,7 @@ auto Spotify::isGranted() const -> bool return m_connector->isAccessGranted(); } -auto Spotify::get(const QNetworkRequest &request, bool isAuthRequired, bool lowPriority) -> QFuture +auto Spotify::get(const QNetworkRequest &request, Options options) -> QFuture { if (!connected() || !m_connector) return {}; @@ -103,15 +103,15 @@ auto Spotify::get(const QNetworkRequest &request, bool isAuthRequired, bool lowP return std::move(reply); }; - return m_connector->get(request, isAuthRequired, lowPriority).then(callback); + return m_connector->get(request, options).then(callback); } -auto Spotify::get(const QUrl &url, bool isAuthRequired, bool lowPriority) -> QFuture +auto Spotify::get(const QUrl &url, Options options) -> QFuture { - return get(QNetworkRequest(url), isAuthRequired, lowPriority); + return get(QNetworkRequest(url), options); } -auto Spotify::put(const QNetworkRequest &request, const QByteArray &data, bool lowPriority) -> QFuture +auto Spotify::put(const QNetworkRequest &request, const QByteArray &data, Options options) -> QFuture { if (!connected() || !m_connector) return {}; @@ -120,15 +120,15 @@ auto Spotify::put(const QNetworkRequest &request, const QByteArray &data, bool l return std::move(reply); }; - return m_connector->put(request, data, lowPriority).then(callback); + return m_connector->put(request, data, options).then(callback); } -auto Spotify::put(const QUrl &url, const QByteArray &data, bool lowPriority) -> QFuture +auto Spotify::put(const QUrl &url, const QByteArray &data, Options options) -> QFuture { - return put(QNetworkRequest(url), data, lowPriority); + return put(QNetworkRequest(url), data, options); } -auto Spotify::post(const QNetworkRequest &request, const QByteArray &data, bool lowPriority) -> QFuture +auto Spotify::post(const QNetworkRequest &request, const QByteArray &data, Options options) -> QFuture { if (!connected() || !m_connector) return {}; @@ -137,7 +137,7 @@ auto Spotify::post(const QNetworkRequest &request, const QByteArray &data, bool return std::move(reply); }; - return m_connector->post(request, data, lowPriority).then(callback); + return m_connector->post(request, data, options).then(callback); } auto Spotify::clientStatus() const -> Status * diff --git a/src/services/spotify/spotify.h b/src/services/spotify/spotify.h index 11bff569..aa4e01a0 100644 --- a/src/services/spotify/spotify.h +++ b/src/services/spotify/spotify.h @@ -1,5 +1,6 @@ #pragma once +#include "../options.h" #include "api/albumapi.h" #include "api/playerapi.h" #include "api/playlistsapi.h" @@ -34,11 +35,11 @@ class Spotify : public Services::Service void grant(); [[nodiscard]] auto isGranted() const -> bool; - auto get(const QNetworkRequest &request, bool isAuthRequired, bool lowPriority) -> QFuture; - auto get(const QUrl &url, bool isAuthRequired, bool lowPriority) -> QFuture; - auto put(const QNetworkRequest &request, const QByteArray &data, bool lowPriority) -> QFuture; - auto put(const QUrl &url, const QByteArray &data, bool lowPriority) -> QFuture; - auto post(const QNetworkRequest &request, const QByteArray &data, bool lowPriority) -> QFuture; + auto get(const QNetworkRequest &request, Options options) -> QFuture; + auto get(const QUrl &url, Options options) -> QFuture; + auto put(const QNetworkRequest &request, const QByteArray &data, Options options) -> QFuture; + auto put(const QUrl &url, const QByteArray &data, Options options) -> QFuture; + auto post(const QNetworkRequest &request, const QByteArray &data, Options options) -> QFuture; [[nodiscard]] auto clientStatus() const -> Status *; diff --git a/src/services/spotify/spotifyconnectorserver.cpp b/src/services/spotify/spotifyconnectorserver.cpp index 952f7292..e75fa8b1 100644 --- a/src/services/spotify/spotifyconnectorserver.cpp +++ b/src/services/spotify/spotifyconnectorserver.cpp @@ -53,7 +53,8 @@ void SpotifyConnectorServer::disconnectService() void SpotifyConnectorServer::sendRequest(RestRequest &&container, QPromise &&promise) { - auto request = container.isAuthRequired() ? addAuthHeader(container.request()) : container.request(); + auto request = + container.options().testFlag(Option::Authenticated) ? addAuthHeader(container.request()) : container.request(); QNetworkReply *reply = nullptr; switch (container.type()) @@ -83,51 +84,46 @@ void SpotifyConnectorServer::sendRequest(RestRequest &&container, QPromise QFuture +auto SpotifyConnectorServer::get(const QNetworkRequest &request, Options options) -> QFuture { QPromise promise; promise.start(); - RestRequest container(request, RestRequest::Type::GET); - container.isAuthRequired(isAuthRequired); - return enqueueRequest(std::move(container), std::move(promise), lowPriority); + RestRequest container(request, RestRequest::Type::GET, options); + return enqueueRequest(std::move(container), std::move(promise)); } -auto SpotifyConnectorServer::get(const QUrl &url, bool isAuthRequired, bool lowPriority) -> QFuture +auto SpotifyConnectorServer::get(const QUrl &url, Options options) -> QFuture { - return get(QNetworkRequest(url), isAuthRequired, lowPriority); + return get(QNetworkRequest(url), options); } -auto SpotifyConnectorServer::put(QNetworkRequest request, const QByteArray &data, bool lowPriority) - -> QFuture +auto SpotifyConnectorServer::put(QNetworkRequest request, const QByteArray &data, Options options) -> QFuture { QPromise promise; promise.start(); - RestRequest container(request, RestRequest::Type::PUT, data); - return enqueueRequest(std::move(container), std::move(promise), lowPriority); + RestRequest container(request, RestRequest::Type::PUT, options, data); + return enqueueRequest(std::move(container), std::move(promise)); } -auto SpotifyConnectorServer::post(QNetworkRequest request, const QByteArray &data, bool lowPriority) +auto SpotifyConnectorServer::post(QNetworkRequest request, const QByteArray &data, Options options) -> QFuture { QPromise promise; promise.start(); - RestRequest container(request, RestRequest::Type::POST, data); - return enqueueRequest(std::move(container), std::move(promise), lowPriority); + RestRequest container(request, RestRequest::Type::POST, options, data); + return enqueueRequest(std::move(container), std::move(promise)); } auto SpotifyConnectorServer::customRequest(const QNetworkRequest &request, const QByteArray &verb, - const QByteArray &data, bool isAuthRequired, bool lowPriority) - -> QFuture + const QByteArray &data, Options options) -> QFuture { Q_UNUSED(request) Q_UNUSED(verb) Q_UNUSED(data) - Q_UNUSED(isAuthRequired) - Q_UNUSED(lowPriority); + Q_UNUSED(options) throw NotImplementedException(); } diff --git a/src/services/spotify/spotifyconnectorserver.h b/src/services/spotify/spotifyconnectorserver.h index bde159c9..36eb7a38 100644 --- a/src/services/spotify/spotifyconnectorserver.h +++ b/src/services/spotify/spotifyconnectorserver.h @@ -27,14 +27,12 @@ class SpotifyConnectorServer : public RESTServiceConnector return m_isAccessGranted; } - auto get(const QUrl &url, bool isAuthRequired, bool lowPriority) -> QFuture; - auto get(const QNetworkRequest &request, bool isAuthRequired, bool lowPriority) -> QFuture override; - auto put(QNetworkRequest request, const QByteArray &data = "", bool lowPriority = false) + auto get(const QUrl &url, Options options) -> QFuture; + auto get(const QNetworkRequest &request, Options options) -> QFuture override; + auto put(QNetworkRequest request, const QByteArray &data, Options options) -> QFuture override; + auto post(QNetworkRequest request, const QByteArray &data, Options options) -> QFuture override; + auto customRequest(const QNetworkRequest &request, const QByteArray &verb, const QByteArray &data, Options options) -> QFuture override; - auto post(QNetworkRequest request, const QByteArray &data = "", bool lowPriority = false) - -> QFuture override; - auto customRequest(const QNetworkRequest &request, const QByteArray &verb, const QByteArray &data, - bool isAuthRequired, bool lowPriority) -> QFuture override; private: O0SettingsStore m_settingsStore = O0SettingsStore(QStringLiteral("gm-companion")); diff --git a/src/services/youtube/pipedconnector.cpp b/src/services/youtube/pipedconnector.cpp index 60fa7daa..67e4110b 100644 --- a/src/services/youtube/pipedconnector.cpp +++ b/src/services/youtube/pipedconnector.cpp @@ -89,50 +89,43 @@ auto PipedConnector::isAccessGranted() const -> bool return true; } -auto PipedConnector::get(const QNetworkRequest &request, bool isAuthRequired, bool lowPriority) -> QFuture +auto PipedConnector::get(const QNetworkRequest &request, Options options) -> QFuture { QPromise promise; promise.start(); - RestRequest container(request, RestRequest::Type::GET); - container.isAuthRequired(isAuthRequired); - return enqueueRequest(std::move(container), std::move(promise), lowPriority); + RestRequest container(request, RestRequest::Type::GET, options); + return enqueueRequest(std::move(container), std::move(promise)); } -auto PipedConnector::put(QNetworkRequest request, const QByteArray &data, bool lowPriority) -> QFuture +auto PipedConnector::put(QNetworkRequest request, const QByteArray &data, Options options) -> QFuture { Q_UNUSED(request) Q_UNUSED(data) - Q_UNUSED(lowPriority) + Q_UNUSED(options) throw NotImplementedException(); } -auto PipedConnector::post(QNetworkRequest request, const QByteArray &data, bool lowPriority) -> QFuture +auto PipedConnector::post(QNetworkRequest request, const QByteArray &data, Options options) -> QFuture { Q_UNUSED(request) Q_UNUSED(data) - Q_UNUSED(lowPriority) + Q_UNUSED(options) throw NotImplementedException(); } auto PipedConnector::customRequest(const QNetworkRequest &request, const QByteArray &verb, const QByteArray &data, - bool isAuthRequired, bool lowPriority) -> QFuture + Options options) -> QFuture { Q_UNUSED(request) Q_UNUSED(verb) Q_UNUSED(data) - Q_UNUSED(isAuthRequired) - Q_UNUSED(lowPriority) + Q_UNUSED(options) throw NotImplementedException(); } void PipedConnector::sendRequest(RestRequest &&container, QPromise &&promise) { - if (container.isAuthRequired()) - { - throw NotImplementedException(); - } - auto request = container.request(); request.setUrl(QUrl(m_currentInstance.apiUrl + request.url().toString())); QNetworkReply *reply = nullptr; @@ -150,6 +143,7 @@ void PipedConnector::sendRequest(RestRequest &&container, QPromise && auto id = container.id(); QtFuture::connect(reply, &QNetworkReply::finished).then([this, reply, id]() mutable { onReplyReceived(id, reply->error(), reply->errorString(), reply->readAll(), reply->rawHeaderPairs()); + reply->deleteLater(); }); markRequestActive(std::move(container), std::move(promise)); diff --git a/src/services/youtube/pipedconnector.h b/src/services/youtube/pipedconnector.h index 53453884..49ca7efd 100644 --- a/src/services/youtube/pipedconnector.h +++ b/src/services/youtube/pipedconnector.h @@ -31,11 +31,11 @@ class PipedConnector : public RESTServiceConnector void disconnectService() override; [[nodiscard]] auto isAccessGranted() const -> bool override; - auto get(const QNetworkRequest &request, bool isAuthRequired, bool lowPriority) -> QFuture override; - auto put(QNetworkRequest request, const QByteArray &data, bool lowPriority) -> QFuture override; - auto post(QNetworkRequest request, const QByteArray &data, bool lowPriority) -> QFuture override; - auto customRequest(const QNetworkRequest &request, const QByteArray &verb, const QByteArray &data, - bool isAuthRequired, bool lowPriority) -> QFuture override; + auto get(const QNetworkRequest &request, Options options) -> QFuture override; + auto put(QNetworkRequest request, const QByteArray &data, Options options) -> QFuture override; + auto post(QNetworkRequest request, const QByteArray &data, Options options) -> QFuture override; + auto customRequest(const QNetworkRequest &request, const QByteArray &verb, const QByteArray &data, Options options) + -> QFuture override; protected: void sendRequest(RestRequest &&request, QPromise &&reply) override; diff --git a/src/services/youtube/youtube.cpp b/src/services/youtube/youtube.cpp index e21f89c5..7b6dc05d 100644 --- a/src/services/youtube/youtube.cpp +++ b/src/services/youtube/youtube.cpp @@ -37,15 +37,15 @@ void YouTube::selectNewPipedInstance() m_connector.selectBestInstance(); } -auto YouTube::getStreamInfoAsync(const VideoId &id, bool lowPriority) -> QFuture +auto YouTube::getStreamInfoAsync(const VideoId &id, Options options) -> QFuture { if (!id.isValid()) return {}; const auto path = u"/streams/%1"_s.arg(id.toString()); const auto request = QNetworkRequest(path); - return m_connector.get(request, false, lowPriority) - .then([this, id, lowPriority](const RestReply &reply) { + return m_connector.get(request, options) + .then([this, id, options](const RestReply &reply) { bool ok = false; if (!reply.hasError()) @@ -59,35 +59,34 @@ auto YouTube::getStreamInfoAsync(const VideoId &id, bool lowPriority) -> QFuture } m_connector.selectBestInstance(); - return getStreamInfoAsync(id, lowPriority); + return getStreamInfoAsync(id, options); }) .unwrap(); } -auto YouTube::getPlaylistInfoAsync(const PlaylistId &id, bool lowPriority) -> QFuture +auto YouTube::getPlaylistInfoAsync(const PlaylistId &id, Options options) -> QFuture { if (!id.isValid()) return {}; const auto path = u"/playlists/%1"_s.arg(id.toString()); const auto request = QNetworkRequest(path); - return m_connector.get(request, false, lowPriority) - .then([this, id, lowPriority](const RestReply &reply) { + return m_connector.get(request, options) + .then([this, id, options](const RestReply &reply) { bool ok = false; if (!reply.hasError()) { - auto playlist = parsePlaylistResponse(reply.data(), id, lowPriority, ok); + auto playlist = parsePlaylistResponse(reply.data(), id, options, ok); if (ok) { return playlist; - // return QtFuture::makeReadyFuture(std::move(playlist)); } } m_connector.selectBestInstance(); - return getPlaylistInfoAsync(id, lowPriority); + return getPlaylistInfoAsync(id, options); }) .unwrap(); } @@ -146,7 +145,7 @@ auto YouTube::parseStreamResponse(const QByteArray &data, const VideoId &id, boo return video; } -auto YouTube::parsePlaylistResponse(const QByteArray &data, const PlaylistId &id, bool lowPriority, bool &ok) +auto YouTube::parsePlaylistResponse(const QByteArray &data, const PlaylistId &id, Options options, bool &ok) -> QFuture { QJsonParseError parseError; @@ -177,20 +176,20 @@ auto YouTube::parsePlaylistResponse(const QByteArray &data, const PlaylistId &id const auto nextpage = QUrl::toPercentEncoding(doc["nextpage"_L1].toString(), "", ":"); if (!nextpage.isEmpty()) { - return continueLoadingOfPlaylist(std::move(playlist), nextpage, lowPriority); + return continueLoadingOfPlaylist(std::move(playlist), nextpage, options); } return QtFuture::makeReadyFuture(std::move(playlist)); } -auto YouTube::continueLoadingOfPlaylist(YouTubePlaylist &&playlist, const QString &nextpage, bool lowPriority) +auto YouTube::continueLoadingOfPlaylist(YouTubePlaylist &&playlist, const QString &nextpage, Options options) -> QFuture { const auto path = u"/nextpage/playlists/%1?nextpage=%2"_s.arg(playlist.id.toString(), nextpage); const auto request = QNetworkRequest(path); - return m_connector.get(request, false, lowPriority) - .then([this, playlist = std::move(playlist), lowPriority](const RestReply &reply) mutable { + return m_connector.get(request, options) + .then([this, playlist = std::move(playlist), options](const RestReply &reply) mutable { if (reply.hasError()) { qCDebug(gmYouTube()) << "Error while retrieving next page of playlist:" << reply.errorText(); @@ -208,7 +207,7 @@ auto YouTube::continueLoadingOfPlaylist(YouTubePlaylist &&playlist, const QStrin const auto nextpage = QUrl::toPercentEncoding(doc["nextpage"_L1].toString(), "", ":"); if (!nextpage.isEmpty()) { - return continueLoadingOfPlaylist(std::move(playlist), nextpage, lowPriority); + return continueLoadingOfPlaylist(std::move(playlist), nextpage, options); } return QtFuture::makeReadyFuture(std::move(playlist)); diff --git a/src/services/youtube/youtube.h b/src/services/youtube/youtube.h index cd1c6100..ebc4ab0c 100644 --- a/src/services/youtube/youtube.h +++ b/src/services/youtube/youtube.h @@ -44,8 +44,8 @@ class YouTube : public Services::Service void setNetworkManager(QNetworkAccessManager *networkManager); void selectNewPipedInstance(); - [[nodiscard]] auto getStreamInfoAsync(const VideoId &id, bool lowPriority) -> QFuture; - [[nodiscard]] auto getPlaylistInfoAsync(const PlaylistId &id, bool lowPriority) -> QFuture; + [[nodiscard]] auto getStreamInfoAsync(const VideoId &id, Options options) -> QFuture; + [[nodiscard]] auto getPlaylistInfoAsync(const PlaylistId &id, Options options) -> QFuture; public slots: void connectService() override; @@ -55,10 +55,10 @@ public slots: explicit YouTube(QObject *parent = nullptr); [[nodiscard]] static auto parseStreamResponse(const QByteArray &data, const VideoId &id, bool &ok) -> YouTubeVideo; - [[nodiscard]] auto parsePlaylistResponse(const QByteArray &data, const PlaylistId &id, bool lowPriority, bool &ok) + [[nodiscard]] auto parsePlaylistResponse(const QByteArray &data, const PlaylistId &id, Options options, bool &ok) -> QFuture; - [[nodiscard]] auto continueLoadingOfPlaylist(YouTubePlaylist &&playlist, const QString &nextpage, bool lowPriority) + [[nodiscard]] auto continueLoadingOfPlaylist(YouTubePlaylist &&playlist, const QString &nextpage, Options options) -> QFuture; static inline YouTube *m_instance = nullptr; diff --git a/src/tools/audio/audiosaveload.cpp b/src/tools/audio/audiosaveload.cpp index c1d1cf8a..e741cb02 100644 --- a/src/tools/audio/audiosaveload.cpp +++ b/src/tools/audio/audiosaveload.cpp @@ -54,7 +54,7 @@ auto AudioSaveLoad::loadProjects(const QObject *context, FileListResult &&files) } } - auto futureContents = File::getDataAsync(fileNames); + auto futureContents = File::getDataAsync(fileNames, Option::AllowCache); return futureContents.then([context](const std::vector &contents) { qCDebug(gmAudioSaveLoad()) << "Found audio projects."; @@ -79,7 +79,7 @@ auto AudioSaveLoad::findMissingFilesAsync(const QList &audioFiles, const auto filePaths = getFilePathsToCheck(audioFiles, basePath); - auto futureCheckResult = File::checkAsync(filePaths, true); + auto futureCheckResult = File::checkAsync(filePaths, Option::AllowCache); return futureCheckResult .then([audioFiles, basePath](const FileMultiCheckResult &multiResult) { const auto foundPaths = multiResult.existing(); diff --git a/src/tools/audio/players/bufferedaudioplayer.cpp b/src/tools/audio/players/bufferedaudioplayer.cpp index c362cf03..1cd4e082 100644 --- a/src/tools/audio/players/bufferedaudioplayer.cpp +++ b/src/tools/audio/players/bufferedaudioplayer.cpp @@ -282,7 +282,7 @@ void BufferedAudioPlayer::loadLocalFile(const AudioFile &file) const auto path = FileUtils::fileInDir(file.url(), SettingsManager::getPath(m_settingsId)); const auto callback = [this](const Files::FileDataResult &result) { onFileReceived(result); }; - Files::File::getDataAsync(path).then(callback); + Files::File::getDataAsync(path, Files::Option::AllowCache).then(callback); } void BufferedAudioPlayer::loadWebFile(const AudioFile &file) @@ -310,7 +310,7 @@ void BufferedAudioPlayer::loadYouTubeFile(AudioFile &file) } Services::YouTube::instance() - ->getStreamInfoAsync(id, false) + ->getStreamInfoAsync(id, Services::Option::None) .then([this, &file](const Services::YouTubeVideo &video) { if (video.audioStreamUrl.isEmpty()) { diff --git a/src/tools/audio/playlist/resolvingaudioplaylist.cpp b/src/tools/audio/playlist/resolvingaudioplaylist.cpp index 724c12c3..609d74a1 100644 --- a/src/tools/audio/playlist/resolvingaudioplaylist.cpp +++ b/src/tools/audio/playlist/resolvingaudioplaylist.cpp @@ -96,8 +96,8 @@ auto ResolvingAudioPlaylist::unwrapPlaylistFile(qsizetype index, const AudioFile { case AudioFile::Source::File: { const auto path = FileUtils::fileInDir(file.url(), SettingsManager::getPath(m_settingsId)); - return Files::File::getDataAsync(path).then( - [callback](const Files::FileDataResult &result) { callback(result.data()); }); + return Files::File::getDataAsync(path, Files::Option::AllowCache) + .then([callback](const Files::FileDataResult &result) { callback(result.data()); }); break; } case AudioFile::Source::Web: { @@ -150,9 +150,9 @@ auto ResolvingAudioPlaylist::unwrapSpotify(qsizetype index, const AudioFile &fil switch (type) { case SpotifyUtils::SpotifyType::Playlist: - return Spotify::instance()->playlists.getPlaylistTracks(id, false).then(callback); + return Spotify::instance()->playlists.getPlaylistTracks(id, Option::None).then(callback); case SpotifyUtils::SpotifyType::Album: - return Spotify::instance()->albums.getAlbumTracks(id, false).then(callback); + return Spotify::instance()->albums.getAlbumTracks(id, Option::None).then(callback); default: qCCritical(gmAudioPlaylistResolving()) << "loadPlaylistRecursiveSpotify(): not implemented for container type" << (int)type; @@ -165,17 +165,19 @@ auto ResolvingAudioPlaylist::unwrapYouTube(qsizetype index, const AudioFile &fil const PlaylistId id(file.url()); if (!id.isValid()) return QtFuture::makeReadyFuture(); - return YouTube::instance()->getPlaylistInfoAsync(id, false).then([this, index](const YouTubePlaylist &playlist) { - QList files; - files.reserve(playlist.streams.size()); - foreach (const auto &video, playlist.streams) - { - if (!video.id.isValid()) continue; + return YouTube::instance() + ->getPlaylistInfoAsync(id, Option::None) + .then([this, index](const YouTubePlaylist &playlist) { + QList files; + files.reserve(playlist.streams.size()); + foreach (const auto &video, playlist.streams) + { + if (!video.id.isValid()) continue; - files << new AudioFile(video.id.toUrl(), AudioFile::Source::Youtube, video.title, &m_fileParent); - } - replace(index, files); - }); + files << new AudioFile(video.id.toUrl(), AudioFile::Source::Youtube, video.title, &m_fileParent); + } + replace(index, files); + }); } void ResolvingAudioPlaylist::loadTitles() @@ -224,7 +226,7 @@ void ResolvingAudioPlaylist::loadSpotifyTitles(const QList &tracks) } }; - Spotify::instance()->tracks.getTracks(trackIds, true).then(callback); + Spotify::instance()->tracks.getTracks(trackIds, Option::LowPriority).then(callback); } auto ResolvingAudioPlaylist::isPlaylist(const QString &file) -> bool diff --git a/src/tools/audio/thumbnails/loaders/fileimageloader.cpp b/src/tools/audio/thumbnails/loaders/fileimageloader.cpp index 51843bcf..f97e3c92 100755 --- a/src/tools/audio/thumbnails/loaders/fileimageloader.cpp +++ b/src/tools/audio/thumbnails/loaders/fileimageloader.cpp @@ -15,7 +15,7 @@ auto FileImageLoader::loadImageAsync(const QString &path) -> QFuture return QtFuture::makeReadyFuture(pixmap); } - auto future = File::getDataAsync(path); + auto future = File::getDataAsync(path, Option::AllowCache | Option::LowPriority); const auto callback = [path](const FileDataResult &result) { return QtConcurrent::run(loadFromFileResult, path, result); diff --git a/src/tools/audio/thumbnails/loaders/spotifyimageloader.cpp b/src/tools/audio/thumbnails/loaders/spotifyimageloader.cpp index 92a951cd..e0052ba8 100755 --- a/src/tools/audio/thumbnails/loaders/spotifyimageloader.cpp +++ b/src/tools/audio/thumbnails/loaders/spotifyimageloader.cpp @@ -82,7 +82,7 @@ auto SpotifyImageLoader::loadPlaylistImageAsync(const QString &id) -> QFutureget(url, false, true); + auto future = Services::Spotify::instance()->get(url, Option::LowPriority); const auto imageCallback = [id, url](const RestReply &reply) { QPixmap image; @@ -99,7 +99,7 @@ auto SpotifyImageLoader::loadPlaylistImageAsync(const QString &id) -> QFutureplaylists.getPlaylist(id, true); + auto future = Spotify::instance()->playlists.getPlaylist(id, Option::LowPriority); return future.then(playlistCallback).unwrap(); } @@ -114,7 +114,7 @@ void SpotifyImageLoader::startRequest(SpotifyUtils::SpotifyType type) qCDebug(gmAudioSpotifyImageLoader()) << "Sending batch request:" << url; - auto future = Spotify::instance()->get(url, true, true); + auto future = Spotify::instance()->get(url, Option::Authenticated | Option::LowPriority); future.then([type](RestReply &&reply) { receivedRequest(std::move(reply), type); }); // Start timer again if there still are ids in the queue @@ -158,7 +158,7 @@ void SpotifyImageLoader::receivedRequest(RestReply &&reply, SpotifyUtils::Spotif } const auto request = QNetworkRequest(url); - auto future = Spotify::instance()->get(request, false, true); + auto future = Spotify::instance()->get(request, Option::LowPriority); const auto callback = [id, url](const RestReply &imageReply) { QPixmap image; diff --git a/src/tools/audio/thumbnails/loaders/tagimageloader.cpp b/src/tools/audio/thumbnails/loaders/tagimageloader.cpp index ae9a0a67..ce998896 100755 --- a/src/tools/audio/thumbnails/loaders/tagimageloader.cpp +++ b/src/tools/audio/thumbnails/loaders/tagimageloader.cpp @@ -95,7 +95,7 @@ auto TagImageLoader::loadFromData(const QString &path, std::unique_ptr QFuture { - auto future = Files::File::getDataAsync(path); + auto future = Files::File::getDataAsync(path, Files::Option::AllowCache | Files::Option::LowPriority); return future.then(QtFuture::Launch::Async, [path](const Files::FileDataResult &result) { auto fileName = FileUtils::fileName(path); diff --git a/src/tools/audio/thumbnails/loaders/youtubeimageloader.cpp b/src/tools/audio/thumbnails/loaders/youtubeimageloader.cpp index 1402d168..f0f2560c 100644 --- a/src/tools/audio/thumbnails/loaders/youtubeimageloader.cpp +++ b/src/tools/audio/thumbnails/loaders/youtubeimageloader.cpp @@ -22,7 +22,7 @@ auto YouTubeImageLoader::loadImageAsync(const VideoId &id, QNetworkAccessManager // Get video info return YouTube::instance() - ->getStreamInfoAsync(id, true) + ->getStreamInfoAsync(id, Option::LowPriority) .then([networkManager](const YouTubeVideo &video) { return loadImageAsync(video, networkManager); }) .unwrap(); } @@ -61,7 +61,7 @@ auto YouTubeImageLoader::loadImageAsync(const PlaylistId &id, QNetworkAccessMana // Get video info return YouTube::instance() - ->getPlaylistInfoAsync(id, true) + ->getPlaylistInfoAsync(id, Option::LowPriority) .then([networkManager](const YouTubePlaylist &playlist) { return loadImageAsync(playlist, networkManager); }) .unwrap(); diff --git a/src/tools/characters/character.cpp b/src/tools/characters/character.cpp index f22f8c57..8b67a795 100644 --- a/src/tools/characters/character.cpp +++ b/src/tools/characters/character.cpp @@ -67,10 +67,11 @@ void Character::loadFileData(qsizetype index) return; } - Files::File::getDataAsync(m_files.at(index)->path()).then([this, index](const Files::FileDataResult &result) { - if (!result.success()) return; + Files::File::getDataAsync(m_files.at(index)->path(), Files::Option::AllowCache) + .then([this, index](const Files::FileDataResult &result) { + if (!result.success()) return; - m_files.at(index)->data(result.data()); - emit fileDataLoaded(index, result.data()); - }); + m_files.at(index)->data(result.data()); + emit fileDataLoaded(index, result.data()); + }); } diff --git a/src/tools/characters/charactertool.cpp b/src/tools/characters/charactertool.cpp index 8657cad2..a4dccd05 100644 --- a/src/tools/characters/charactertool.cpp +++ b/src/tools/characters/charactertool.cpp @@ -115,8 +115,9 @@ void CharacterTool::loadData() setIsDataLoaded(true); const auto filePath = FileUtils::fileInDir(u"inactive.json"_s, SettingsManager::getPath(u"characters"_s)); - Files::File::getDataAsync(filePath).then( - [this](const Files::FileDataResult &result) { loadInactiveCharacters(result.data()); }); + Files::File::getDataAsync(filePath, Files::Option::AllowCache).then([this](const Files::FileDataResult &result) { + loadInactiveCharacters(result.data()); + }); } void CharacterTool::setCurrentCategory(int index) @@ -157,8 +158,8 @@ void CharacterTool::loadInactiveCharacters(const QByteArray &data) << "Inactive characters file data is empty, maybe old .ini file exists, trying to convert ..."; const auto filePath = FileUtils::fileInDir(u"settings.ini"_s, SettingsManager::getPath(u"characters"_s)); - Files::File::getDataAsync(filePath).then( - [this](const Files::FileDataResult &result) { convertSettingsFile(result.data()); }); + Files::File::getDataAsync(filePath, Files::Option::AllowCache) + .then([this](const Files::FileDataResult &result) { convertSettingsFile(result.data()); }); return; } diff --git a/src/tools/maps/map.cpp b/src/tools/maps/map.cpp index bb26f8e7..6f04fb1f 100644 --- a/src/tools/maps/map.cpp +++ b/src/tools/maps/map.cpp @@ -25,7 +25,7 @@ Map::Map(QObject *parent) : QObject(parent), m_markers(this) Map::Map(const QString &name, const QString &path, QObject *parent) : QObject(parent), a_name(name), a_path(path), m_markers(this) { - Files::File::getDataAsync(path).then([this](const Files::FileDataResult &result) { + Files::File::getDataAsync(path, Files::Option::AllowCache).then([this](const Files::FileDataResult &result) { if (!result.success()) return; QPixmap pixmap; @@ -56,17 +56,18 @@ void Map::loadMarkers() Files::File::checkAsync(filePath).then([this, filePath](const Files::FileCheckResult &checkResult) { if (!checkResult.success() || !checkResult.exists()) return; - Files::File::getDataAsync(filePath).then([this](const Files::FileDataResult &dataResult) { - if (!dataResult.success()) return; + Files::File::getDataAsync(filePath, Files::Option::AllowCache) + .then([this](const Files::FileDataResult &dataResult) { + if (!dataResult.success()) return; - auto markers = QJsonDocument::fromJson(dataResult.data()).object()["markers"_L1].toArray(); + auto markers = QJsonDocument::fromJson(dataResult.data()).object()["markers"_L1].toArray(); - foreach (const auto &marker, markers) - { - addMarker(new MapMarker(marker.toObject(), this)); - } - emit markersChanged(); - }); + foreach (const auto &marker, markers) + { + addMarker(new MapMarker(marker.toObject(), this)); + } + emit markersChanged(); + }); }); } diff --git a/src/tools/notes/notessaveload.cpp b/src/tools/notes/notessaveload.cpp index fe87ba39..284cd086 100644 --- a/src/tools/notes/notessaveload.cpp +++ b/src/tools/notes/notessaveload.cpp @@ -76,7 +76,7 @@ void NotesSaveLoad::loadPageContent() const qCDebug(gmNotesSaveLoad()) << "Loading page content of" << fileName; - Files::File::getDataAsync(fileName).then([page](const Files::FileDataResult &result) { + Files::File::getDataAsync(fileName, Files::Option::AllowCache).then([page](const Files::FileDataResult &result) { if (!page) return; page->onContentLoaded(result.data()); }); diff --git a/src/tools/shop/baseshoptool.cpp b/src/tools/shop/baseshoptool.cpp index 914a5a63..4faf8663 100644 --- a/src/tools/shop/baseshoptool.cpp +++ b/src/tools/shop/baseshoptool.cpp @@ -35,8 +35,8 @@ void BaseShopTool::onShopFilesFound(Files::FileListResult &&result) return; } - Files::File::getDataAsync(files).then( - [this](const std::vector &results) { onShopFileDataReceived(results); }); + Files::File::getDataAsync(files, Files::Option::AllowCache) + .then([this](const std::vector &results) { onShopFileDataReceived(results); }); } void BaseShopTool::onShopFileDataReceived(const std::vector &results) diff --git a/src/tools/shop/itemeditor.cpp b/src/tools/shop/itemeditor.cpp index 21200fd8..5bd4e76f 100644 --- a/src/tools/shop/itemeditor.cpp +++ b/src/tools/shop/itemeditor.cpp @@ -158,7 +158,7 @@ void ItemEditor::onFileCheckReceived(const Files::FileCheckResult &checkResult) return; } - Files::File::getDataAsync(path) + Files::File::getDataAsync(path, Files::Option::AllowCache) .then([this](const Files::FileDataResult &dataResult) { onDataReceived(dataResult); }) .onCanceled([this]() { isLoading(false); }); } diff --git a/src/tools/shop/shopeditor.cpp b/src/tools/shop/shopeditor.cpp index 635aba8b..5005d391 100644 --- a/src/tools/shop/shopeditor.cpp +++ b/src/tools/shop/shopeditor.cpp @@ -72,20 +72,21 @@ void ShopEditor::onItemFilesFound(Files::FileListResult &&result) return; } - Files::File::getDataAsync(files).then([this](const std::vector &results) { - QList groups = {}; - groups.reserve(results.size()); + Files::File::getDataAsync(files, Files::Option::AllowCache) + .then([this](const std::vector &results) { + QList groups = {}; + groups.reserve(results.size()); - foreach (const auto &result, results) - { - if (!result.success()) continue; + foreach (const auto &result, results) + { + if (!result.success()) continue; - groups.append(new ItemGroup(tr("Custom"), QJsonDocument::fromJson(result.data()).object(), this)); - } + groups.append(new ItemGroup(tr("Custom"), QJsonDocument::fromJson(result.data()).object(), this)); + } - itemGroups(groups); - currentItemGroup(a_itemGroups.isEmpty() ? nullptr : a_itemGroups.constFirst()); - }); + itemGroups(groups); + currentItemGroup(a_itemGroups.isEmpty() ? nullptr : a_itemGroups.constFirst()); + }); } /// Create a project, category or shop diff --git a/tests/filesystem/abstractaccesstest.cpp b/tests/filesystem/abstractaccesstest.cpp index a0e47320..f0219dfc 100644 --- a/tests/filesystem/abstractaccesstest.cpp +++ b/tests/filesystem/abstractaccesstest.cpp @@ -61,20 +61,20 @@ void AbstractAccessTest::runAllTests() void AbstractAccessTest::getDataAsync() { // File that exists - verifyFileContent(getFilePath(u"test1"_s), QByteArray("This is test 1."), false); + verifyFileContent(getFilePath(u"test1"_s), QByteArray("This is test 1."), Option::None); // Test file one again, this time it should be in cache - verifyFileContent(getFilePath(u"test1"_s), QByteArray("This is test 1."), true); + verifyFileContent(getFilePath(u"test1"_s), QByteArray("This is test 1."), Option::AllowCache); #ifndef Q_OS_WIN // File with special characters in name and content verifyFileContent(getFilePath(u"file&with\"special characters"_s), QByteArray("file&with\"special characters"), - false); + Option::None); #endif // File that does not exist expectWarning(); - const auto future2 = File::getDataAsync(getFilePath(u"missing-file"_s), false, fileAccess); + const auto future2 = File::getDataAsync(getFilePath(u"missing-file"_s), Option::None, fileAccess); testFuture(future2, "File::getDataAsync", [future2]() { EXPECT_FALSE(future2.isCanceled()) << "QFuture is canceled!"; @@ -84,7 +84,7 @@ void AbstractAccessTest::getDataAsync() // Multiple files const auto future3 = File::getDataAsync({getFilePath(u"test1"_s), getFilePath(u"test2"_s), getFilePath(u"test3"_s)}, - false, fileAccess); + Option::None, fileAccess); const QList expectedData = {QByteArray("This is test 1."), QByteArray("This is test 2."), QByteArray("This is test 3.")}; @@ -348,7 +348,7 @@ void AbstractAccessTest::checkAsync() verifyThatFileExists(getFilePath(u"test1"_s)); // Check single file that does not exist - auto future2 = File::checkAsync(getFilePath(u"test1-missing"_s), false, fileAccess); + auto future2 = File::checkAsync(getFilePath(u"test1-missing"_s), Option::None, fileAccess); testFuture(future2, "File::checkAsync", [future2]() { EXPECT_FALSE(future2.isCanceled()) << "QFuture is canceled!"; EXPECT_FALSE(future2.result().exists()) << "Apparently the file exists when it should not."; @@ -358,7 +358,7 @@ void AbstractAccessTest::checkAsync() auto files = QStringList{getFilePath(u"test1"_s), getFilePath(u"test2"_s), getFilePath(u"test3"_s), getFilePath(u"missing-file"_s)}; - auto future3 = File::checkAsync(files, false, fileAccess); + auto future3 = File::checkAsync(files, Option::None, fileAccess); testFuture(future3, "File::checkAsync", [future3]() { EXPECT_FALSE(future3.isCanceled()) << "QFuture is canceled!"; EXPECT_EQ(future3.result().existing().length(), 3) << "Incorrect number of files found."; diff --git a/tests/filesystem/testnextcloudaccess.cpp b/tests/filesystem/testnextcloudaccess.cpp index fb29e400..0971d2ce 100644 --- a/tests/filesystem/testnextcloudaccess.cpp +++ b/tests/filesystem/testnextcloudaccess.cpp @@ -26,7 +26,7 @@ class NextcloudAccessTest : public AbstractAccessTest QDesktopServices::setUrlHandler(u"https"_s, networkManager.get(), "simulateBrowser"); mock = networkManager.get(); - nc = new NextCloud(MOCK_SERVICE, *mock, nullptr); + nc = new NextCloud(MOCK_SERVICE, mock, nullptr); nc->disconnectService(); EXPECT_FALSE(nc->connected()); diff --git a/tests/services/rest/testrestrequest.cpp b/tests/services/rest/testrestrequest.cpp index c9ca6f61..e77c6923 100644 --- a/tests/services/rest/testrestrequest.cpp +++ b/tests/services/rest/testrestrequest.cpp @@ -7,54 +7,56 @@ using namespace Services; TEST(RestRequestTest, CanConstructGetRequest) { const QNetworkRequest networkRequest(QUrl(u"https://localhost"_s)); - const RestRequest restRequest(networkRequest, RestRequest::Type::GET); + const RestRequest restRequest(networkRequest, RestRequest::Type::GET, Option::Authenticated); EXPECT_EQ(restRequest.id(), -1); EXPECT_EQ(restRequest.type(), RestRequest::Type::GET); EXPECT_EQ(restRequest.request(), networkRequest); EXPECT_TRUE(restRequest.data().isEmpty()); EXPECT_EQ(restRequest.verb(), "GET"); + EXPECT_EQ(restRequest.options(), Option::Authenticated); } TEST(RestRequestTest, CanConstructCustomRequest) { const QNetworkRequest networkRequest(QUrl(u"https://localhost"_s)); - const RestRequest restRequest(5, networkRequest, RestRequest::Type::CUSTOM, "data", "TEST"); + const RestRequest restRequest(5, networkRequest, RestRequest::Type::CUSTOM, Option::Authenticated, "data", "TEST"); EXPECT_EQ(restRequest.id(), 5); EXPECT_EQ(restRequest.type(), RestRequest::Type::CUSTOM); EXPECT_EQ(restRequest.request(), networkRequest); EXPECT_EQ(restRequest.data(), "data"); EXPECT_EQ(restRequest.verb(), "TEST"); + EXPECT_EQ(restRequest.options(), Option::Authenticated); } TEST(RestRequestTest, CanSetId) { const QNetworkRequest networkRequest(QUrl(u"https://localhost"_s)); - RestRequest restRequest(networkRequest, RestRequest::Type::GET); + RestRequest restRequest(networkRequest, RestRequest::Type::GET, Option::None); restRequest.id(3); EXPECT_EQ(restRequest.id(), 3); } -TEST(RestRequestTest, CanSetAuthRequirements) +TEST(RestRequestTest, CanSetOptions) { const QNetworkRequest networkRequest(QUrl(u"https://localhost"_s)); - RestRequest restRequest(networkRequest, RestRequest::Type::GET); + RestRequest restRequest(networkRequest, RestRequest::Type::GET, Option::None); - restRequest.isAuthRequired(true); - EXPECT_TRUE(restRequest.isAuthRequired()); + restRequest.options(Option::Authenticated); + EXPECT_TRUE(restRequest.options().testFlag(Option::Authenticated)); - restRequest.isAuthRequired(false); - EXPECT_FALSE(restRequest.isAuthRequired()); + restRequest.options(Option::None); + EXPECT_FALSE(restRequest.options().testFlag(Option::Authenticated)); } TEST(RestRequestTest, CanCorrectlySetVerb) { const QNetworkRequest networkRequest(QUrl(u"https://localhost"_s)); - EXPECT_EQ(RestRequest(networkRequest, RestRequest::Type::GET).verb(), "GET"); - EXPECT_EQ(RestRequest(networkRequest, RestRequest::Type::PUT).verb(), "PUT"); - EXPECT_EQ(RestRequest(networkRequest, RestRequest::Type::POST).verb(), "POST"); - EXPECT_EQ(RestRequest(networkRequest, RestRequest::Type::CUSTOM).verb(), ""); + EXPECT_EQ(RestRequest(networkRequest, RestRequest::Type::GET, Option::None).verb(), "GET"); + EXPECT_EQ(RestRequest(networkRequest, RestRequest::Type::PUT, Option::None).verb(), "PUT"); + EXPECT_EQ(RestRequest(networkRequest, RestRequest::Type::POST, Option::None).verb(), "POST"); + EXPECT_EQ(RestRequest(networkRequest, RestRequest::Type::CUSTOM, Option::None).verb(), ""); } diff --git a/tests/services/youtube/testpipedconnector.cpp b/tests/services/youtube/testpipedconnector.cpp index 891bf378..4a3e6813 100644 --- a/tests/services/youtube/testpipedconnector.cpp +++ b/tests/services/youtube/testpipedconnector.cpp @@ -254,7 +254,7 @@ void testGetRequest(PipedConnector &connector, int currentFailCount) testGetRequest(connector, currentFailCount + 1); }; - auto future = connector.get(QNetworkRequest(QUrl(u"/streams/9bZkp7q19f0"_s)), false, false); + auto future = connector.get(QNetworkRequest(QUrl(u"/streams/9bZkp7q19f0"_s)), Option::None); StaticAbstractTest::testFutureNoAuth(future, "get stream", [tryAgain, future, currentFailCount]() { auto reply = future.result(); @@ -295,9 +295,9 @@ TEST(PipedConnectorTest, ThrowOnNonGetRequests) PipedConnector connector; connector.setNetworkManager(makeNetworkManager()); - EXPECT_THROW(connector.put(QNetworkRequest(), {}, false), NotImplementedException); - EXPECT_THROW(connector.post(QNetworkRequest(), {}, false), NotImplementedException); - EXPECT_THROW(connector.customRequest(QNetworkRequest(), "", {}, false, false), NotImplementedException); + EXPECT_THROW(connector.put(QNetworkRequest(), {}, Option::None), NotImplementedException); + EXPECT_THROW(connector.post(QNetworkRequest(), {}, Option::None), NotImplementedException); + EXPECT_THROW(connector.customRequest(QNetworkRequest(), {}, {}, Option::None), NotImplementedException); } #include "testpipedconnector.moc" diff --git a/tests/services/youtube/testyoutube.cpp b/tests/services/youtube/testyoutube.cpp index 753d9b93..fe98e5dd 100644 --- a/tests/services/youtube/testyoutube.cpp +++ b/tests/services/youtube/testyoutube.cpp @@ -18,8 +18,8 @@ class YouTubeTest : public ::testing::Test TEST_F(YouTubeTest, CanGetStreamInfos) { - auto future = - YouTube::instance()->getStreamInfoAsync(VideoId(u"https://www.youtube.com/watch?v=dQw4w9WgXcQ"_s), false); + auto future = YouTube::instance()->getStreamInfoAsync(VideoId(u"https://www.youtube.com/watch?v=dQw4w9WgXcQ"_s), + Option::None); StaticAbstractTest::testFutureNoAuth(future, "getAudioStreamAsync", [future]() { const auto res = future.result(); EXPECT_EQ(res.id.toString().toStdString(), "dQw4w9WgXcQ"); @@ -33,7 +33,7 @@ TEST_F(YouTubeTest, CanGetStreamInfos) TEST_F(YouTubeTest, CanGetPlaylistInfos) { auto future = YouTube::instance()->getPlaylistInfoAsync( - PlaylistId(u"https://www.youtube.com/playlist?list=PL53mjgVKFq7yu0LdAvpp42ZGLzRCkFKuz"_s), false); + PlaylistId(u"https://www.youtube.com/playlist?list=PL53mjgVKFq7yu0LdAvpp42ZGLzRCkFKuz"_s), Option::None); StaticAbstractTest::testFutureNoAuth(future, "getPlaylistInfoAsync", [future]() { const auto res = future.result(); @@ -48,7 +48,7 @@ TEST_F(YouTubeTest, CanGetPlaylistInfos) TEST_F(YouTubeTest, CanGetLongPlaylistInfos) { auto future = YouTube::instance()->getPlaylistInfoAsync( - PlaylistId(u"https://www.youtube.com/playlist?list=PLUckL1YFRK3tiL676pRCURVuZ8RHgu974"_s), false); + PlaylistId(u"https://www.youtube.com/playlist?list=PLUckL1YFRK3tiL676pRCURVuZ8RHgu974"_s), Option::None); StaticAbstractTest::testFutureNoAuth(future, "getPlaylistInfoAsync", [future]() { const auto res = future.result(); diff --git a/tests/testhelper/abstracttest.cpp b/tests/testhelper/abstracttest.cpp index 02359202..59b11d54 100644 --- a/tests/testhelper/abstracttest.cpp +++ b/tests/testhelper/abstracttest.cpp @@ -22,13 +22,13 @@ AbstractTest::~AbstractTest() SettingsManager::instance()->set(u"cloudMode"_s, cloudMode); } -void AbstractTest::verifyFileContent(const QString &path, const QByteArray &content, bool cached) +void AbstractTest::verifyFileContent(const QString &path, const QByteArray &content, Options options) { checkOrCreateFileAccess(); - qDebug() << "Verifying content of file" << path << "(cached:" << cached << ")"; + qDebug() << "Verifying content of file" << path << "(cached:" << options << ")"; - const auto future = File::getDataAsync(path, cached, fileAccess); + const auto future = File::getDataAsync(path, options, fileAccess); testFuture( future, "File::getDataAsync", [future, content]() { @@ -39,7 +39,7 @@ void AbstractTest::verifyFileContent(const QString &path, const QByteArray &cont << "File::getDataAsync did not return a valid result: " << result.errorMessage().toStdString(); EXPECT_EQ(result.data(), content); }, - cached); + options); } void AbstractTest::verifyThatFileExists(const QString &path, bool shouldExist) @@ -48,7 +48,7 @@ void AbstractTest::verifyThatFileExists(const QString &path, bool shouldExist) qDebug() << "Verifying that file" << path << (shouldExist ? "exists" : "does not exist") << "..."; - auto future = File::checkAsync(path, false, fileAccess); + auto future = File::checkAsync(path, Option::None, fileAccess); testFuture(future, "File::checkAsync", [future, path, shouldExist]() { EXPECT_FALSE(future.isCanceled()) << "QFuture is canceled!"; EXPECT_EQ(shouldExist, future.result().exists()) diff --git a/tests/testhelper/abstracttest.h b/tests/testhelper/abstracttest.h index 2a16ba47..a8f935ba 100644 --- a/tests/testhelper/abstracttest.h +++ b/tests/testhelper/abstracttest.h @@ -54,7 +54,8 @@ class AbstractTest : public StaticAbstractTest testFunc(); } - void verifyFileContent(const QString &path, const QByteArray &content, bool cached = false); + void verifyFileContent(const QString &path, const QByteArray &content, + Files::Options options = Files::Option::None); void verifyThatFileExists(const QString &path, bool shouldExist = true); [[nodiscard]] virtual auto getFilePath(const QString &filename = QLatin1String()) const -> QString;