diff --git a/src/webui/CMakeLists.txt b/src/webui/CMakeLists.txt index c82340239..377a8356b 100644 --- a/src/webui/CMakeLists.txt +++ b/src/webui/CMakeLists.txt @@ -2,6 +2,7 @@ add_library(qbt_webui STATIC # headers api/apicontroller.h api/apierror.h + api/apistatus.h api/appcontroller.h api/authcontroller.h api/isessionmanager.h diff --git a/src/webui/api/apicontroller.cpp b/src/webui/api/apicontroller.cpp index ca2c318f4..ce279b45f 100644 --- a/src/webui/api/apicontroller.cpp +++ b/src/webui/api/apicontroller.cpp @@ -42,6 +42,7 @@ void APIResult::clear() data.clear(); mimeType.clear(); filename.clear(); + status = APIStatus::Ok; } APIController::APIController(IApplication *app, QObject *parent) @@ -105,3 +106,8 @@ void APIController::setResult(const QByteArray &result, const QString &mimeType, m_result.mimeType = mimeType; m_result.filename = filename; } + +void APIController::setStatus(const APIStatus status) +{ + m_result.status = status; +} diff --git a/src/webui/api/apicontroller.h b/src/webui/api/apicontroller.h index edcfc16fc..898ad3fdc 100644 --- a/src/webui/api/apicontroller.h +++ b/src/webui/api/apicontroller.h @@ -34,6 +34,7 @@ #include #include "base/applicationcomponent.h" +#include "apistatus.h" using DataMap = QHash; using StringMap = QHash; @@ -43,6 +44,7 @@ struct APIResult QVariant data; QString mimeType; QString filename; + APIStatus status = APIStatus::Ok; void clear(); }; @@ -67,6 +69,8 @@ protected: void setResult(const QJsonObject &result); void setResult(const QByteArray &result, const QString &mimeType = {}, const QString &filename = {}); + void setStatus(APIStatus status); + private: StringMap m_params; DataMap m_data; diff --git a/src/webui/api/apistatus.h b/src/webui/api/apistatus.h new file mode 100644 index 000000000..b88a5c724 --- /dev/null +++ b/src/webui/api/apistatus.h @@ -0,0 +1,35 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2024 Thomas Piccirello + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#pragma once + +enum class APIStatus +{ + Ok, + Async +}; diff --git a/src/webui/api/appcontroller.cpp b/src/webui/api/appcontroller.cpp index 701160542..398f08086 100644 --- a/src/webui/api/appcontroller.cpp +++ b/src/webui/api/appcontroller.cpp @@ -123,6 +123,7 @@ void AppController::shutdownAction() { QCoreApplication::exit(); }); + setResult(QString()); } void AppController::preferencesAction() @@ -1167,6 +1168,8 @@ void AppController::setPreferencesAction() // Save preferences pref->apply(); + + setResult(QString()); } void AppController::defaultSavePathAction() @@ -1177,9 +1180,9 @@ void AppController::defaultSavePathAction() void AppController::sendTestEmailAction() { app()->sendTestEmail(); + setResult(QString()); } - void AppController::getDirectoryContentAction() { requireParams({u"dirPath"_s}); @@ -1269,6 +1272,8 @@ void AppController::setCookiesAction() } Net::DownloadManager::instance()->setAllCookies(cookies); + + setResult(QString()); } void AppController::networkInterfaceListAction() diff --git a/src/webui/api/authcontroller.cpp b/src/webui/api/authcontroller.cpp index 5c308a62e..ee52c1df6 100644 --- a/src/webui/api/authcontroller.cpp +++ b/src/webui/api/authcontroller.cpp @@ -46,11 +46,13 @@ AuthController::AuthController(ISessionManager *sessionManager, IApplication *ap void AuthController::setUsername(const QString &username) { m_username = username; + setResult(QString()); } void AuthController::setPasswordHash(const QByteArray &passwordHash) { m_passwordHash = passwordHash; + setResult(QString()); } void AuthController::loginAction() @@ -96,9 +98,10 @@ void AuthController::loginAction() } } -void AuthController::logoutAction() const +void AuthController::logoutAction() { m_sessionManager->sessionEnd(); + setResult(QString()); } bool AuthController::isBanned() const diff --git a/src/webui/api/authcontroller.h b/src/webui/api/authcontroller.h index 0a47c2338..cdd7c4cfb 100644 --- a/src/webui/api/authcontroller.h +++ b/src/webui/api/authcontroller.h @@ -52,7 +52,7 @@ public: private slots: void loginAction(); - void logoutAction() const; + void logoutAction(); private: bool isBanned() const; diff --git a/src/webui/api/rsscontroller.cpp b/src/webui/api/rsscontroller.cpp index d4308db46..8cdd1d532 100644 --- a/src/webui/api/rsscontroller.cpp +++ b/src/webui/api/rsscontroller.cpp @@ -53,6 +53,8 @@ void RSSController::addFolderAction() const nonstd::expected result = RSS::Session::instance()->addFolder(path); if (!result) throw APIError(APIErrorType::Conflict, result.error()); + + setResult(QString()); } void RSSController::addFeedAction() @@ -65,6 +67,8 @@ void RSSController::addFeedAction() const nonstd::expected result = RSS::Session::instance()->addFeed(url, (path.isEmpty() ? url : path), std::chrono::seconds(refreshInterval)); if (!result) throw APIError(APIErrorType::Conflict, result.error()); + + setResult(QString()); } void RSSController::setFeedURLAction() @@ -76,6 +80,8 @@ void RSSController::setFeedURLAction() const nonstd::expected result = RSS::Session::instance()->setFeedURL(path, url); if (!result) throw APIError(APIErrorType::Conflict, result.error()); + + setResult(QString()); } void RSSController::setFeedRefreshIntervalAction() @@ -103,6 +109,8 @@ void RSSController::removeItemAction() const nonstd::expected result = RSS::Session::instance()->removeItem(path); if (!result) throw APIError(APIErrorType::Conflict, result.error()); + + setResult(QString()); } void RSSController::moveItemAction() @@ -114,6 +122,8 @@ void RSSController::moveItemAction() const nonstd::expected result = RSS::Session::instance()->moveItem(itemPath, destPath); if (!result) throw APIError(APIErrorType::Conflict, result.error()); + + setResult(QString()); } void RSSController::itemsAction() @@ -148,6 +158,8 @@ void RSSController::markAsReadAction() { item->markAsRead(); } + + setResult(QString()); } void RSSController::refreshItemAction() @@ -158,6 +170,8 @@ void RSSController::refreshItemAction() RSS::Item *item = RSS::Session::instance()->itemByPath(itemPath); if (item) item->refresh(); + + setResult(QString()); } void RSSController::setRuleAction() @@ -169,6 +183,8 @@ void RSSController::setRuleAction() const auto jsonObj = QJsonDocument::fromJson(ruleDef).object(); RSS::AutoDownloader::instance()->setRule(RSS::AutoDownloadRule::fromJsonObject(jsonObj, ruleName)); + + setResult(QString()); } void RSSController::renameRuleAction() @@ -179,6 +195,8 @@ void RSSController::renameRuleAction() const QString newRuleName {params()[u"newRuleName"_s]}; RSS::AutoDownloader::instance()->renameRule(ruleName, newRuleName); + + setResult(QString()); } void RSSController::removeRuleAction() @@ -187,6 +205,8 @@ void RSSController::removeRuleAction() const QString ruleName {params()[u"ruleName"_s]}; RSS::AutoDownloader::instance()->removeRule(ruleName); + + setResult(QString()); } void RSSController::rulesAction() diff --git a/src/webui/api/searchcontroller.cpp b/src/webui/api/searchcontroller.cpp index d7b0c9786..2af76a0f4 100644 --- a/src/webui/api/searchcontroller.cpp +++ b/src/webui/api/searchcontroller.cpp @@ -142,6 +142,8 @@ void SearchController::stopAction() searchHandler->cancelSearch(); m_activeSearches.remove(id); } + + setResult(QString()); } void SearchController::statusAction() @@ -215,6 +217,8 @@ void SearchController::deleteAction() searchHandler->cancelSearch(); m_activeSearches.remove(id); m_searchHandlers.erase(iter); + + setResult(QString()); } void SearchController::downloadTorrentAction() @@ -238,6 +242,8 @@ void SearchController::downloadTorrentAction() downloadHandler->deleteLater(); }); } + + setResult(QString()); } void SearchController::pluginsAction() @@ -253,6 +259,8 @@ void SearchController::installPluginAction() const QStringList sources = params()[u"sources"_s].split(u'|'); for (const QString &source : sources) SearchPluginManager::instance()->installPlugin(source); + + setResult(QString()); } void SearchController::uninstallPluginAction() @@ -262,6 +270,8 @@ void SearchController::uninstallPluginAction() const QStringList names = params()[u"names"_s].split(u'|'); for (const QString &name : names) SearchPluginManager::instance()->uninstallPlugin(name.trimmed()); + + setResult(QString()); } void SearchController::enablePluginAction() @@ -273,6 +283,8 @@ void SearchController::enablePluginAction() for (const QString &name : names) SearchPluginManager::instance()->enablePlugin(name.trimmed(), enable); + + setResult(QString()); } void SearchController::updatePluginsAction() @@ -282,6 +294,8 @@ void SearchController::updatePluginsAction() connect(pluginManager, &SearchPluginManager::checkForUpdatesFinished, this, &SearchController::checkForUpdatesFinished); connect(pluginManager, &SearchPluginManager::checkForUpdatesFailed, this, &SearchController::checkForUpdatesFailed); pluginManager->checkForUpdates(); + + setResult(QString()); } void SearchController::checkForUpdatesFinished(const QHash &updateInfo) @@ -300,6 +314,8 @@ void SearchController::checkForUpdatesFinished(const QHashupdatePlugin(pluginName); } + + setResult(QString()); } void SearchController::checkForUpdatesFailed(const QString &reason) diff --git a/src/webui/api/torrentcreatorcontroller.cpp b/src/webui/api/torrentcreatorcontroller.cpp index e3f3769e7..35e2ce849 100644 --- a/src/webui/api/torrentcreatorcontroller.cpp +++ b/src/webui/api/torrentcreatorcontroller.cpp @@ -256,4 +256,6 @@ void TorrentCreatorController::deleteTaskAction() if (!m_torrentCreationManager->deleteTask(id)) throw APIError(APIErrorType::NotFound); + + setResult(QString()); } diff --git a/src/webui/api/torrentscontroller.cpp b/src/webui/api/torrentscontroller.cpp index cc3b2e6df..9fe658f95 100644 --- a/src/webui/api/torrentscontroller.cpp +++ b/src/webui/api/torrentscontroller.cpp @@ -670,6 +670,8 @@ void TorrentsController::addWebSeedsAction() } torrent->addUrlSeeds(urls); + + setResult(QString()); } void TorrentsController::editWebSeedAction() @@ -702,6 +704,8 @@ void TorrentsController::editWebSeedAction() torrent->removeUrlSeeds({origUrl}); torrent->addUrlSeeds({newUrl}); } + + setResult(QString()); } void TorrentsController::removeWebSeedsAction() @@ -725,6 +729,8 @@ void TorrentsController::removeWebSeedsAction() } torrent->removeUrlSeeds(urls); + + setResult(QString()); } // Returns the files in a torrent in JSON format. @@ -939,6 +945,8 @@ void TorrentsController::addTrackersAction() const QList entries = BitTorrent::parseTrackerEntries(params()[u"urls"_s]); torrent->addTrackers(entries); + + setResult(QString()); } void TorrentsController::editTrackerAction() @@ -992,6 +1000,8 @@ void TorrentsController::editTrackerAction() if (!torrent->isStopped()) torrent->forceReannounce(); + + setResult(QString()); } void TorrentsController::removeTrackersAction() @@ -1026,6 +1036,8 @@ void TorrentsController::removeTrackersAction() for (BitTorrent::Torrent *const torrent : asConst(torrents)) torrent->removeTrackers(urls); + + setResult(QString()); } void TorrentsController::addPeersAction() @@ -1072,6 +1084,8 @@ void TorrentsController::stopAction() const QStringList hashes = params()[u"hashes"_s].split(u'|'); applyToTorrents(hashes, [](BitTorrent::Torrent *const torrent) { torrent->stop(); }); + + setResult(QString()); } void TorrentsController::startAction() @@ -1080,6 +1094,8 @@ void TorrentsController::startAction() const QStringList idStrings = params()[u"hashes"_s].split(u'|'); applyToTorrents(idStrings, [](BitTorrent::Torrent *const torrent) { torrent->start(); }); + + setResult(QString()); } void TorrentsController::filePrioAction() @@ -1121,6 +1137,8 @@ void TorrentsController::filePrioAction() if (priorityChanged) torrent->prioritizeFiles(priorities); + + setResult(QString()); } void TorrentsController::uploadLimitAction() @@ -1169,6 +1187,8 @@ void TorrentsController::setUploadLimitAction() const QStringList hashes {params()[u"hashes"_s].split(u'|')}; applyToTorrents(hashes, [limit](BitTorrent::Torrent *const torrent) { torrent->setUploadLimit(limit); }); + + setResult(QString()); } void TorrentsController::setDownloadLimitAction() @@ -1181,6 +1201,8 @@ void TorrentsController::setDownloadLimitAction() const QStringList hashes {params()[u"hashes"_s].split(u'|')}; applyToTorrents(hashes, [limit](BitTorrent::Torrent *const torrent) { torrent->setDownloadLimit(limit); }); + + setResult(QString()); } void TorrentsController::setShareLimitsAction() @@ -1198,6 +1220,8 @@ void TorrentsController::setShareLimitsAction() torrent->setSeedingTimeLimit(seedingTimeLimit); torrent->setInactiveSeedingTimeLimit(inactiveSeedingTimeLimit); }); + + setResult(QString()); } void TorrentsController::toggleSequentialDownloadAction() @@ -1206,6 +1230,8 @@ void TorrentsController::toggleSequentialDownloadAction() const QStringList hashes {params()[u"hashes"_s].split(u'|')}; applyToTorrents(hashes, [](BitTorrent::Torrent *const torrent) { torrent->toggleSequentialDownload(); }); + + setResult(QString()); } void TorrentsController::toggleFirstLastPiecePrioAction() @@ -1214,6 +1240,8 @@ void TorrentsController::toggleFirstLastPiecePrioAction() const QStringList hashes {params()[u"hashes"_s].split(u'|')}; applyToTorrents(hashes, [](BitTorrent::Torrent *const torrent) { torrent->toggleFirstLastPiecePriority(); }); + + setResult(QString()); } void TorrentsController::setSuperSeedingAction() @@ -1223,6 +1251,8 @@ void TorrentsController::setSuperSeedingAction() const bool value {parseBool(params()[u"value"_s]).value_or(false)}; const QStringList hashes {params()[u"hashes"_s].split(u'|')}; applyToTorrents(hashes, [value](BitTorrent::Torrent *const torrent) { torrent->setSuperSeeding(value); }); + + setResult(QString()); } void TorrentsController::setForceStartAction() @@ -1235,6 +1265,8 @@ void TorrentsController::setForceStartAction() { torrent->start(value ? BitTorrent::TorrentOperatingMode::Forced : BitTorrent::TorrentOperatingMode::AutoManaged); }); + + setResult(QString()); } void TorrentsController::deleteAction() @@ -1248,6 +1280,8 @@ void TorrentsController::deleteAction() { BitTorrent::Session::instance()->removeTorrent(torrent->id(), deleteOption); }); + + setResult(QString()); } void TorrentsController::increasePrioAction() @@ -1259,6 +1293,8 @@ void TorrentsController::increasePrioAction() const QStringList hashes {params()[u"hashes"_s].split(u'|')}; BitTorrent::Session::instance()->increaseTorrentsQueuePos(toTorrentIDs(hashes)); + + setResult(QString()); } void TorrentsController::decreasePrioAction() @@ -1270,6 +1306,8 @@ void TorrentsController::decreasePrioAction() const QStringList hashes {params()[u"hashes"_s].split(u'|')}; BitTorrent::Session::instance()->decreaseTorrentsQueuePos(toTorrentIDs(hashes)); + + setResult(QString()); } void TorrentsController::topPrioAction() @@ -1281,6 +1319,8 @@ void TorrentsController::topPrioAction() const QStringList hashes {params()[u"hashes"_s].split(u'|')}; BitTorrent::Session::instance()->topTorrentsQueuePos(toTorrentIDs(hashes)); + + setResult(QString()); } void TorrentsController::bottomPrioAction() @@ -1292,6 +1332,8 @@ void TorrentsController::bottomPrioAction() const QStringList hashes {params()[u"hashes"_s].split(u'|')}; BitTorrent::Session::instance()->bottomTorrentsQueuePos(toTorrentIDs(hashes)); + + setResult(QString()); } void TorrentsController::setLocationAction() @@ -1315,6 +1357,8 @@ void TorrentsController::setLocationAction() torrent->setAutoTMMEnabled(false); torrent->setSavePath(newLocation); }); + + setResult(QString()); } void TorrentsController::setSavePathAction() @@ -1340,6 +1384,8 @@ void TorrentsController::setSavePathAction() if (!torrent->isAutoTMMEnabled()) torrent->setSavePath(newPath); }); + + setResult(QString()); } void TorrentsController::setDownloadPathAction() @@ -1365,6 +1411,8 @@ void TorrentsController::setDownloadPathAction() if (!torrent->isAutoTMMEnabled()) torrent->setDownloadPath(newPath); }); + + setResult(QString()); } void TorrentsController::renameAction() @@ -1383,6 +1431,8 @@ void TorrentsController::renameAction() name.replace(QRegularExpression(u"\r?\n|\r"_s), u" "_s); torrent->setName(name); + + setResult(QString()); } void TorrentsController::setAutoManagementAction() @@ -1396,6 +1446,8 @@ void TorrentsController::setAutoManagementAction() { torrent->setAutoTMMEnabled(isEnabled); }); + + setResult(QString()); } void TorrentsController::recheckAction() @@ -1404,6 +1456,8 @@ void TorrentsController::recheckAction() const QStringList hashes {params()[u"hashes"_s].split(u'|')}; applyToTorrents(hashes, [](BitTorrent::Torrent *const torrent) { torrent->forceRecheck(); }); + + setResult(QString()); } void TorrentsController::reannounceAction() @@ -1412,6 +1466,8 @@ void TorrentsController::reannounceAction() const QStringList hashes {params()[u"hashes"_s].split(u'|')}; applyToTorrents(hashes, [](BitTorrent::Torrent *const torrent) { torrent->forceReannounce(); }); + + setResult(QString()); } void TorrentsController::setCategoryAction() @@ -1426,6 +1482,8 @@ void TorrentsController::setCategoryAction() if (!torrent->setCategory(category)) throw APIError(APIErrorType::Conflict, tr("Incorrect category name")); }); + + setResult(QString()); } void TorrentsController::createCategoryAction() @@ -1451,6 +1509,8 @@ void TorrentsController::createCategoryAction() if (!BitTorrent::Session::instance()->addCategory(category, categoryOptions)) throw APIError(APIErrorType::Conflict, tr("Unable to create category")); + + setResult(QString()); } void TorrentsController::editCategoryAction() @@ -1473,6 +1533,8 @@ void TorrentsController::editCategoryAction() if (!BitTorrent::Session::instance()->editCategory(category, categoryOptions)) throw APIError(APIErrorType::Conflict, tr("Unable to edit category")); + + setResult(QString()); } void TorrentsController::removeCategoriesAction() @@ -1482,6 +1544,8 @@ void TorrentsController::removeCategoriesAction() const QStringList categories {params()[u"categories"_s].split(u'\n')}; for (const QString &category : categories) BitTorrent::Session::instance()->removeCategory(category); + + setResult(QString()); } void TorrentsController::categoriesAction() @@ -1517,6 +1581,8 @@ void TorrentsController::addTagsAction() torrent->addTag(Tag(tagStr)); }); } + + setResult(QString()); } void TorrentsController::setTagsAction() @@ -1562,6 +1628,8 @@ void TorrentsController::removeTagsAction() torrent->removeAllTags(); }); } + + setResult(QString()); } void TorrentsController::createTagsAction() @@ -1572,6 +1640,8 @@ void TorrentsController::createTagsAction() for (const QString &tagStr : tags) BitTorrent::Session::instance()->addTag(Tag(tagStr)); + + setResult(QString()); } void TorrentsController::deleteTagsAction() @@ -1581,6 +1651,8 @@ void TorrentsController::deleteTagsAction() const QStringList tags {params()[u"tags"_s].split(u',', Qt::SkipEmptyParts)}; for (const QString &tagStr : tags) BitTorrent::Session::instance()->removeTag(Tag(tagStr)); + + setResult(QString()); } void TorrentsController::tagsAction() @@ -1611,6 +1683,8 @@ void TorrentsController::renameFileAction() { throw APIError(APIErrorType::Conflict, error.message()); } + + setResult(QString()); } void TorrentsController::renameFolderAction() @@ -1633,6 +1707,8 @@ void TorrentsController::renameFolderAction() { throw APIError(APIErrorType::Conflict, error.message()); } + + setResult(QString()); } void TorrentsController::exportAction() @@ -1689,4 +1765,6 @@ void TorrentsController::setSSLParametersAction() throw APIError(APIErrorType::BadData); torrent->setSSLParameters(sslParams); + + setResult(QString()); } diff --git a/src/webui/api/transfercontroller.cpp b/src/webui/api/transfercontroller.cpp index 66ecb2747..8ef1c5803 100644 --- a/src/webui/api/transfercontroller.cpp +++ b/src/webui/api/transfercontroller.cpp @@ -104,6 +104,8 @@ void TransferController::setUploadLimitAction() if (limit == 0) limit = -1; BitTorrent::Session::instance()->setUploadSpeedLimit(limit); + + setResult(QString()); } void TransferController::setDownloadLimitAction() @@ -113,12 +115,16 @@ void TransferController::setDownloadLimitAction() if (limit == 0) limit = -1; BitTorrent::Session::instance()->setDownloadSpeedLimit(limit); + + setResult(QString()); } void TransferController::toggleSpeedLimitsModeAction() { BitTorrent::Session *const session = BitTorrent::Session::instance(); session->setAltGlobalSpeedLimitEnabled(!session->isAltGlobalSpeedLimitEnabled()); + + setResult(QString()); } void TransferController::speedLimitsModeAction() @@ -136,6 +142,8 @@ void TransferController::setSpeedLimitsModeAction() // Any non-zero values are considered as alternative mode BitTorrent::Session::instance()->setAltGlobalSpeedLimitEnabled(mode != 0); + + setResult(QString()); } void TransferController::banPeersAction() @@ -149,4 +157,6 @@ void TransferController::banPeersAction() if (!addr.ip.isNull()) BitTorrent::Session::instance()->banIP(addr.ip.toString()); } + + setResult(QString()); } diff --git a/src/webui/webapplication.cpp b/src/webui/webapplication.cpp index 22c6dc401..a28e002b5 100644 --- a/src/webui/webapplication.cpp +++ b/src/webui/webapplication.cpp @@ -361,25 +361,43 @@ void WebApplication::doProcessRequest() try { const APIResult result = controller->run(action, m_params, data); - switch (result.data.userType()) + if (result.data.isNull()) { - case QMetaType::QJsonDocument: - print(result.data.toJsonDocument().toJson(QJsonDocument::Compact), Http::CONTENT_TYPE_JSON); - break; - case QMetaType::QByteArray: + status(204); + } + else + { + switch (result.data.userType()) { - const auto resultData = result.data.toByteArray(); - print(resultData, (!result.mimeType.isEmpty() ? result.mimeType : Http::CONTENT_TYPE_TXT)); - if (!result.filename.isEmpty()) + case QMetaType::QJsonDocument: + print(result.data.toJsonDocument().toJson(QJsonDocument::Compact), Http::CONTENT_TYPE_JSON); + break; + case QMetaType::QByteArray: { - setHeader({u"Content-Disposition"_s, u"attachment; filename=\"%1\""_s.arg(result.filename)}); + const auto resultData = result.data.toByteArray(); + print(resultData, (!result.mimeType.isEmpty() ? result.mimeType : Http::CONTENT_TYPE_TXT)); + if (!result.filename.isEmpty()) + { + setHeader({u"Content-Disposition"_s, u"attachment; filename=\"%1\""_s.arg(result.filename)}); + } } + break; + case QMetaType::QString: + default: + print(result.data.toString(), Http::CONTENT_TYPE_TXT); + break; + } + + switch (result.status) + { + case APIStatus::Async: + status(202); + break; + case APIStatus::Ok: + default: + status(200); + break; } - break; - case QMetaType::QString: - default: - print(result.data.toString(), Http::CONTENT_TYPE_TXT); - break; } } catch (const APIError &error)