summaryrefslogtreecommitdiffstats
path: root/src/network
diff options
context:
space:
mode:
authorUwe Klotz <uklotz@mixxx.org>2021-01-10 16:37:38 +0100
committerUwe Klotz <uklotz@mixxx.org>2021-01-10 16:37:38 +0100
commit0d974db280f2eaf969a79d34080051f683d9c6fa (patch)
treedaa064812d027267c4491d05c7e6469400d11739 /src/network
parentaef1e1e2a96cbf922f8364d83c33e37c0168168d (diff)
parent39465acc94e51acbdff636f63264a4cc4023531a (diff)
Merge branch '2.3' of git@github.com:mixxxdj/mixxx.git into main
# Conflicts: # src/preferences/dialog/dlgprefsound.cpp # src/soundio/soundmanagerconfig.h # src/track/cueinfo.cpp # src/track/cueinfo.h
Diffstat (limited to 'src/network')
-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
6 files changed, 582 insertions, 321 deletions
diff --git a/src/network/jsonwebtask.cpp b/src/network/jsonwebtask.cpp
index 0c141c0394..961d3a0543 100644
--- a/src/network/jsonwebtask.cpp
+++ b/src/network/jsonwebtask.cpp
@@ -29,64 +29,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?
@@ -110,43 +101,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();
}
@@ -230,12 +217,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(
@@ -248,26 +247,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()
@@ -277,7 +281,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 if available.
+ void emitAborted(
+ const QUrl& requestUrl = QUrl{});
+
+ /// All member variables must only be accessed from
+ /// the event loop thread!!
+ const SafeQPointer<QNetworkAccessManager> m_networkAccessManagerWeakPtr;
+
+ public:
+ static constexpr int kNoTimeout = 0;
+
+ /// Start a new task by sending a network request.
+ ///
+ /// timeoutMillis <= 0: No timeout (unlimited)
+ /// timeoutMillis > 0: Implicitly aborted after timeout expired
+ ///
+ /// This function is thread-safe and could be invoked from any thread.
+ void invokeStart(
+ int timeoutMillis = kNoTimeout);
+
+ /// Cancel the task by aborting the pending network request.
+ ///
+ /// This function is thread-safe and could be invoked from any thread.
+ void invokeAbort();
+
+ public slots:
+ virtual void slotStart(
+ int timeoutMillis) = 0;
+ virtual void slotAbort() = 0;
+
+ signals:
+ /// The receiver is responsible for deleting the task in the
+ /// corresponding slot handler!! Otherwise the task will remain
+ /// in memory as a dysfunctional zombie until its parent object
+ /// is finally deleted. If no receiver is connected the task
+ /// will be deleted implicitly.
+
+ /// Client-side abort
+ void aborted(
+ const QUrl& requestUrl);
+};
+
+} // namespace network
+
+} // namespace mixxx
diff --git a/src/network/webtask.cpp b/src/network/webtask.cpp
index 02d2f037b6..30d910b800 100644
--- a/src/network/webtask.cpp
+++ b/src/network/webtask.cpp
@@ -1,11 +1,10 @@
#include "network/webtask.h"
+#include <QMimeDatabase>
#include <QTimerEvent>
#include <mutex> // std::once_flag
#include "moc_webtask.cpp"
-#include "util/assert.h"
-#include "util/counter.h"
#include "util/logger.h"
#include "util/thread_affinity.h"
@@ -19,26 +18,32 @@ const Logger kLogger("mixxx::network::WebTask");
constexpr int kInvalidTimerId = -1;
-// count = even number (ctor + dtor)
-// sum = 0 (no memory leaks)
-Counter s_instanceCounter(QStringLiteral("mixxx::network::WebTask"));
-
std::once_flag registerMetaTypesOnceFlag;
void registerMetaTypesOnce() {
WebResponse::registerMetaType();
- CustomWebResponse::registerMetaType();
+ WebResponseWithContent::registerMetaType();
}
int readStatusCode(
- const QNetworkReply* networkReply) {
+ const QNetworkReply& networkReply) {
const QVariant statusCodeAttr =
- networkReply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
+ networkReply.attribute(QNetworkRequest::HttpStatusCodeAttribute);
+ if (!statusCodeAttr.isValid()) {
+ // No status code available
+ return kHttpStatusCodeInvalid;
+ }
+ VERIFY_OR_DEBUG_ASSERT(statusCodeAttr.canConvert<int>()) {
+ kLogger.warning()
+ << "Invalid status code attribute"
+ << statusCodeAttr;
+ return kHttpStatusCodeInvalid;
+ }
bool statusCodeValid = false;
const int statusCode = statusCodeAttr.toInt(&statusCodeValid);
VERIFY_OR_DEBUG_ASSERT(statusCodeValid && HttpStatusCode_isValid(statusCode)) {
kLogger.warning()
- << "Invalid or missing status code attribute"
+ << "Failed to read status code attribute"
<< statusCodeAttr;
return kHttpStatusCodeInvalid;
}
@@ -54,123 +59,138 @@ int readStatusCode(
QDebug operator<<(QDebug dbg, const WebResponse& arg) {
return dbg
<< "WebResponse{"
- << arg.replyUrl
- << arg.statusCode
+ << arg.m_requestUrl
+ << arg.m_replyUrl
+ << arg.m_statusCode
<< '}';
}
-/*static*/ void CustomWebResponse::registerMetaType() {
- qRegisterMetaType<CustomWebResponse>("mixxx::network::CustomWebResponse");
+/*static*/ void WebResponseWithContent::registerMetaType() {
+ qRegisterMetaType<WebResponseWithContent>("mixxx::network::WebResponseWithContent");
}
-QDebug operator<<(QDebug dbg, const CustomWebResponse& arg) {
+QDebug operator<<(QDebug dbg, const WebResponseWithContent& arg) {
return dbg
- << "CustomWebResponse{"
- << static_cast<const WebResponse&>(arg)
- << arg.content
+ << "WebResponseWithContent{"
+ << arg.m_response
+ << arg.m_contentType
+ << arg.m_contentData
<< '}';
}
+//static
+QMimeType WebTask::readContentType(
+ const QNetworkReply& reply) {
+ const QVariant contentTypeHeader = reply.header(QNetworkRequest::ContentTypeHeader);
+ if (!contentTypeHeader.isValid() || contentTypeHeader.isNull()) {
+ if (reply.isReadable() && reply.bytesAvailable() > 0) {
+ 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;
+}
+
+//static
+std::optional<QByteArray> WebTask::readContentData(
+ QNetworkReply* reply) {
+ if (!reply->isReadable()) {
+ return std::nullopt;
+ }
+ return reply->readAll();
+}
+
WebTask::WebTask(
QNetworkAccessManager* networkAccessManager,
QObject* parent)
- : QObject(parent),
- m_networkAccessManagerWeakPtr(networkAccessManager),
- m_timeoutTimerId(kInvalidTimerId),
- m_state(State::Idle) {
+ : NetworkTask(networkAccessManager, parent),
+ m_state(State::Idle),
+ m_timeoutTimerId(kInvalidTimerId) {
std::call_once(registerMetaTypesOnceFlag, registerMetaTypesOnce);
- s_instanceCounter.increment(1);
-}
-
-WebTask::~WebTask() {
- s_instanceCounter.increment(-1);
}
void WebTask::onNetworkError(
- QUrl&& requestUrl,
QNetworkReply::NetworkError errorCode,
- QString&& errorString,
- QByteArray&& errorContent) {
+ const QString& errorString,
+ const WebResponseWithContent& responseWithContent) {
+ DEBUG_ASSERT(m_state == State::Pending);
DEBUG_ASSERT(m_timeoutTimerId == kInvalidTimerId);
- VERIFY_OR_DEBUG_ASSERT(m_state == State::Pending) {
- return;
- }
DEBUG_ASSERT(errorCode != QNetworkReply::NoError);
switch (errorCode) {
case QNetworkReply::OperationCanceledError:
+ // Client-side abort or timeout
m_state = State::Aborted;
break;
case QNetworkReply::TimeoutError:
+ // Network or server-side timeout
m_state = State::TimedOut;
break;
default:
m_state = State::Failed;
}
+ DEBUG_ASSERT(hasTerminated());
+
+ if (m_state == State::Aborted) {
+ emitAborted(responseWithContent.requestUrl());
+ } else {
+ emitNetworkError(
+ errorCode,
+ errorString,
+ responseWithContent);
+ }
+}
+void WebTask::emitNetworkError(
+ QNetworkReply::NetworkError errorCode,
+ const QString& errorString,
+ const WebResponseWithContent& responseWithContent) {
VERIFY_OR_DEBUG_ASSERT(
isSignalFuncConnected(&WebTask::networkError)) {
kLogger.warning()
<< this
- << "Unhandled network error:"
- << requestUrl
+ << "Unhandled network error signal"
<< errorCode
<< errorString
- << errorContent;
+ << responseWithContent;
deleteLater();
return;
}
emit networkError(
- std::move(requestUrl),
errorCode,
- std::move(errorString),
- std::move(errorContent));
-}
-
-void WebTask::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 WebTask::invokeAbort() {
- QMetaObject::invokeMethod(
- this,
-#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
- "slotAbort"
-#else
- [this] {
- this->slotAbort();
- }
-#endif
- );
+ errorString,
+ responseWithContent);
}
void WebTask::slotStart(int timeoutMillis) {
DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this);
- VERIFY_OR_DEBUG_ASSERT(m_state != State::Pending) {
+ if (m_state == State::Pending) {
+ kLogger.warning()
+ << "Task is still busy and cannot be started again";
return;
}
+
+ // Reset state
DEBUG_ASSERT(!m_pendingNetworkReplyWeakPtr);
+ DEBUG_ASSERT(m_timeoutTimerId == kInvalidTimerId);
m_state = State::Idle;
auto* const pNetworkAccessManager = m_networkAccessManagerWeakPtr.data();
VERIFY_OR_DEBUG_ASSERT(pNetworkAccessManager) {
m_state = State::Pending;
onNetworkError(
- QUrl(),
QNetworkReply::NetworkSessionFailedError,
tr("No network access"),
- QByteArray());
+ WebResponseWithContent{});
return;
}
DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(pNetworkAccessManager);
@@ -178,23 +198,31 @@ void WebTask::slotStart(int timeoutMillis) {
kLogger.debug()
<< this
<< "Starting...";
+ m_timer.start();
m_pendingNetworkReplyWeakPtr = doStartNetworkRequest(
pNetworkAccessManager,
timeoutMillis);
// Still idle, because we are in the same thread.
- // The callee is not supposed to abort a request
- // before it has beeen started successfully.
+ // The derived class is not allowed to abort a request
+ // during the callback before it has beeen started
+ // successfully. Instead it should return nullptr
+ // to abort the task immediately.
DEBUG_ASSERT(m_state == State::Idle);
if (!m_pendingNetworkReplyWeakPtr) {
kLogger.debug()
- << "Network task has not been started";
+ << this
+ << "Network request has not been started";
+ m_state = State::Aborted;
+ emitAborted(/*request URL is unknown*/);
return;
}
+
m_state = State::Pending;
DEBUG_ASSERT(m_timeoutTimerId == kInvalidTimerId);
- if (timeoutMillis > 0) {
+ if (timeoutMillis != kNoTimeout) {
+ DEBUG_ASSERT(timeoutMillis > 0);
m_timeoutTimerId = startTimer(timeoutMillis);
DEBUG_ASSERT(m_timeoutTimerId != kInvalidTimerId);
}
@@ -208,49 +236,65 @@ void WebTask::slotStart(int timeoutMillis) {
Qt::UniqueConnection);
}
-void WebTask::abort() {
+void WebTask::slotAbort() {
DEBUG_ASSERT_QOBJECT_THREAD_AFFINITY(this);
if (m_state != State::Pending) {
DEBUG_ASSERT(m_timeoutTimerId == kInvalidTimerId);
+ if (m_state == State::Idle) {
+ kLogger.debug()
+ << this
+ << "Cannot abort idle task";
+ } else {
+ DEBUG_ASSERT(hasTerminated());
+ kLogger.debug()
+ << this
+ << "Cannot abort terminated task";
+ }
return;
}
- auto* const pPendingNetworkReply = m_pendingNetworkReplyWeakPtr.data();
- VERIFY_OR_DEBUG_ASSERT(pPendingNetworkReply) {
- DEBUG_ASSERT(m_timeoutTimerId == kInvalidTimerId);
- return;
- }
- if (m_timeoutTimerId != kInvalidTimerId) {
- killTimer(m_timeoutTimerId);
- m_timeoutTimerId = kInvalidTimerId;
- }
- m_state = State::Aborting;
+
kLogger.debug()
<< this
<< "Aborting...";
- if (pPendingNetworkReply->isRunning()) {
- pPendingNetworkReply->abort();
- doNetworkReplyAborted(pPendingNetworkReply);
- // Suspend and await finished signal
- return;
+
+ if (m_timeoutTimerId != kInvalidTimerId) {
+ killTimer(m_timeoutTimerId);
+ m_timeoutTimerId = kInvalidTimerId;
}
- doNetworkReplyAborted(pPendingNetworkReply);
- m_state = State::Aborted;
- const auto requestUrl = pPendingNetworkReply->request().url();
- VERIFY_OR_DEBUG_ASSERT(
- isSignalFuncConnected(&WebTask::aborted)) {
- kLogger.warning()
+
+ auto* const pPendingNetworkReply = m_pendingNetworkReplyWeakPtr.data();
+ QUrl requestUrl;
+ if (pPendingNetworkReply) {
+ if (pPendingNetworkReply->isRunning()) {
+ kLogger.debug()
+ << this
+ << "Aborting pending network reply after"
+ << m_timer.elapsed().toIntegerMillis()
+ << "ms";
+ pPendingNetworkReply->abort();
+ // Aborting a pending reply will immediately emit a network
+ // error signal that gets handled in this thread before
+ // continuing with the next statements.
+ DEBUG_ASSERT(hasTerminated());
+ DEBUG_ASSERT(!m_pendingNetworkReplyWeakPtr);
+ return;
+ }
+ kLogger.debug()
<< this
- << "Unhandled abort signal"
- << requestUrl;
- deleteLater();
- return;
+ << "Aborted pending network reply after"
+ << m_timer.elapsed().toIntegerMillis()
+ << "ms";
+ // Save the request URL for emitting the signal (see below)
+ requestUrl = pPendingNetworkReply->request().url();
+ // Ensure that the aborted reply is scheduled for deletion when leaving
+ // this scope.
+ const auto pendingNetworkReplyDeleter = ScopedDeleteLater(pPendingNetworkReply);
+ m_pendingNetworkReplyWeakPtr.clear();
+ doNetworkReplyAborted(pPendingNetworkReply);
}
- emit aborted(
- std::move(requestUrl));
-}
-void WebTask::slotAbort() {
- abort();
+ m_state = State::Aborted;
+ emitAborted(requestUrl);
}
void WebTask::timerEvent(QTimerEvent* event) {
@@ -260,23 +304,22 @@ void WebTask::timerEvent(QTimerEvent* event) {
VERIFY_OR_DEBUG_ASSERT(timerId == m_timeoutTimerId) {
return;
}
+
killTimer(m_timeoutTimerId);
m_timeoutTimerId = kInvalidTimerId;
- VERIFY_OR_DEBUG_ASSERT(m_state == State::Pending) {
- return;
- }
- auto* const pPendingNetworkReply = m_pendingNetworkReplyWeakPtr.data();
- VERIFY_OR_DEBUG_ASSERT(pPendingNetworkReply) {
+
+ if (hasTerminated()) {
+ DEBUG_ASSERT(!m_pendingNetworkReplyWeakPtr);
return;
}
- if (pPendingNetworkReply->isFinished()) {
- // Nothing to do
- }
+ DEBUG_ASSERT(m_state == State::Pending);
+
kLogger.info()
<< this
<< "Aborting after timed out";
- DEBUG_ASSERT(pPendingNetworkReply->isRunning());
- pPendingNetworkReply->abort();
+ // Trigger the regular abort workflow after a client-side
+ // timeout occurred
+ slotAbort();
}
void WebTask::slotNetworkReplyFinished() {
@@ -286,7 +329,8 @@ void WebTask::slotNetworkReplyFinished() {
VERIFY_OR_DEBUG_ASSERT(pFinishedNetworkReply) {
return;
}
- const auto deleteFinishedNetworkReply = ScopedDeleteLater(pFinishedNetworkReply);
+ // Ensure that the received reply gets deleted eventually
+ const auto finishedNetworkReplyDeleter = ScopedDeleteLater(pFinishedNetworkReply);
if (kLogger.debugEnabled()) {
if (pFinishedNetworkReply->url() == pFinishedNetworkReply->request().url()) {
@@ -316,28 +360,38 @@ void WebTask::slotNetworkReplyFinished() {
<< "pending =" << pPendingNetworkReply;
return;
}
+ m_pendingNetworkReplyWeakPtr.clear();
- VERIFY_OR_DEBUG_ASSERT(m_state == State::Pending) {
- DEBUG_ASSERT(m_timeoutTimerId == kInvalidTimerId);
- return;
- }
+ DEBUG_ASSERT(m_state == State::Pending);
+ kLogger.debug()
+ << this
+