summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDaniel Schürmann <daschuer@mixxx.org>2021-01-09 15:20:55 +0100
committerGitHub <noreply@github.com>2021-01-09 15:20:55 +0100
commit39465acc94e51acbdff636f63264a4cc4023531a (patch)
tree6c152c25178a8e2b89fa609ab17c5443e1c1d36e /src
parent571fb9a9d1546e5fd7604df3b1618537aeac3832 (diff)
parent95e86310b9c58661e4abf195edf1370d7d72f17e (diff)
Merge pull request #3519 from uklotzde/networktask
Networking tasks: Split classes / validate request URLs / Bugfixes
Diffstat (limited to 'src')
-rw-r--r--src/musicbrainz/tagfetcher.cpp116
-rw-r--r--src/musicbrainz/tagfetcher.h9
-rw-r--r--src/musicbrainz/web/acoustidlookuptask.cpp29
-rw-r--r--src/musicbrainz/web/acoustidlookuptask.h4
-rw-r--r--src/musicbrainz/web/musicbrainzrecordingstask.cpp23
-rw-r--r--src/musicbrainz/web/musicbrainzrecordingstask.h6
-rw-r--r--src/network/jsonwebtask.cpp152
-rw-r--r--src/network/jsonwebtask.h51
-rw-r--r--src/network/networktask.cpp83
-rw-r--r--src/network/networktask.h96
-rw-r--r--src/network/webtask.cpp300
-rw-r--r--src/network/webtask.h221
12 files changed, 689 insertions, 401 deletions
diff --git a/src/musicbrainz/tagfetcher.cpp b/src/musicbrainz/tagfetcher.cpp
index d347c8e211..8da4e79ef1 100644
--- a/src/musicbrainz/tagfetcher.cpp
+++ b/src/musicbrainz/tagfetcher.cpp
@@ -49,14 +49,12 @@ void TagFetcher::cancel() {
m_fingerprintWatcher.disconnect(this);
m_fingerprintWatcher.cancel();
if (m_pAcoustIdTask) {
- m_pAcoustIdTask->disconnect(this);
- m_pAcoustIdTask->deleteLater();
- m_pAcoustIdTask = nullptr;
+ m_pAcoustIdTask->invokeAbort();
+ DEBUG_ASSERT(!m_pAcoustIdTask);
}
if (m_pMusicBrainzTask) {
- m_pMusicBrainzTask->disconnect(this);
- m_pMusicBrainzTask->deleteLater();
- m_pMusicBrainzTask = nullptr;
+ m_pMusicBrainzTask->invokeAbort();
+ DEBUG_ASSERT(!m_pMusicBrainzTask);
}
}
@@ -106,12 +104,19 @@ void TagFetcher::slotFingerprintReady() {
void TagFetcher::slotAcoustIdTaskSucceeded(
QList<QUuid> recordingIds) {
DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this);
- DEBUG_ASSERT(m_pAcoustIdTask.get() ==
- qobject_cast<mixxx::AcoustIdLookupTask*>(sender()));
+ auto* const pAcoustIdTask = m_pAcoustIdTask.get();
+ VERIFY_OR_DEBUG_ASSERT(pAcoustIdTask ==
+ qobject_cast<mixxx::AcoustIdLookupTask*>(sender())) {
+ return;
+ }
+ m_pAcoustIdTask = nullptr;
+ const auto taskDeleter = mixxx::ScopedDeleteLater(pAcoustIdTask);
+ pAcoustIdTask->disconnect(this);
if (recordingIds.isEmpty()) {
auto pTrack = std::move(m_pTrack);
cancel();
+
emit resultAvailable(
std::move(pTrack),
QList<mixxx::musicbrainz::TrackRelease>());
@@ -144,83 +149,102 @@ void TagFetcher::slotAcoustIdTaskSucceeded(
kMusicBrainzTimeoutMillis);
}
+bool TagFetcher::onAcoustIdTaskTerminated() {
+ DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this);
+ auto* const pAcoustIdTask = m_pAcoustIdTask.get();
+ DEBUG_ASSERT(sender());
+ VERIFY_OR_DEBUG_ASSERT(pAcoustIdTask ==
+ qobject_cast<mixxx::AcoustIdLookupTask*>(sender())) {
+ return false;
+ }
+ m_pAcoustIdTask = nullptr;
+ const auto taskDeleter = mixxx::ScopedDeleteLater(pAcoustIdTask);
+ pAcoustIdTask->disconnect(this);
+ return true;
+}
+
void TagFetcher::slotAcoustIdTaskFailed(
const mixxx::network::JsonWebResponse& response) {
DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this);
- DEBUG_ASSERT(m_pAcoustIdTask.get() ==
- qobject_cast<mixxx::AcoustIdLookupTask*>(sender()));
+ if (!onAcoustIdTaskTerminated()) {
+ return;
+ }
cancel();
emit networkError(
- response.statusCode,
+ response.statusCode(),
"AcoustID",
- response.content.toJson(),
+ response.content().toJson(),
-1);
}
void TagFetcher::slotAcoustIdTaskAborted() {
DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this);
- DEBUG_ASSERT(m_pAcoustIdTask.get() ==
- qobject_cast<mixxx::AcoustIdLookupTask*>(sender()));
+ if (!onAcoustIdTaskTerminated()) {
+ return;
+ }
- auto pTrack = std::move(m_pTrack);
cancel();
-
- emit resultAvailable(
- std::move(pTrack),
- QList<mixxx::musicbrainz::TrackRelease>{});
}
void TagFetcher::slotAcoustIdTaskNetworkError(
- const QUrl& requestUrl,
QNetworkReply::NetworkError errorCode,
const QString& errorString,
- const QByteArray& errorContent) {
- Q_UNUSED(requestUrl);
- Q_UNUSED(errorContent);
+ const mixxx::network::WebResponseWithContent& responseWithContent) {
+ Q_UNUSED(responseWithContent);
DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this);
- DEBUG_ASSERT(m_pAcoustIdTask.get() ==
- qobject_cast<mixxx::AcoustIdLookupTask*>(sender()));
+ if (!onAcoustIdTaskTerminated()) {
+ return;
+ }
cancel();
emit networkError(
mixxx::network::kHttpStatusCodeInvalid,
- "AcoustID",
+ QStringLiteral("AcoustID"),
errorString,
errorCode);
}
+bool TagFetcher::onMusicBrainzTaskTerminated() {
+ DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this);
+ auto* const pMusicBrainzTask = m_pMusicBrainzTask.get();
+ DEBUG_ASSERT(sender());
+ VERIFY_OR_DEBUG_ASSERT(pMusicBrainzTask ==
+ qobject_cast<mixxx::MusicBrainzRecordingsTask*>(sender())) {
+ return false;
+ }
+ m_pMusicBrainzTask = nullptr;
+ const auto taskDeleter = mixxx::ScopedDeleteLater(pMusicBrainzTask);
+ pMusicBrainzTask->disconnect(this);
+ return true;
+}
+
void TagFetcher::slotMusicBrainzTaskAborted() {
DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this);
- DEBUG_ASSERT(m_pMusicBrainzTask.get() ==
- qobject_cast<mixxx::MusicBrainzRecordingsTask*>(sender()));
+ if (!onMusicBrainzTaskTerminated()) {
+ return;
+ }
- auto pTrack = std::move(m_pTrack);
cancel();
-
- emit resultAvailable(
- std::move(pTrack),
- QList<mixxx::musicbrainz::TrackRelease>{});
}
void TagFetcher::slotMusicBrainzTaskNetworkError(
- const QUrl& requestUrl,
QNetworkReply::NetworkError errorCode,
const QString& errorString,
- const QByteArray& errorContent) {
- Q_UNUSED(requestUrl);
- Q_UNUSED(errorContent);
+ const mixxx::network::WebResponseWithContent& responseWithContent) {
+ Q_UNUSED(responseWithContent);
DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this);
- DEBUG_ASSERT(m_pMusicBrainzTask.get() ==
- qobject_cast<mixxx::MusicBrainzRecordingsTask*>(sender()));
+ if (!onMusicBrainzTaskTerminated()) {
+ return;
+ }
cancel();
emit networkError(
mixxx::network::kHttpStatusCodeInvalid,
- "MusicBrainz",
+ QStringLiteral("MusicBrainz"),
errorString,
errorCode);
}
@@ -230,13 +254,14 @@ void TagFetcher::slotMusicBrainzTaskFailed(
int errorCode,
const QString& errorMessage) {
DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this);
- DEBUG_ASSERT(m_pMusicBrainzTask.get() ==
- qobject_cast<mixxx::MusicBrainzRecordingsTask*>(sender()));
+ if (!onMusicBrainzTaskTerminated()) {
+ return;
+ }
cancel();
emit networkError(
- response.statusCode,
+ response.statusCode(),
"MusicBrainz",
errorMessage,
errorCode);
@@ -245,8 +270,9 @@ void TagFetcher::slotMusicBrainzTaskFailed(
void TagFetcher::slotMusicBrainzTaskSucceeded(
const QList<mixxx::musicbrainz::TrackRelease>& guessedTrackReleases) {
DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this);
- DEBUG_ASSERT(m_pMusicBrainzTask.get() ==
- qobject_cast<mixxx::MusicBrainzRecordingsTask*>(sender()));
+ if (!onMusicBrainzTaskTerminated()) {
+ return;
+ }
auto pTrack = std::move(m_pTrack);
cancel();
diff --git a/src/musicbrainz/tagfetcher.h b/src/musicbrainz/tagfetcher.h
index 7d5784fa14..160a854aed 100644
--- a/src/musicbrainz/tagfetcher.h
+++ b/src/musicbrainz/tagfetcher.h
@@ -48,10 +48,9 @@ class TagFetcher : public QObject {
const mixxx::network::JsonWebResponse& response);
void slotAcoustIdTaskAborted();
void slotAcoustIdTaskNetworkError(
- const QUrl& requestUrl,
QNetworkReply::NetworkError errorCode,
const QString& errorString,
- const QByteArray& errorContent);
+ const mixxx::network::WebResponseWithContent& responseWithContent);
void slotMusicBrainzTaskSucceeded(
const QList<mixxx::musicbrainz::TrackRelease>& guessedTrackReleases);
@@ -61,12 +60,14 @@ class TagFetcher : public QObject {
const QString& errorMessage);
void slotMusicBrainzTaskAborted();
void slotMusicBrainzTaskNetworkError(
- const QUrl& requestUrl,
QNetworkReply::NetworkError errorCode,
const QString& errorString,
- const QByteArray& errorContent);
+ const mixxx::network::WebResponseWithContent& responseWithContent);
private:
+ bool onAcoustIdTaskTerminated();
+ bool onMusicBrainzTaskTerminated();
+
QNetworkAccessManager m_network;
QFutureWatcher<QString> m_fingerprintWatcher;
diff --git a/src/musicbrainz/web/acoustidlookuptask.cpp b/src/musicbrainz/web/acoustidlookuptask.cpp
index c79578a0d4..9319641f75 100644
--- a/src/musicbrainz/web/acoustidlookuptask.cpp
+++ b/src/musicbrainz/web/acoustidlookuptask.cpp
@@ -113,37 +113,37 @@ QNetworkReply* AcoustIdLookupTask::sendNetworkRequest(
}
void AcoustIdLookupTask::onFinished(
- network::JsonWebResponse&& response) {
+ const network::JsonWebResponse& response) {
if (!response.isStatusCodeSuccess()) {
kLogger.warning()
<< "Request failed with HTTP status code"
- << response.statusCode;
- emitFailed(std::move(response));
+ << response.statusCode();
+ emitFailed(response);
return;
}
- VERIFY_OR_DEBUG_ASSERT(response.statusCode == network::kHttpStatusCodeOk) {
+ VERIFY_OR_DEBUG_ASSERT(response.statusCode() == network::kHttpStatusCodeOk) {
kLogger.warning()
<< "Unexpected HTTP status code"
- << response.statusCode;
- emitFailed(std::move(response));
+ << response.statusCode();
+ emitFailed(response);
return;
}
- VERIFY_OR_DEBUG_ASSERT(response.content.isObject()) {
+ VERIFY_OR_DEBUG_ASSERT(response.content().isObject()) {
kLogger.warning()
<< "Invalid JSON content"
- << response.content;
- emitFailed(std::move(response));
+ << response.content();
+ emitFailed(response);
return;
}
- const auto jsonObject = response.content.object();
+ const auto jsonObject = response.content().object();
const auto statusText = jsonObject.value(QStringLiteral("status")).toString();
if (statusText != QStringLiteral("ok")) {
kLogger.warning()
<< "Unexpected response status"
<< statusText;
- emitFailed(std::move(response));
+ emitFailed(response);
return;
}
@@ -207,11 +207,11 @@ void AcoustIdLookupTask::onFinished(
}
}
}
- emitSucceeded(std::move(recordingIds));
+ emitSucceeded(recordingIds);
}
void AcoustIdLookupTask::emitSucceeded(
- QList<QUuid>&& recordingIds) {
+ const QList<QUuid>& recordingIds) {
VERIFY_OR_DEBUG_ASSERT(
isSignalFuncConnected(&AcoustIdLookupTask::succeeded)) {
kLogger.warning()
@@ -219,8 +219,7 @@ void AcoustIdLookupTask::emitSucceeded(
deleteLater();
return;
}
- emit succeeded(
- std::move(recordingIds));
+ emit succeeded(recordingIds);
}
} // namespace mixxx
diff --git a/src/musicbrainz/web/acoustidlookuptask.h b/src/musicbrainz/web/acoustidlookuptask.h
index 75e9b827f5..74317b8c10 100644
--- a/src/musicbrainz/web/acoustidlookuptask.h
+++ b/src/musicbrainz/web/acoustidlookuptask.h
@@ -32,10 +32,10 @@ class AcoustIdLookupTask : public network::JsonWebTask {
private:
void onFinished(
- network::JsonWebResponse&& response) override;
+ const network::JsonWebResponse& response) override;
void emitSucceeded(
- QList<QUuid>&& recordingIds);
+ const QList<QUuid>& recordingIds);
const QUrlQuery m_urlQuery;
};
diff --git a/src/musicbrainz/web/musicbrainzrecordingstask.cpp b/src/musicbrainz/web/musicbrainzrecordingstask.cpp
index 5afec1225e..77b14ba9ee 100644
--- a/src/musicbrainz/web/musicbrainzrecordingstask.cpp
+++ b/src/musicbrainz/web/musicbrainzrecordingstask.cpp
@@ -71,7 +71,7 @@ MusicBrainzRecordingsTask::MusicBrainzRecordingsTask(
: network::WebTask(
networkAccessManager,
parent),
- m_queuedRecordingIds(std::move(recordingIds)),
+ m_queuedRecordingIds(recordingIds),
m_parentTimeoutMillis(0) {
musicbrainz::registerMetaTypesOnce();
}
@@ -126,9 +126,10 @@ void MusicBrainzRecordingsTask::doNetworkReplyFinished(
emitFailed(
network::WebResponse(
finishedNetworkReply->url(),
+ finishedNetworkReply->request().url(),
statusCode),
error.code,
- std::move(error.message));
+ error.message);
return;
}
@@ -147,9 +148,10 @@ void MusicBrainzRecordingsTask::doNetworkReplyFinished(
emitFailed(
network::WebResponse(
finishedNetworkReply->url(),
+ finishedNetworkReply->request().url(),
statusCode),
-1,
- "Failed to parse XML response");
+ QStringLiteral("Failed to parse XML response"));
return;
}
@@ -158,7 +160,7 @@ void MusicBrainzRecordingsTask::doNetworkReplyFinished(
m_finishedRecordingIds.clear();
auto trackReleases = m_trackReleases.values();
m_trackReleases.clear();
- emitSucceeded(std::move(trackReleases));
+ emitSucceeded(trackReleases);
return;
}
@@ -168,7 +170,7 @@ void MusicBrainzRecordingsTask::doNetworkReplyFinished(
}
void MusicBrainzRecordingsTask::emitSucceeded(
- QList<musicbrainz::TrackRelease>&& trackReleases) {
+ const QList<musicbrainz::TrackRelease>& trackReleases) {
VERIFY_OR_DEBUG_ASSERT(
isSignalFuncConnected(&MusicBrainzRecordingsTask::succeeded)) {
kLogger.warning()
@@ -176,14 +178,13 @@ void MusicBrainzRecordingsTask::emitSucceeded(
deleteLater();
return;
}
- emit succeeded(
- std::move(trackReleases));
+ emit succeeded(trackReleases);
}
void MusicBrainzRecordingsTask::emitFailed(
- network::WebResponse&& response,
+ const network::WebResponse& response,
int errorCode,
- QString&& errorMessage) {
+ const QString& errorMessage) {
VERIFY_OR_DEBUG_ASSERT(
isSignalFuncConnected(&MusicBrainzRecordingsTask::failed)) {
kLogger.warning()
@@ -195,9 +196,9 @@ void MusicBrainzRecordingsTask::emitFailed(
return;
}
emit failed(
- std::move(response),
+ response,
errorCode,
- std::move(errorMessage));
+ errorMessage);
}
} // namespace mixxx
diff --git a/src/musicbrainz/web/musicbrainzrecordingstask.h b/src/musicbrainz/web/musicbrainzrecordingstask.h
index 39678d65ff..3a8451745d 100644
--- a/src/musicbrainz/web/musicbrainzrecordingstask.h
+++ b/src/musicbrainz/web/musicbrainzrecordingstask.h
@@ -38,11 +38,11 @@ class MusicBrainzRecordingsTask : public network::WebTask {
network::HttpStatusCode statusCode) override;
void emitSucceeded(
- QList<musicbrainz::TrackRelease>&& trackReleases);
+ const QList<musicbrainz::TrackRelease>& trackReleases);
void emitFailed(
- network::WebResponse&& response,
+ const network::WebResponse& response,
int errorCode,
- QString&& errorMessage);
+ const QString& errorMessage);
void continueWithNextRequest();
diff --git a/src/network/jsonwebtask.cpp b/src/network/jsonwebtask.cpp
index 0e355db369..434ab27081 100644
--- a/src/network/jsonwebtask.cpp
+++ b/src/network/jsonwebtask.cpp
@@ -37,64 +37,55 @@ const QString JSON_CONTENT_TYPE = "application/json";
const QMimeType JSON_MIME_TYPE = QMimeDatabase().mimeTypeForName(JSON_CONTENT_TYPE);
-QMimeType readContentType(
- const QNetworkReply* reply) {
- DEBUG_ASSERT(reply);
- const QVariant contentTypeHeader = reply->header(QNetworkRequest::ContentTypeHeader);
- if (!contentTypeHeader.isValid() || contentTypeHeader.isNull()) {
- kLogger.warning()
- << "Missing content type header";
- return QMimeType();
- }
- const QString contentTypeString = contentTypeHeader.toString();
- const QString contentTypeWithoutParams = contentTypeString.left(contentTypeString.indexOf(';'));
- const QMimeType contentType = QMimeDatabase().mimeTypeForName(contentTypeWithoutParams);
- if (!contentType.isValid()) {
- kLogger.warning()
- << "Unknown content type"
- << contentTypeWithoutParams;
- }
- return contentType;
-}
-
-bool readJsonContent(
+/// If parsing fails the functions returns std::nullopt and optionally
+/// the response content in pInvalidResponseContent for further processing.
+std::optional<QJsonDocument> readJsonContent(
QNetworkReply* reply,
- QJsonDocument* jsonContent) {
+ std::pair<QMimeType, QByteArray>* pInvalidResponseContent = nullptr) {
DEBUG_ASSERT(reply);
- DEBUG_ASSERT(jsonContent);
DEBUG_ASSERT(JSON_MIME_TYPE.isValid());
- const auto contentType = readContentType(reply);
+ auto contentType = WebTask::readContentType(*reply);
+ auto optContentData = WebTask::readContentData(reply);
if (contentType != JSON_MIME_TYPE) {
- kLogger.warning()
- << "Unexpected content type"
- << contentType;
- return false;
+ kLogger.debug()
+ << "Received content type"
+ << contentType
+ << "instead of"
+ << JSON_MIME_TYPE;
+ if (pInvalidResponseContent) {
+ *pInvalidResponseContent = std::make_pair(
+ std::move(contentType),
+ optContentData.value_or(QByteArray{}));
+ }
+ return std::nullopt;
}
- QByteArray jsonData = reply->readAll();
- if (jsonData.isEmpty()) {
- kLogger.warning()
- << "Empty reply";
- return false;
+ if (!optContentData || optContentData->isEmpty()) {
+ kLogger.debug() << "Empty JSON network reply";
+ return QJsonDocument{};
}
QJsonParseError parseError;
- const auto jsonDoc = QJsonDocument::fromJson(
- jsonData,
+ auto jsonDocument = QJsonDocument::fromJson(
+ *optContentData,
&parseError);
// QJsonDocument::fromJson() returns a non-null document
// if parsing succeeds and otherwise null on error. The
// parse error must only be evaluated if the returned
// document is null!
- if (jsonDoc.isNull() &&
+ if (jsonDocument.isNull() &&
parseError.error != QJsonParseError::NoError) {
kLogger.warning()
<< "Failed to parse JSON data:"
<< parseError.errorString()
<< "at offset"
<< parseError.offset;
- return false;
+ if (pInvalidResponseContent) {
+ *pInvalidResponseContent = std::make_pair(
+ std::move(contentType),
+ std::move(*optContentData));
+ }
+ return std::nullopt;
}
- *jsonContent = jsonDoc;
- return true;
+ return jsonDocument;
}
// TODO: Allow to customize headers and attributes?
@@ -115,43 +106,39 @@ QNetworkRequest newRequest(
QDebug operator<<(QDebug dbg, const JsonWebResponse& arg) {
return dbg
- << "CustomWebResponse{"
- << static_cast<const WebResponse&>(arg)
- << arg.content
- << '}';
+ << "WebResponseWithContent{"
+ << arg.m_response
+ << arg.m_content
+ << '}';
}
JsonWebTask::JsonWebTask(
QNetworkAccessManager* networkAccessManager,
- const QUrl& baseUrl,
+ QUrl baseUrl,
JsonWebRequest&& request,
QObject* parent)
: WebTask(networkAccessManager, parent),
- m_baseUrl(baseUrl),
- m_request(std::move(request)) {
+ m_baseUrl(std::move(baseUrl)),
+ m_request(request) {
std::call_once(registerMetaTypesOnceFlag, registerMetaTypesOnce);
DEBUG_ASSERT(!m_baseUrl.isEmpty());
}
void JsonWebTask::onFinished(
- JsonWebResponse&& response) {
+ const JsonWebResponse& jsonResponse) {
kLogger.info()
<< this
- << "Response received"
- << response.replyUrl
- << response.statusCode
- << response.content;
+ << "Received JSON response"
+ << jsonResponse;
deleteLater();
}
void JsonWebTask::onFinishedCustom(
- CustomWebResponse&& response) {
+ const WebResponseWithContent& customResponse) {
kLogger.info()
<< this
- << "Custom response received"
- << response.replyUrl
- << response.statusCode
- << response.content;
+ << "Received custom response"
+ << customResponse;
deleteLater();
}
@@ -244,12 +231,24 @@ QNetworkReply* JsonWebTask::doStartNetworkRequest(
DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this);
DEBUG_ASSERT(networkAccessManager);
- DEBUG_ASSERT(m_baseUrl.isValid());
+ VERIFY_OR_DEBUG_ASSERT(m_baseUrl.isValid()) {
+ kLogger.warning() << "Invalid base URL" << m_baseUrl;
+ return nullptr;
+ }
QUrl url = m_baseUrl;
url.setPath(m_request.path);
+ VERIFY_OR_DEBUG_ASSERT(url.isValid()) {
+ kLogger.warning() << "Invalid request path" << m_request.path;
+ return nullptr;
+ }
if (!m_request.query.isEmpty()) {
url.setQuery(m_request.query);
+ VERIFY_OR_DEBUG_ASSERT(url.isValid()) {
+ kLogger.warning() << "Invalid query string" << m_request.query.toString();
+ return nullptr;
+ }
}
+ // Already validated while composing (see above)
DEBUG_ASSERT(url.isValid());
return sendNetworkRequest(
@@ -262,26 +261,31 @@ QNetworkReply* JsonWebTask::doStartNetworkRequest(
void JsonWebTask::doNetworkReplyFinished(
QNetworkReply* finishedNetworkReply,
HttpStatusCode statusCode) {
- QJsonDocument content;
- if (statusCode != kHttpStatusCodeInvalid &&
- finishedNetworkReply->bytesAvailable() > 0 &&
- !readJsonContent(finishedNetworkReply, &content)) {
- onFinishedCustom(CustomWebResponse{
- WebResponse{
- finishedNetworkReply->url(),
- statusCode},
- finishedNetworkReply->readAll()});
- } else {
- onFinished(JsonWebResponse{
- WebResponse{
- finishedNetworkReply->url(),
- statusCode},
- std::move(content)});
+ DEBUG_ASSERT(finishedNetworkReply);
+ std::optional<QJsonDocument> optJsonContent;
+ auto webResponse = WebResponse{
+ finishedNetworkReply->url(),
+ finishedNetworkReply->request().url(),
+ statusCode};
+ if (statusCode != kHttpStatusCodeInvalid) {
+ std::pair<QMimeType, QByteArray> contentTypeAndBytes;
+ optJsonContent = readJsonContent(finishedNetworkReply, &contentTypeAndBytes);
+ if (!optJsonContent) {
+ // Failed to read JSON content
+ onFinishedCustom(WebResponseWithContent{
+ std::move(webResponse),
+ std::move(contentTypeAndBytes.first),
+ std::move(contentTypeAndBytes.second)});
+ return;
+ }
}
+ onFinished(JsonWebResponse{
+ std::move(webResponse),
+ optJsonContent.value_or(QJsonDocument{})});
}
void JsonWebTask::emitFailed(
- network::JsonWebResponse&& response) {
+ const network::JsonWebResponse& response) {
VERIFY_OR_DEBUG_ASSERT(
isSignalFuncConnected(&JsonWebTask::failed)) {
kLogger.warning()
@@ -291,7 +295,7 @@ void JsonWebTask::emitFailed(
deleteLater();
return;
}
- emit failed(std::move(response));
+ emit failed(response);
}
} // namespace network
diff --git a/src/network/jsonwebtask.h b/src/network/jsonwebtask.h
index 544fd97760..916be0cc99 100644
--- a/src/network/jsonwebtask.h
+++ b/src/network/jsonwebtask.h
@@ -25,28 +25,45 @@ struct JsonWebRequest final {
QJsonDocument content;
};
-struct JsonWebResponse : public WebResponse {
+class JsonWebResponse final {
public:
static void registerMetaType();
JsonWebResponse() = default;
JsonWebResponse(
- WebResponse response,
- QJsonDocument content)
- : WebResponse(std::move(response)),
- content(std::move(content)) {
+ WebResponse&& response,
+ QJsonDocument&& content)
+ : m_response(response),
+ m_content(content) {
}
JsonWebResponse(const JsonWebResponse&) = default;
JsonWebResponse(JsonWebResponse&&) = default;
- ~JsonWebResponse() override = default;
JsonWebResponse& operator=(const JsonWebResponse&) = default;
JsonWebResponse& operator=(JsonWebResponse&&) = default;
- QJsonDocument content;
-};
+ bool isStatusCodeSuccess() const {
+ return m_response.isStatusCodeSuccess();
+ }
-QDebug operator<<(QDebug dbg, const JsonWebResponse& arg);
+ HttpStatusCode statusCode() const {
+ return m_response.statusCode();
+ }
+
+ const QUrl& replyUrl() const {
+ return m_response.replyUrl();
+ }
+
+ const QJsonDocument& content() const {
+ return m_content;
+ }
+
+ friend QDebug operator<<(QDebug dbg, const JsonWebResponse& arg);
+
+ private:
+ WebResponse m_response;
+ QJsonDocument m_content;
+};
class JsonWebTask : public WebTask {
Q_OBJECT
@@ -54,7 +71,7 @@ class JsonWebTask : public WebTask {
public:
JsonWebTask(
QNetworkAccessManager* networkAccessManager,
- const QUrl& baseUrl,
+ QUrl baseUrl,
JsonWebRequest&& request,
QObject* parent = nullptr);
~JsonWebTask() override = default;
@@ -72,16 +89,18 @@ class JsonWebTask : public WebTask {
const QJsonDocument& content);
void emitFailed(
- network::JsonWebResponse&& response);
+ const network::JsonWebResponse& response);
private:
- // Handle the response and ensure that the task eventually
- // gets deleted. The default implementation discards the
- // response and deletes the task.
+ /// Handle the response and ensure that the task eventually
+ /// gets deleted.
+ ///
+ /// Could be overridden by derived classes. The default
+ /// implementation discards the response and deletes the task.
virtual void onFinished(
- JsonWebResponse&& response);
+ const JsonWebResponse& jsonResponse);
virtual void onFinishedCustom(
- CustomWebResponse&& response);
+ const WebResponseWithContent& customResponse);
QNetworkReply* doStartNetworkRequest(
QNetworkAccessManager* networkAccessManager,
diff --git a/src/network/networktask.cpp b/src/network/networktask.cpp
new file mode 100644
index 0000000000..4aac5191bc
--- /dev/null
+++ b/src/network/networktask.cpp
@@ -0,0 +1,83 @@
+#include "network/networktask.h"
+
+#include "moc_networktask.cpp"
+#include "util/counter.h"
+#include "util/logger.h"
+#include "util/thread_affinity.h"
+
+namespace mixxx {
+
+namespace network {
+
+namespace {
+
+const Logger kLogger("mixxx::network::NetworkTask");
+
+// count = even number (ctor + dtor)
+// sum = 0 (no memory leaks)
+Counter s_instanceCounter(QStringLiteral("mixxx::network::NetworkTask"));
+
+} // anonymous namespace
+
+NetworkTask::NetworkTask(
+ QNetworkAccessManager* networkAccessManager,
+ QObject* parent)
+ : QObject(parent),
+ m_networkAccessManagerWeakPtr(networkAccessManager) {
+ s_instanceCounter.increment(1);
+}
+
+NetworkTask::~NetworkTask() {
+ s_instanceCounter.increment(-1);
+}
+
+void NetworkTask::invokeStart(int timeoutMillis) {
+ QMetaObject::invokeMethod(
+ this,
+#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
+ "slotStart",
+ Qt::AutoConnection,
+ Q_ARG(int, timeoutMillis)
+#else
+ [this, timeoutMillis] {
+ this->slotStart(timeoutMillis);
+ }
+#endif
+ );
+}
+
+void NetworkTask::invokeAbort() {
+ QMetaObject::invokeMethod(
+ this,
+#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
+ "slotAbort"
+#else
+ [this] {
+ this->slotAbort();
+ }
+#endif
+ );
+}
+
+void NetworkTask::abort() {
+ DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this);
+ slotAbort();
+}
+
+void NetworkTask::emitAborted(
+ const QUrl& requestUrl) {
+ VERIFY_OR_DEBUG_ASSERT(
+ isSignalFuncConnected(&NetworkTask::aborted)) {
+ kLogger.warning()
+ << this
+ << "Unhandled abort signal"
+ << requestUrl;
+ deleteLater();
+ return;
+ }
+ emit aborted(requestUrl);
+}
+
+} // namespace network
+
+} // namespace mixxx
diff --git a/src/network/networktask.h b/src/network/networktask.h
new file mode 100644
index 0000000000..410b977977
--- /dev/null
+++ b/src/network/networktask.h
@@ -0,0 +1,96 @@
+#pragma once
+
+#include <QMetaMethod>
+#include <QNetworkAccessManager>
+#include <QNetworkReply>
+#include <QUrl>
+
+#include "util/assert.h"
+#include "util/qt.h"
+
+namespace mixxx {
+
+namespace network {
+
+/// A transient task for performing a single generic network request
+/// asynchronously.
+///
+/// The results are transmitted by emitting signals. At least one
+/// of the signal receivers is responsible for destroying the task
+/// by invoking QObject::deleteLater(). If no receiver is connected
+/// at the time the finalization signal is emitted then the task
+/// will destroy itself.
+class NetworkTask : public QObject {
+ Q_OBJECT
+
+ protected:
+ explicit NetworkTask(
+ QNetworkAccessManager* networkAccessManager,
+ QObject* parent = nullptr);
+ virtual ~NetworkTask();
+
+ /// Cancel the task by aborting the pending network request.
+ ///
+ /// This function is NOT thread-safe and must only be called from
+ /// the event loop thread.
+ void abort();
+
+ // Required to allow derived classes to invoke abort()
+ // on other instances of this class.
+ void static abortThis(NetworkTask* pThis) {
+ DEBUG_ASSERT(pThis);
+ pThis->abort();
+ }
+
+ template<typename S>
+ bool isSignalFuncConnected(
+ S signalFunc) {
+ const QMetaMethod signal = QMetaMethod::fromSignal(signalFunc);
+ return isSignalConnected(signal);
+ }
+
+ /// Send an aborted signal with the optional request URL