From c8ff812ab89044890d88779e33f3c6f86c4b8f74 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 25 Jun 2023 12:02:46 -0700 Subject: feat(net): ApiUpload ^& fix unfired `finished` signals Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/net/Upload.cpp | 367 +++++++++++++++++++++++++----------------------- 1 file changed, 189 insertions(+), 178 deletions(-) (limited to 'launcher/net/Upload.cpp') diff --git a/launcher/net/Upload.cpp b/launcher/net/Upload.cpp index 4f9553ed..27759f53 100644 --- a/launcher/net/Upload.cpp +++ b/launcher/net/Upload.cpp @@ -39,218 +39,229 @@ #include "Upload.h" #include -#include "ByteArraySink.h" #include "BuildConfig.h" +#include "ByteArraySink.h" + +#if defined(LAUNCHER_APPLICATION) #include "Application.h" +#endif #include "net/Logging.h" namespace Net { - bool Upload::abort() - { - if (m_reply) { - m_reply->abort(); - } else { - m_state = State::AbortedByUser; - } - return true; +bool Upload::abort() +{ + if (m_reply) { + m_reply->abort(); + } else { + m_state = State::AbortedByUser; } + return true; +} - void Upload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { - setProgress(bytesReceived, bytesTotal); - } +void Upload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) +{ + setProgress(bytesReceived, bytesTotal); +} - void Upload::downloadError(QNetworkReply::NetworkError error) { - if (error == QNetworkReply::OperationCanceledError) { - qCCritical(taskUploadLogC) << getUid().toString() << "Aborted " << m_url.toString(); - m_state = State::AbortedByUser; - } else { - // error happened during download. - qCCritical(taskUploadLogC) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error; - m_state = State::Failed; - } +void Upload::downloadError(QNetworkReply::NetworkError error) +{ + if (error == QNetworkReply::OperationCanceledError) { + qCCritical(taskUploadLogC) << getUid().toString() << "Aborted " << m_url.toString(); + m_state = State::AbortedByUser; + } else { + // error happened during download. + qCCritical(taskUploadLogC) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error; + m_state = State::Failed; } +} - void Upload::sslErrors(const QList &errors) { - int i = 1; - for (const auto& error : errors) { - qCCritical(taskUploadLogC) << getUid().toString() << "Upload" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); - auto cert = error.certificate(); - qCCritical(taskUploadLogC) << getUid().toString() << "Certificate in question:\n" << cert.toText(); - i++; - } +void Upload::sslErrors(const QList& errors) +{ + int i = 1; + for (const auto& error : errors) { + qCCritical(taskUploadLogC) << getUid().toString() << "Upload" << m_url.toString() << "SSL Error #" << i << " : " + << error.errorString(); + auto cert = error.certificate(); + qCCritical(taskUploadLogC) << getUid().toString() << "Certificate in question:\n" << cert.toText(); + i++; } +} - bool Upload::handleRedirect() - { - QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl(); - if (!redirect.isValid()) { - if (!m_reply->hasRawHeader("Location")) { - // no redirect -> it's fine to continue - return false; - } - // there is a Location header, but it's not correct. we need to apply some workarounds... - QByteArray redirectBA = m_reply->rawHeader("Location"); - if (redirectBA.size() == 0) { - // empty, yet present redirect header? WTF? - return false; - } - QString redirectStr = QString::fromUtf8(redirectBA); - - if (redirectStr.startsWith("//")) { - /* - * IF the URL begins with //, we need to insert the URL scheme. - * See: https://bugreports.qt.io/browse/QTBUG-41061 - * See: http://tools.ietf.org/html/rfc3986#section-4.2 - */ - redirectStr = m_reply->url().scheme() + ":" + redirectStr; - } else if (redirectStr.startsWith("/")) { - /* - * IF the URL begins with /, we need to process it as a relative URL - */ - auto url = m_reply->url(); - url.setPath(redirectStr, QUrl::TolerantMode); - redirectStr = url.toString(); - } +bool Upload::handleRedirect() +{ + QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl(); + if (!redirect.isValid()) { + if (!m_reply->hasRawHeader("Location")) { + // no redirect -> it's fine to continue + return false; + } + // there is a Location header, but it's not correct. we need to apply some workarounds... + QByteArray redirectBA = m_reply->rawHeader("Location"); + if (redirectBA.size() == 0) { + // empty, yet present redirect header? WTF? + return false; + } + QString redirectStr = QString::fromUtf8(redirectBA); + if (redirectStr.startsWith("//")) { /* - * Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues. - * FIXME: report Qt bug for this + * IF the URL begins with //, we need to insert the URL scheme. + * See: https://bugreports.qt.io/browse/QTBUG-41061 + * See: http://tools.ietf.org/html/rfc3986#section-4.2 */ - redirect = QUrl(redirectStr, QUrl::TolerantMode); - if (!redirect.isValid()) { - qCWarning(taskUploadLogC) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr; - downloadError(QNetworkReply::ProtocolFailure); - return false; - } - qCDebug(taskUploadLogC) << getUid().toString() << "Fixed location header:" << redirect; - } else { - qCDebug(taskUploadLogC) << getUid().toString() << "Location header:" << redirect; + redirectStr = m_reply->url().scheme() + ":" + redirectStr; + } else if (redirectStr.startsWith("/")) { + /* + * IF the URL begins with /, we need to process it as a relative URL + */ + auto url = m_reply->url(); + url.setPath(redirectStr, QUrl::TolerantMode); + redirectStr = url.toString(); } - m_url = QUrl(redirect.toString()); - qCDebug(taskUploadLogC) << getUid().toString() << "Following redirect to " << m_url.toString(); - startAction(m_network); - return true; + /* + * Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues. + * FIXME: report Qt bug for this + */ + redirect = QUrl(redirectStr, QUrl::TolerantMode); + if (!redirect.isValid()) { + qCWarning(taskUploadLogC) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr; + downloadError(QNetworkReply::ProtocolFailure); + return false; + } + qCDebug(taskUploadLogC) << getUid().toString() << "Fixed location header:" << redirect; + } else { + qCDebug(taskUploadLogC) << getUid().toString() << "Location header:" << redirect; } - void Upload::downloadFinished() { - // handle HTTP redirection first - // very unlikely for post requests, still can happen - if (handleRedirect()) { - qCDebug(taskUploadLogC) << getUid().toString() << "Upload redirected:" << m_url.toString(); - return; - } + m_url = QUrl(redirect.toString()); + qCDebug(taskUploadLogC) << getUid().toString() << "Following redirect to " << m_url.toString(); + startAction(m_network); + return true; +} - // if the download failed before this point ... - if (m_state == State::Succeeded) { - qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed but we are allowed to proceed:" << m_url.toString(); - m_sink->abort(); - m_reply.reset(); - emit succeeded(); - return; - } else if (m_state == State::Failed) { - qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed in previous step:" << m_url.toString(); - m_sink->abort(); - m_reply.reset(); - emit failed(""); - return; - } else if (m_state == State::AbortedByUser) { - qCDebug(taskUploadLogC) << getUid().toString() << "Upload aborted in previous step:" << m_url.toString(); - m_sink->abort(); - m_reply.reset(); - emit aborted(); - return; - } +void Upload::downloadFinished() +{ + // handle HTTP redirection first + // very unlikely for post requests, still can happen + if (handleRedirect()) { + qCDebug(taskUploadLogC) << getUid().toString() << "Upload redirected:" << m_url.toString(); + return; + } - // make sure we got all the remaining data, if any - auto data = m_reply->readAll(); - if (data.size()) { - qCDebug(taskUploadLogC) << getUid().toString() << "Writing extra" << data.size() << "bytes"; - m_state = m_sink->write(data); - } + // if the download failed before this point ... + if (m_state == State::Succeeded) { + qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed but we are allowed to proceed:" << m_url.toString(); + m_sink->abort(); + m_reply.reset(); + emitSucceeded(); + return; + } else if (m_state == State::Failed) { + qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed in previous step:" << m_url.toString(); + m_sink->abort(); + m_reply.reset(); + emitFailed(""); + return; + } else if (m_state == State::AbortedByUser) { + qCDebug(taskUploadLogC) << getUid().toString() << "Upload aborted in previous step:" << m_url.toString(); + m_sink->abort(); + m_reply.reset(); + emitAborted(); + return; + } - // otherwise, finalize the whole graph - m_state = m_sink->finalize(*m_reply.get()); - if (m_state != State::Succeeded) { - qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed to finalize:" << m_url.toString(); - m_sink->abort(); - m_reply.reset(); - emit failed(""); - return; - } + // make sure we got all the remaining data, if any + auto data = m_reply->readAll(); + if (data.size()) { + qCDebug(taskUploadLogC) << getUid().toString() << "Writing extra" << data.size() << "bytes"; + m_state = m_sink->write(data); + } + + // otherwise, finalize the whole graph + m_state = m_sink->finalize(*m_reply.get()); + if (m_state != State::Succeeded) { + qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed to finalize:" << m_url.toString(); + m_sink->abort(); m_reply.reset(); - qCDebug(taskUploadLogC) << getUid().toString() << "Upload succeeded:" << m_url.toString(); - emit succeeded(); + emitFailed(""); + return; } + m_reply.reset(); + qCDebug(taskUploadLogC) << getUid().toString() << "Upload succeeded:" << m_url.toString(); + emitSucceeded(); +} - void Upload::downloadReadyRead() { - if (m_state == State::Running) { - auto data = m_reply->readAll(); - m_state = m_sink->write(data); - } +void Upload::downloadReadyRead() +{ + if (m_state == State::Running) { + auto data = m_reply->readAll(); + m_state = m_sink->write(data); } +} - void Upload::executeTask() { - setStatus(tr("Uploading %1").arg(m_url.toString())); +void Upload::executeTask() +{ + setStatus(tr("Uploading %1").arg(m_url.toString())); - if (m_state == State::AbortedByUser) { - qCWarning(taskUploadLogC) << getUid().toString() << "Attempt to start an aborted Upload:" << m_url.toString(); - emit aborted(); + if (m_state == State::AbortedByUser) { + qCWarning(taskUploadLogC) << getUid().toString() << "Attempt to start an aborted Upload:" << m_url.toString(); + emitAborted(); + return; + } + QNetworkRequest request(m_url); + m_state = m_sink->init(request); + switch (m_state) { + case State::Succeeded: + emitSucceeded(); + qCDebug(taskUploadLogC) << getUid().toString() << "Upload cache hit " << m_url.toString(); return; - } - QNetworkRequest request(m_url); - m_state = m_sink->init(request); - switch (m_state) { - case State::Succeeded: - emitSucceeded(); - qCDebug(taskUploadLogC) << getUid().toString() << "Upload cache hit " << m_url.toString(); - return; - case State::Running: - qCDebug(taskUploadLogC) << getUid().toString() << "Uploading " << m_url.toString(); - break; - case State::Inactive: - case State::Failed: - emitFailed(""); - return; - case State::AbortedByUser: - emitAborted(); - return; - } + case State::Running: + qCDebug(taskUploadLogC) << getUid().toString() << "Uploading " << m_url.toString(); + break; + case State::Inactive: + case State::Failed: + emitFailed(""); + return; + case State::AbortedByUser: + emitAborted(); + return; + } - request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgent().toUtf8()); - // TODO remove duplication - if (APPLICATION->capabilities() & Application::SupportsFlame && request.url().host() == QUrl(BuildConfig.FLAME_BASE_URL).host()) { - request.setRawHeader("x-api-key", APPLICATION->getFlameAPIKey().toUtf8()); - } else if (request.url().host() == QUrl(BuildConfig.MODRINTH_PROD_URL).host() || - request.url().host() == QUrl(BuildConfig.MODRINTH_STAGING_URL).host()) { - QString token = APPLICATION->getModrinthAPIToken(); - if (!token.isNull()) - request.setRawHeader("Authorization", token.toUtf8()); - } +#if defined(LAUNCHER_APPLICATION) + auto user_agent = APPLICATION->getUserAgent(); +#else + auto user_agent = BuildConfig.USER_AGENT; +#endif + request.setHeader(QNetworkRequest::UserAgentHeader, user_agent.toUtf8()); + + for (auto& header_proxy : m_headerProxies) { + header_proxy->writeHeaders(request); + } - //TODO other types of post requests ? - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - QNetworkReply* rep = m_network->post(request, m_post_data); + // TODO other types of post requests ? + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QNetworkReply* rep = m_network->post(request, m_post_data); - m_reply.reset(rep); - connect(rep, &QNetworkReply::downloadProgress, this, &Upload::downloadProgress); - connect(rep, &QNetworkReply::finished, this, &Upload::downloadFinished); -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 - connect(rep, &QNetworkReply::errorOccurred, this, &Upload::downloadError); + m_reply.reset(rep); + connect(rep, &QNetworkReply::downloadProgress, this, &Upload::downloadProgress); + connect(rep, &QNetworkReply::finished, this, &Upload::downloadFinished); +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 + connect(rep, &QNetworkReply::errorOccurred, this, &Upload::downloadError); #else - connect(rep, QOverload::of(&QNetworkReply::error), this, &Upload::downloadError); + connect(rep, QOverload::of(&QNetworkReply::error), this, &Upload::downloadError); #endif - connect(rep, &QNetworkReply::sslErrors, this, &Upload::sslErrors); - connect(rep, &QNetworkReply::readyRead, this, &Upload::downloadReadyRead); - } + connect(rep, &QNetworkReply::sslErrors, this, &Upload::sslErrors); + connect(rep, &QNetworkReply::readyRead, this, &Upload::downloadReadyRead); +} - Upload::Ptr Upload::makeByteArray(QUrl url, QByteArray *output, QByteArray m_post_data) { - auto up = makeShared(); - up->m_url = std::move(url); - up->m_sink.reset(new ByteArraySink(output)); - up->m_post_data = std::move(m_post_data); - return up; - } -} // Net +Upload::Ptr Upload::makeByteArray(QUrl url, QByteArray* output, QByteArray m_post_data) +{ + auto up = makeShared(); + up->m_url = std::move(url); + up->m_sink.reset(new ByteArraySink(output)); + up->m_post_data = std::move(m_post_data); + return up; +} +} // namespace Net -- cgit From c49ee8785792142b67d6c431d1913a4d7d2fa936 Mon Sep 17 00:00:00 2001 From: seth Date: Sun, 25 Jun 2023 18:49:50 -0400 Subject: fix(net): fix emit signals in download & upload task Signed-off-by: seth --- launcher/net/Download.cpp | 12 ++++++------ launcher/net/Upload.cpp | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) (limited to 'launcher/net/Upload.cpp') diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index c8645213..40d8a5bb 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -117,7 +117,7 @@ void Download::executeTask() switch (m_state) { case State::Succeeded: qCDebug(taskDownloadLogC) << getUid().toString() << "Download cache hit " << m_url.toString(); - emitSucceeded(); + emit succeeded(); return; case State::Running: qCDebug(taskDownloadLogC) << getUid().toString() << "Downloading " << m_url.toString(); @@ -294,19 +294,19 @@ void Download::downloadFinished() qCDebug(taskDownloadLogC) << getUid().toString() << "Download failed but we are allowed to proceed:" << m_url.toString(); m_sink->abort(); m_reply.reset(); - emitSucceeded(); + emit succeeded(); return; } else if (m_state == State::Failed) { qCDebug(taskDownloadLogC) << getUid().toString() << "Download failed in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); - emitFailed(""); + emit failed(""); return; } else if (m_state == State::AbortedByUser) { qCDebug(taskDownloadLogC) << getUid().toString() << "Download aborted in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); - emitAborted(); + emit aborted(); return; } @@ -323,13 +323,13 @@ void Download::downloadFinished() qCDebug(taskDownloadLogC) << getUid().toString() << "Download failed to finalize:" << m_url.toString(); m_sink->abort(); m_reply.reset(); - emitFailed(""); + emit failed(""); return; } m_reply.reset(); qCDebug(taskDownloadLogC) << getUid().toString() << "Download succeeded:" << m_url.toString(); - emitSucceeded(); + emit succeeded(); } void Download::downloadReadyRead() diff --git a/launcher/net/Upload.cpp b/launcher/net/Upload.cpp index 8881b16a..0688c5a8 100644 --- a/launcher/net/Upload.cpp +++ b/launcher/net/Upload.cpp @@ -157,19 +157,19 @@ void Upload::downloadFinished() qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed but we are allowed to proceed:" << m_url.toString(); m_sink->abort(); m_reply.reset(); - emitSucceeded(); + emit succeeded(); return; } else if (m_state == State::Failed) { qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); - emitFailed(""); + emit failed(""); return; } else if (m_state == State::AbortedByUser) { qCDebug(taskUploadLogC) << getUid().toString() << "Upload aborted in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); - emitAborted(); + emit aborted(); return; } @@ -186,12 +186,12 @@ void Upload::downloadFinished() qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed to finalize:" << m_url.toString(); m_sink->abort(); m_reply.reset(); - emitFailed(""); + emit failed(""); return; } m_reply.reset(); qCDebug(taskUploadLogC) << getUid().toString() << "Upload succeeded:" << m_url.toString(); - emitSucceeded(); + emit succeeded(); } void Upload::downloadReadyRead() @@ -208,7 +208,7 @@ void Upload::executeTask() if (m_state == State::AbortedByUser) { qCWarning(taskUploadLogC) << getUid().toString() << "Attempt to start an aborted Upload:" << m_url.toString(); - emitAborted(); + emit aborted(); return; } QNetworkRequest request(m_url); -- cgit From b142407b218383992f854486cabebb766e83fa93 Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Sun, 25 Jun 2023 16:13:26 -0700 Subject: fix: ensure `finished` signal is emited Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com> --- launcher/net/Download.cpp | 6 ++++++ launcher/net/Upload.cpp | 6 ++++++ 2 files changed, 12 insertions(+) (limited to 'launcher/net/Upload.cpp') diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index 40d8a5bb..c0828185 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -118,6 +118,7 @@ void Download::executeTask() case State::Succeeded: qCDebug(taskDownloadLogC) << getUid().toString() << "Download cache hit " << m_url.toString(); emit succeeded(); + emit finished(); return; case State::Running: qCDebug(taskDownloadLogC) << getUid().toString() << "Downloading " << m_url.toString(); @@ -295,18 +296,21 @@ void Download::downloadFinished() m_sink->abort(); m_reply.reset(); emit succeeded(); + emit finished(); return; } else if (m_state == State::Failed) { qCDebug(taskDownloadLogC) << getUid().toString() << "Download failed in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit failed(""); + emit finished(); return; } else if (m_state == State::AbortedByUser) { qCDebug(taskDownloadLogC) << getUid().toString() << "Download aborted in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit aborted(); + emit finished(); return; } @@ -324,12 +328,14 @@ void Download::downloadFinished() m_sink->abort(); m_reply.reset(); emit failed(""); + emit finished(); return; } m_reply.reset(); qCDebug(taskDownloadLogC) << getUid().toString() << "Download succeeded:" << m_url.toString(); emit succeeded(); + emit finished(); } void Download::downloadReadyRead() diff --git a/launcher/net/Upload.cpp b/launcher/net/Upload.cpp index 0688c5a8..d9115ede 100644 --- a/launcher/net/Upload.cpp +++ b/launcher/net/Upload.cpp @@ -158,18 +158,21 @@ void Upload::downloadFinished() m_sink->abort(); m_reply.reset(); emit succeeded(); + emit finished(); return; } else if (m_state == State::Failed) { qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit failed(""); + emit finished(); return; } else if (m_state == State::AbortedByUser) { qCDebug(taskUploadLogC) << getUid().toString() << "Upload aborted in previous step:" << m_url.toString(); m_sink->abort(); m_reply.reset(); emit aborted(); + emit finished(); return; } @@ -187,11 +190,13 @@ void Upload::downloadFinished() m_sink->abort(); m_reply.reset(); emit failed(""); + emit finished(); return; } m_reply.reset(); qCDebug(taskUploadLogC) << getUid().toString() << "Upload succeeded:" << m_url.toString(); emit succeeded(); + emit finished(); } void Upload::downloadReadyRead() @@ -209,6 +214,7 @@ void Upload::executeTask() if (m_state == State::AbortedByUser) { qCWarning(taskUploadLogC) << getUid().toString() << "Attempt to start an aborted Upload:" << m_url.toString(); emit aborted(); + emit finished(); return; } QNetworkRequest request(m_url); -- cgit From 385babb4583a10a6c669c80b0f1b75799fa85049 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 27 Jun 2023 01:01:02 +0300 Subject: Simplify Upload and Download Signed-off-by: Trial97 --- launcher/CMakeLists.txt | 2 + launcher/net/ApiDownload.cpp | 3 + launcher/net/ApiUpload.cpp | 11 +- launcher/net/Download.cpp | 272 +---------------------------------- launcher/net/Download.h | 34 +---- launcher/net/NetRequest.cpp | 327 +++++++++++++++++++++++++++++++++++++++++++ launcher/net/NetRequest.h | 97 +++++++++++++ launcher/net/Upload.cpp | 216 +--------------------------- launcher/net/Upload.h | 22 +-- 9 files changed, 451 insertions(+), 533 deletions(-) create mode 100644 launcher/net/NetRequest.cpp create mode 100644 launcher/net/NetRequest.h (limited to 'launcher/net/Upload.cpp') diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 20372dbc..df8489e5 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -143,6 +143,8 @@ set(NET_SOURCES net/ApiDownload.cpp net/ApiUpload.cpp net/ApiUpload.h + net/NetRequest.cpp + net/NetRequest.h ) # Game launch logic diff --git a/launcher/net/ApiDownload.cpp b/launcher/net/ApiDownload.cpp index aaa8ff65..5dfaa51d 100644 --- a/launcher/net/ApiDownload.cpp +++ b/launcher/net/ApiDownload.cpp @@ -28,6 +28,7 @@ namespace Net { auto ApiDownload::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Download::Ptr { auto dl = makeShared(); + dl->logCat = taskDownloadLogC; dl->m_url = url; dl->setObjectName(QString("CACHE:") + url.toString()); dl->m_options = options; @@ -40,6 +41,7 @@ auto ApiDownload::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> D auto ApiDownload::makeByteArray(QUrl url, std::shared_ptr output, Options options) -> Download::Ptr { auto dl = makeShared(); + dl->logCat = taskDownloadLogC; dl->m_url = url; dl->setObjectName(QString("BYTES:") + url.toString()); dl->m_options = options; @@ -50,6 +52,7 @@ auto ApiDownload::makeByteArray(QUrl url, std::shared_ptr output, Op auto ApiDownload::makeFile(QUrl url, QString path, Options options) -> Download::Ptr { auto dl = makeShared(); + dl->logCat = taskDownloadLogC; dl->m_url = url; dl->setObjectName(QString("FILE:") + url.toString()); dl->m_options = options; diff --git a/launcher/net/ApiUpload.cpp b/launcher/net/ApiUpload.cpp index 96ebc49c..ec4f14af 100644 --- a/launcher/net/ApiUpload.cpp +++ b/launcher/net/ApiUpload.cpp @@ -27,11 +27,12 @@ namespace Net { Upload::Ptr ApiUpload::makeByteArray(QUrl url, std::shared_ptr output, QByteArray m_post_data) { - auto up = makeShared(); - up->m_url = std::move(url); - up->m_sink.reset(new ByteArraySink(output)); - up->m_post_data = std::move(m_post_data); - return up; + auto up = makeShared(); + up->logCat = taskUploadLogC; + up->m_url = std::move(url); + up->m_sink.reset(new ByteArraySink(output)); + up->m_post_data = std::move(m_post_data); + return up; } void ApiUpload::init() diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index c0828185..dc316878 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -65,6 +65,7 @@ namespace Net { auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Download::Ptr { auto dl = makeShared(); + dl->logCat = taskDownloadLogC; dl->m_url = url; dl->setObjectName(QString("CACHE:") + url.toString()); dl->m_options = options; @@ -78,6 +79,7 @@ auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Down auto Download::makeByteArray(QUrl url, std::shared_ptr output, Options options) -> Download::Ptr { auto dl = makeShared(); + dl->logCat = taskDownloadLogC; dl->m_url = url; dl->setObjectName(QString("BYTES:") + url.toString()); dl->m_options = options; @@ -88,6 +90,7 @@ auto Download::makeByteArray(QUrl url, std::shared_ptr output, Optio auto Download::makeFile(QUrl url, QString path, Options options) -> Download::Ptr { auto dl = makeShared(); + dl->logCat = taskDownloadLogC; dl->m_url = url; dl->setObjectName(QString("FILE:") + url.toString()); dl->m_options = options; @@ -95,271 +98,8 @@ auto Download::makeFile(QUrl url, QString path, Options options) -> Download::Pt return dl; } -void Download::addValidator(Validator* v) +QNetworkReply* Download::getReply(QNetworkRequest& request) { - m_sink->addValidator(v); -} - -void Download::executeTask() -{ - init(); - - setStatus(tr("Downloading %1").arg(StringUtils::truncateUrlHumanFriendly(m_url, 80))); - - if (getState() == Task::State::AbortedByUser) { - qCWarning(taskDownloadLogC) << getUid().toString() << "Attempt to start an aborted Download:" << m_url.toString(); - emitAborted(); - return; - } - - QNetworkRequest request(m_url); - m_state = m_sink->init(request); - switch (m_state) { - case State::Succeeded: - qCDebug(taskDownloadLogC) << getUid().toString() << "Download cache hit " << m_url.toString(); - emit succeeded(); - emit finished(); - return; - case State::Running: - qCDebug(taskDownloadLogC) << getUid().toString() << "Downloading " << m_url.toString(); - break; - case State::Inactive: - case State::Failed: - emitFailed(); - return; - case State::AbortedByUser: - emitAborted(); - return; - } - -#if defined (LAUNCHER_APPLICATION) - auto user_agent = APPLICATION->getUserAgent(); -#else - auto user_agent = BuildConfig.USER_AGENT; -#endif - - request.setHeader(QNetworkRequest::UserAgentHeader, user_agent.toUtf8()); - for ( auto& header_proxy : m_headerProxies ) { - - header_proxy->writeHeaders(request); - } - // TODO remove duplication - - -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) - request.setTransferTimeout(); -#endif - - m_last_progress_time = m_clock.now(); - m_last_progress_bytes = 0; - - QNetworkReply* rep = m_network->get(request); - m_reply.reset(rep); - connect(rep, &QNetworkReply::downloadProgress, this, &Download::downloadProgress); - connect(rep, &QNetworkReply::finished, this, &Download::downloadFinished); -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 - connect(rep, &QNetworkReply::errorOccurred, this, &Download::downloadError); -#else - connect(rep, QOverload::of(&QNetworkReply::error), this, &Download::downloadError); -#endif - connect(rep, &QNetworkReply::sslErrors, this, &Download::sslErrors); - connect(rep, &QNetworkReply::readyRead, this, &Download::downloadReadyRead); -} - -void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) -{ - auto now = m_clock.now(); - auto elapsed = now - m_last_progress_time; - - // use milliseconds for speed precision - auto elapsed_ms = std::chrono::duration_cast(elapsed); - auto bytes_received_since = bytesReceived - m_last_progress_bytes; - auto dl_speed_bps = (double)bytes_received_since / elapsed_ms.count() * 1000; - auto remaining_time_s = (bytesTotal - bytesReceived) / dl_speed_bps; - - //: Current amount of bytes downloaded, out of the total amount of bytes in the download - QString dl_progress = - tr("%1 / %2").arg(StringUtils::humanReadableFileSize(bytesReceived)).arg(StringUtils::humanReadableFileSize(bytesTotal)); - - QString dl_speed_str; - if (elapsed_ms.count() > 0) { - auto str_eta = bytesTotal > 0 ? Time::humanReadableDuration(remaining_time_s) : tr("unknown"); - //: Download speed, in bytes per second (remaining download time in parenthesis) - dl_speed_str = - tr("%1 /s (%2)").arg(StringUtils::humanReadableFileSize(dl_speed_bps)).arg(str_eta); - } else { - //: Download speed at 0 bytes per second - dl_speed_str = tr("0 B/s"); - } - - setDetails(dl_progress + "\n" + dl_speed_str); - - setProgress(bytesReceived, bytesTotal); -} - -void Download::downloadError(QNetworkReply::NetworkError error) -{ - if (error == QNetworkReply::OperationCanceledError) { - qCCritical(taskDownloadLogC) << getUid().toString() << "Aborted " << m_url.toString(); - m_state = State::AbortedByUser; - } else { - if (m_options & Option::AcceptLocalFiles) { - if (m_sink->hasLocalData()) { - m_state = State::Succeeded; - return; - } - } - // error happened during download. - qCCritical(taskDownloadLogC) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error; - m_state = State::Failed; - } -} - -void Download::sslErrors(const QList& errors) -{ - int i = 1; - for (auto error : errors) { - qCCritical(taskDownloadLogC) << getUid().toString() << "Download" << m_url.toString() << "SSL Error #" << i << " : " - << error.errorString(); - auto cert = error.certificate(); - qCCritical(taskDownloadLogC) << getUid().toString() << "Certificate in question:\n" << cert.toText(); - i++; - } -} - -auto Download::handleRedirect() -> bool -{ - QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl(); - if (!redirect.isValid()) { - if (!m_reply->hasRawHeader("Location")) { - // no redirect -> it's fine to continue - return false; - } - // there is a Location header, but it's not correct. we need to apply some workarounds... - QByteArray redirectBA = m_reply->rawHeader("Location"); - if (redirectBA.size() == 0) { - // empty, yet present redirect header? WTF? - return false; - } - QString redirectStr = QString::fromUtf8(redirectBA); - - if (redirectStr.startsWith("//")) { - /* - * IF the URL begins with //, we need to insert the URL scheme. - * See: https://bugreports.qt.io/browse/QTBUG-41061 - * See: http://tools.ietf.org/html/rfc3986#section-4.2 - */ - redirectStr = m_reply->url().scheme() + ":" + redirectStr; - } else if (redirectStr.startsWith("/")) { - /* - * IF the URL begins with /, we need to process it as a relative URL - */ - auto url = m_reply->url(); - url.setPath(redirectStr, QUrl::TolerantMode); - redirectStr = url.toString(); - } - - /* - * Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues. - * FIXME: report Qt bug for this - */ - redirect = QUrl(redirectStr, QUrl::TolerantMode); - if (!redirect.isValid()) { - qCWarning(taskDownloadLogC) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr; - downloadError(QNetworkReply::ProtocolFailure); - return false; - } - qCDebug(taskDownloadLogC) << getUid().toString() << "Fixed location header:" << redirect; - } else { - qCDebug(taskDownloadLogC) << getUid().toString() << "Location header:" << redirect; - } - - m_url = QUrl(redirect.toString()); - qCDebug(taskDownloadLogC) << getUid().toString() << "Following redirect to " << m_url.toString(); - startAction(m_network); - - return true; -} - -void Download::downloadFinished() -{ - // handle HTTP redirection first - if (handleRedirect()) { - qCDebug(taskDownloadLogC) << getUid().toString() << "Download redirected:" << m_url.toString(); - return; - } - - // if the download failed before this point ... - if (m_state == State::Succeeded) // pretend to succeed so we continue processing :) - { - qCDebug(taskDownloadLogC) << getUid().toString() << "Download failed but we are allowed to proceed:" << m_url.toString(); - m_sink->abort(); - m_reply.reset(); - emit succeeded(); - emit finished(); - return; - } else if (m_state == State::Failed) { - qCDebug(taskDownloadLogC) << getUid().toString() << "Download failed in previous step:" << m_url.toString(); - m_sink->abort(); - m_reply.reset(); - emit failed(""); - emit finished(); - return; - } else if (m_state == State::AbortedByUser) { - qCDebug(taskDownloadLogC) << getUid().toString() << "Download aborted in previous step:" << m_url.toString(); - m_sink->abort(); - m_reply.reset(); - emit aborted(); - emit finished(); - return; - } - - // make sure we got all the remaining data, if any - auto data = m_reply->readAll(); - if (data.size()) { - qCDebug(taskDownloadLogC) << getUid().toString() << "Writing extra" << data.size() << "bytes"; - m_state = m_sink->write(data); - } - - // otherwise, finalize the whole graph - m_state = m_sink->finalize(*m_reply.get()); - if (m_state != State::Succeeded) { - qCDebug(taskDownloadLogC) << getUid().toString() << "Download failed to finalize:" << m_url.toString(); - m_sink->abort(); - m_reply.reset(); - emit failed(""); - emit finished(); - return; - } - - m_reply.reset(); - qCDebug(taskDownloadLogC) << getUid().toString() << "Download succeeded:" << m_url.toString(); - emit succeeded(); - emit finished(); -} - -void Download::downloadReadyRead() -{ - if (m_state == State::Running) { - auto data = m_reply->readAll(); - m_state = m_sink->write(data); - if (m_state == State::Failed) { - qCCritical(taskDownloadLogC) << getUid().toString() << "Failed to process response chunk"; - } - // qDebug() << "Download" << m_url.toString() << "gained" << data.size() << "bytes"; - } else { - qCCritical(taskDownloadLogC) << getUid().toString() << "Cannot write download data! illegal status " << m_status; - } -} - -} // namespace Net - -auto Net::Download::abort() -> bool -{ - if (m_reply) { - m_reply->abort(); - } else { - m_state = State::AbortedByUser; - } - return true; + return m_network->get(request); } +} // namespace Net \ No newline at end of file diff --git a/launcher/net/Download.h b/launcher/net/Download.h index 2fa318ef..113875ee 100644 --- a/launcher/net/Download.h +++ b/launcher/net/Download.h @@ -46,15 +46,14 @@ #include "Validator.h" #include "QObjectPtr.h" +#include "net/NetRequest.h" namespace Net { -class Download : public NetAction { +class Download : public NetRequest { Q_OBJECT public: using Ptr = shared_qobject_ptr; - enum class Option { NoOptions = 0, AcceptLocalFiles = 1, MakeEternal = 2 }; - Q_DECLARE_FLAGS(Options, Option) public: ~Download() override = default; @@ -66,34 +65,9 @@ class Download : public NetAction { static auto makeByteArray(QUrl url, std::shared_ptr output, Options options = Option::NoOptions) -> Download::Ptr; static auto makeFile(QUrl url, QString path, Options options = Option::NoOptions) -> Download::Ptr; - void init() override {}; - - public: - void addValidator(Validator* v); - auto abort() -> bool override; - auto canAbort() const -> bool override { return true; }; - - private: - auto handleRedirect() -> bool; - - protected slots: - void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override; - void downloadError(QNetworkReply::NetworkError error) override; - void sslErrors(const QList& errors) override; - void downloadFinished() override; - void downloadReadyRead() override; - - public slots: - void executeTask() override; + void init() override{}; protected: - std::unique_ptr m_sink; - Options m_options; - - std::chrono::steady_clock m_clock; - std::chrono::time_point m_last_progress_time; - qint64 m_last_progress_bytes; + virtual QNetworkReply* getReply(QNetworkRequest&) override; }; } // namespace Net - -Q_DECLARE_OPERATORS_FOR_FLAGS(Net::Download::Options) diff --git a/launcher/net/NetRequest.cpp b/launcher/net/NetRequest.cpp new file mode 100644 index 00000000..3f7cdbaa --- /dev/null +++ b/launcher/net/NetRequest.cpp @@ -0,0 +1,327 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2022 flowln + * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2023 TheKodeToad + * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> + * + * 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, version 3. + * + * 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, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "NetRequest.h" +#include + +#include +#include +#include + +#include "ByteArraySink.h" +#include "ChecksumValidator.h" +#include "MetaCacheSink.h" + +#if defined(LAUNCHER_APPLICATION) +#include "Application.h" +#endif + +#include "BuildConfig.h" + +#include "net/Logging.h" +#include "net/NetAction.h" + +#include "MMCTime.h" +#include "StringUtils.h" + +namespace Net { + +void NetRequest::addValidator(Validator* v) +{ + m_sink->addValidator(v); +} + +void NetRequest::executeTask() +{ + init(); + + setStatus(tr("Downloading %1").arg(StringUtils::truncateUrlHumanFriendly(m_url, 80))); + + if (getState() == Task::State::AbortedByUser) { + qCWarning(logCat) << getUid().toString() << "Attempt to start an aborted Request:" << m_url.toString(); + emitAborted(); + return; + } + + QNetworkRequest request(m_url); + m_state = m_sink->init(request); + switch (m_state) { + case State::Succeeded: + qCDebug(logCat) << getUid().toString() << "Request cache hit " << m_url.toString(); + emit succeeded(); + emit finished(); + return; + case State::Running: + qCDebug(logCat) << getUid().toString() << "Runninng " << m_url.toString(); + break; + case State::Inactive: + case State::Failed: + emitFailed(); + return; + case State::AbortedByUser: + emitAborted(); + return; + } + +#if defined(LAUNCHER_APPLICATION) + auto user_agent = APPLICATION->getUserAgent(); +#else + auto user_agent = BuildConfig.USER_AGENT; +#endif + + request.setHeader(QNetworkRequest::UserAgentHeader, user_agent.toUtf8()); + for (auto& header_proxy : m_headerProxies) { + header_proxy->writeHeaders(request); + } + // TODO remove duplication + +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + request.setTransferTimeout(); +#endif + + m_last_progress_time = m_clock.now(); + m_last_progress_bytes = 0; + + QNetworkReply* rep = getReply(request); + m_reply.reset(rep); + connect(rep, &QNetworkReply::downloadProgress, this, &NetRequest::downloadProgress); + connect(rep, &QNetworkReply::finished, this, &NetRequest::downloadFinished); +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 + connect(rep, &QNetworkReply::errorOccurred, this, &NetRequest::downloadError); +#else + connect(rep, QOverload::of(&QNetworkReply::error), this, &NetRequest::downloadError); +#endif + connect(rep, &QNetworkReply::sslErrors, this, &NetRequest::sslErrors); + connect(rep, &QNetworkReply::readyRead, this, &NetRequest::downloadReadyRead); +} + +void NetRequest::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) +{ + auto now = m_clock.now(); + auto elapsed = now - m_last_progress_time; + + // use milliseconds for speed precision + auto elapsed_ms = std::chrono::duration_cast(elapsed); + auto bytes_received_since = bytesReceived - m_last_progress_bytes; + auto dl_speed_bps = (double)bytes_received_since / elapsed_ms.count() * 1000; + auto remaining_time_s = (bytesTotal - bytesReceived) / dl_speed_bps; + + //: Current amount of bytes downloaded, out of the total amount of bytes in the download + QString dl_progress = + tr("%1 / %2").arg(StringUtils::humanReadableFileSize(bytesReceived)).arg(StringUtils::humanReadableFileSize(bytesTotal)); + + QString dl_speed_str; + if (elapsed_ms.count() > 0) { + auto str_eta = bytesTotal > 0 ? Time::humanReadableDuration(remaining_time_s) : tr("unknown"); + //: Download speed, in bytes per second (remaining download time in parenthesis) + dl_speed_str = tr("%1 /s (%2)").arg(StringUtils::humanReadableFileSize(dl_speed_bps)).arg(str_eta); + } else { + //: Download speed at 0 bytes per second + dl_speed_str = tr("0 B/s"); + } + + setDetails(dl_progress + "\n" + dl_speed_str); + + setProgress(bytesReceived, bytesTotal); +} + +void NetRequest::downloadError(QNetworkReply::NetworkError error) +{ + if (error == QNetworkReply::OperationCanceledError) { + qCCritical(logCat) << getUid().toString() << "Aborted " << m_url.toString(); + m_state = State::AbortedByUser; + } else { + if (m_options & Option::AcceptLocalFiles) { + if (m_sink->hasLocalData()) { + m_state = State::Succeeded; + return; + } + } + // error happened during download. + qCCritical(logCat) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error; + m_state = State::Failed; + } +} + +void NetRequest::sslErrors(const QList& errors) +{ + int i = 1; + for (auto error : errors) { + qCCritical(logCat) << getUid().toString() << "Request" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); + auto cert = error.certificate(); + qCCritical(logCat) << getUid().toString() << "Certificate in question:\n" << cert.toText(); + i++; + } +} + +auto NetRequest::handleRedirect() -> bool +{ + QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl(); + if (!redirect.isValid()) { + if (!m_reply->hasRawHeader("Location")) { + // no redirect -> it's fine to continue + return false; + } + // there is a Location header, but it's not correct. we need to apply some workarounds... + QByteArray redirectBA = m_reply->rawHeader("Location"); + if (redirectBA.size() == 0) { + // empty, yet present redirect header? WTF? + return false; + } + QString redirectStr = QString::fromUtf8(redirectBA); + + if (redirectStr.startsWith("//")) { + /* + * IF the URL begins with //, we need to insert the URL scheme. + * See: https://bugreports.qt.io/browse/QTBUG-41061 + * See: http://tools.ietf.org/html/rfc3986#section-4.2 + */ + redirectStr = m_reply->url().scheme() + ":" + redirectStr; + } else if (redirectStr.startsWith("/")) { + /* + * IF the URL begins with /, we need to process it as a relative URL + */ + auto url = m_reply->url(); + url.setPath(redirectStr, QUrl::TolerantMode); + redirectStr = url.toString(); + } + + /* + * Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues. + * FIXME: report Qt bug for this + */ + redirect = QUrl(redirectStr, QUrl::TolerantMode); + if (!redirect.isValid()) { + qCWarning(logCat) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr; + downloadError(QNetworkReply::ProtocolFailure); + return false; + } + qCDebug(logCat) << getUid().toString() << "Fixed location header:" << redirect; + } else { + qCDebug(logCat) << getUid().toString() << "Location header:" << redirect; + } + + m_url = QUrl(redirect.toString()); + qCDebug(logCat) << getUid().toString() << "Following redirect to " << m_url.toString(); + startAction(m_network); + + return true; +} + +void NetRequest::downloadFinished() +{ + // handle HTTP redirection first + if (handleRedirect()) { + qCDebug(logCat) << getUid().toString() << "Request redirected:" << m_url.toString(); + return; + } + + // if the download failed before this point ... + if (m_state == State::Succeeded) // pretend to succeed so we continue processing :) + { + qCDebug(logCat) << getUid().toString() << "Request failed but we are allowed to proceed:" << m_url.toString(); + m_sink->abort(); + m_reply.reset(); + emit succeeded(); + emit finished(); + return; + } else if (m_state == State::Failed) { + qCDebug(logCat) << getUid().toString() << "Request failed in previous step:" << m_url.toString(); + m_sink->abort(); + m_reply.reset(); + emit failed(""); + emit finished(); + return; + } else if (m_state == State::AbortedByUser) { + qCDebug(logCat) << getUid().toString() << "Request aborted in previous step:" << m_url.toString(); + m_sink->abort(); + m_reply.reset(); + emit aborted(); + emit finished(); + return; + } + + // make sure we got all the remaining data, if any + auto data = m_reply->readAll(); + if (data.size()) { + qCDebug(logCat) << getUid().toString() << "Writing extra" << data.size() << "bytes"; + m_state = m_sink->write(data); + } + + // otherwise, finalize the whole graph + m_state = m_sink->finalize(*m_reply.get()); + if (m_state != State::Succeeded) { + qCDebug(logCat) << getUid().toString() << "Request failed to finalize:" << m_url.toString(); + m_sink->abort(); + m_reply.reset(); + emit failed(""); + emit finished(); + return; + } + + m_reply.reset(); + qCDebug(logCat) << getUid().toString() << "Request succeeded:" << m_url.toString(); + emit succeeded(); + emit finished(); +} + +void NetRequest::downloadReadyRead() +{ + if (m_state == State::Running) { + auto data = m_reply->readAll(); + m_state = m_sink->write(data); + if (m_state == State::Failed) { + qCCritical(logCat) << getUid().toString() << "Failed to process response chunk"; + } + // qDebug() << "Request" << m_url.toString() << "gained" << data.size() << "bytes"; + } else { + qCCritical(logCat) << getUid().toString() << "Cannot write download data! illegal status " << m_status; + } +} + +} // namespace Net + +auto Net::NetRequest::abort() -> bool +{ + if (m_reply) { + m_reply->abort(); + } else { + m_state = State::AbortedByUser; + } + return true; +} diff --git a/launcher/net/NetRequest.h b/launcher/net/NetRequest.h new file mode 100644 index 00000000..0105a60c --- /dev/null +++ b/launcher/net/NetRequest.h @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2022 flowln + * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> + * + * 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, version 3. + * + * 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, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include "NetAction.h" +#include "Sink.h" +#include "Validator.h" + +#include "QObjectPtr.h" +#include "net/Logging.h" + +namespace Net { +class NetRequest : public NetAction { + Q_OBJECT + + public: + using Ptr = shared_qobject_ptr; + enum class Option { NoOptions = 0, AcceptLocalFiles = 1, MakeEternal = 2 }; + Q_DECLARE_FLAGS(Options, Option) + + public: + ~NetRequest() override = default; + + void init() override{}; + + public: + void addValidator(Validator* v); + auto abort() -> bool override; + auto canAbort() const -> bool override { return true; }; + + private: + auto handleRedirect() -> bool; + virtual QNetworkReply* getReply(QNetworkRequest&) = 0; + + protected slots: + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override; + void downloadError(QNetworkReply::NetworkError error) override; + void sslErrors(const QList& errors) override; + void downloadFinished() override; + void downloadReadyRead() override; + + public slots: + void executeTask() override; + + protected: + std::unique_ptr m_sink; + Options m_options; + + typedef const QLoggingCategory& (*logCatFunc)(); + logCatFunc logCat = taskUploadLogC; + + std::chrono::steady_clock m_clock; + std::chrono::time_point m_last_progress_time; + qint64 m_last_progress_bytes; +}; +} // namespace Net + +Q_DECLARE_OPERATORS_FOR_FLAGS(Net::NetRequest::Options) diff --git a/launcher/net/Upload.cpp b/launcher/net/Upload.cpp index d9115ede..245a52ae 100644 --- a/launcher/net/Upload.cpp +++ b/launcher/net/Upload.cpp @@ -40,232 +40,22 @@ #include #include -#include "BuildConfig.h" #include "ByteArraySink.h" -#if defined(LAUNCHER_APPLICATION) -#include "Application.h" -#endif - #include "net/Logging.h" namespace Net { -bool Upload::abort() +QNetworkReply* Upload::getReply(QNetworkRequest& request) { - if (m_reply) { - m_reply->abort(); - } else { - m_state = State::AbortedByUser; - } - return true; -} - -void Upload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) -{ - setProgress(bytesReceived, bytesTotal); -} - -void Upload::downloadError(QNetworkReply::NetworkError error) -{ - if (error == QNetworkReply::OperationCanceledError) { - qCCritical(taskUploadLogC) << getUid().toString() << "Aborted " << m_url.toString(); - m_state = State::AbortedByUser; - } else { - // error happened during download. - qCCritical(taskUploadLogC) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error; - m_state = State::Failed; - } -} - -void Upload::sslErrors(const QList& errors) -{ - int i = 1; - for (const auto& error : errors) { - qCCritical(taskUploadLogC) << getUid().toString() << "Upload" << m_url.toString() << "SSL Error #" << i << " : " - << error.errorString(); - auto cert = error.certificate(); - qCCritical(taskUploadLogC) << getUid().toString() << "Certificate in question:\n" << cert.toText(); - i++; - } -} - -bool Upload::handleRedirect() -{ - QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl(); - if (!redirect.isValid()) { - if (!m_reply->hasRawHeader("Location")) { - // no redirect -> it's fine to continue - return false; - } - // there is a Location header, but it's not correct. we need to apply some workarounds... - QByteArray redirectBA = m_reply->rawHeader("Location"); - if (redirectBA.size() == 0) { - // empty, yet present redirect header? WTF? - return false; - } - QString redirectStr = QString::fromUtf8(redirectBA); - - if (redirectStr.startsWith("//")) { - /* - * IF the URL begins with //, we need to insert the URL scheme. - * See: https://bugreports.qt.io/browse/QTBUG-41061 - * See: http://tools.ietf.org/html/rfc3986#section-4.2 - */ - redirectStr = m_reply->url().scheme() + ":" + redirectStr; - } else if (redirectStr.startsWith("/")) { - /* - * IF the URL begins with /, we need to process it as a relative URL - */ - auto url = m_reply->url(); - url.setPath(redirectStr, QUrl::TolerantMode); - redirectStr = url.toString(); - } - - /* - * Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues. - * FIXME: report Qt bug for this - */ - redirect = QUrl(redirectStr, QUrl::TolerantMode); - if (!redirect.isValid()) { - qCWarning(taskUploadLogC) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr; - downloadError(QNetworkReply::ProtocolFailure); - return false; - } - qCDebug(taskUploadLogC) << getUid().toString() << "Fixed location header:" << redirect; - } else { - qCDebug(taskUploadLogC) << getUid().toString() << "Location header:" << redirect; - } - - m_url = QUrl(redirect.toString()); - qCDebug(taskUploadLogC) << getUid().toString() << "Following redirect to " << m_url.toString(); - startAction(m_network); - return true; -} - -void Upload::downloadFinished() -{ - // handle HTTP redirection first - // very unlikely for post requests, still can happen - if (handleRedirect()) { - qCDebug(taskUploadLogC) << getUid().toString() << "Upload redirected:" << m_url.toString(); - return; - } - - // if the download failed before this point ... - if (m_state == State::Succeeded) { - qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed but we are allowed to proceed:" << m_url.toString(); - m_sink->abort(); - m_reply.reset(); - emit succeeded(); - emit finished(); - return; - } else if (m_state == State::Failed) { - qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed in previous step:" << m_url.toString(); - m_sink->abort(); - m_reply.reset(); - emit failed(""); - emit finished(); - return; - } else if (m_state == State::AbortedByUser) { - qCDebug(taskUploadLogC) << getUid().toString() << "Upload aborted in previous step:" << m_url.toString(); - m_sink->abort(); - m_reply.reset(); - emit aborted(); - emit finished(); - return; - } - - // make sure we got all the remaining data, if any - auto data = m_reply->readAll(); - if (data.size()) { - qCDebug(taskUploadLogC) << getUid().toString() << "Writing extra" << data.size() << "bytes"; - m_state = m_sink->write(data); - } - - // otherwise, finalize the whole graph - m_state = m_sink->finalize(*m_reply.get()); - if (m_state != State::Succeeded) { - qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed to finalize:" << m_url.toString(); - m_sink->abort(); - m_reply.reset(); - emit failed(""); - emit finished(); - return; - } - m_reply.reset(); - qCDebug(taskUploadLogC) << getUid().toString() << "Upload succeeded:" << m_url.toString(); - emit succeeded(); - emit finished(); -} - -void Upload::downloadReadyRead() -{ - if (m_state == State::Running) { - auto data = m_reply->readAll(); - m_state = m_sink->write(data); - } -} - -void Upload::executeTask() -{ - setStatus(tr("Uploading %1").arg(m_url.toString())); - - if (m_state == State::AbortedByUser) { - qCWarning(taskUploadLogC) << getUid().toString() << "Attempt to start an aborted Upload:" << m_url.toString(); - emit aborted(); - emit finished(); - return; - } - QNetworkRequest request(m_url); - m_state = m_sink->init(request); - switch (m_state) { - case State::Succeeded: - emitSucceeded(); - qCDebug(taskUploadLogC) << getUid().toString() << "Upload cache hit " << m_url.toString(); - return; - case State::Running: - qCDebug(taskUploadLogC) << getUid().toString() << "Uploading " << m_url.toString(); - break; - case State::Inactive: - case State::Failed: - emitFailed(""); - return; - case State::AbortedByUser: - emitAborted(); - return; - } - -#if defined(LAUNCHER_APPLICATION) - auto user_agent = APPLICATION->getUserAgent(); -#else - auto user_agent = BuildConfig.USER_AGENT; -#endif - request.setHeader(QNetworkRequest::UserAgentHeader, user_agent.toUtf8()); - - for (auto& header_proxy : m_headerProxies) { - header_proxy->writeHeaders(request); - } - - // TODO other types of post requests ? request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - QNetworkReply* rep = m_network->post(request, m_post_data); - - m_reply.reset(rep); - connect(rep, &QNetworkReply::downloadProgress, this, &Upload::downloadProgress); - connect(rep, &QNetworkReply::finished, this, &Upload::downloadFinished); -#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15 - connect(rep, &QNetworkReply::errorOccurred, this, &Upload::downloadError); -#else - connect(rep, QOverload::of(&QNetworkReply::error), this, &Upload::downloadError); -#endif - connect(rep, &QNetworkReply::sslErrors, this, &Upload::sslErrors); - connect(rep, &QNetworkReply::readyRead, this, &Upload::downloadReadyRead); + return m_network->post(request, m_post_data); } Upload::Ptr Upload::makeByteArray(QUrl url, std::shared_ptr output, QByteArray m_post_data) { auto up = makeShared(); + up->logCat = taskUploadLogC; up->m_url = std::move(url); up->m_sink.reset(new ByteArraySink(output)); up->m_post_data = std::move(m_post_data); diff --git a/launcher/net/Upload.h b/launcher/net/Upload.h index 8be3f2c1..51ce410b 100644 --- a/launcher/net/Upload.h +++ b/launcher/net/Upload.h @@ -37,37 +37,21 @@ #pragma once -#include "NetAction.h" -#include "Sink.h" +#include "net/NetRequest.h" namespace Net { -class Upload : public NetAction { +class Upload : public NetRequest { Q_OBJECT public: using Ptr = shared_qobject_ptr; static Upload::Ptr makeByteArray(QUrl url, std::shared_ptr output, QByteArray m_post_data); - auto abort() -> bool override; - auto canAbort() const -> bool override { return true; }; - virtual void init() override{}; - - protected slots: - void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override; - void downloadError(QNetworkReply::NetworkError error) override; - void sslErrors(const QList& errors) override; - void downloadFinished() override; - void downloadReadyRead() override; - - public slots: - void executeTask() override; protected: - std::unique_ptr m_sink; + virtual QNetworkReply* getReply(QNetworkRequest&) override; QByteArray m_post_data; - - bool handleRedirect(); }; } // namespace Net -- cgit From 45c39d078ff4675a87f97bc2e5cc5f072e591856 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Tue, 27 Jun 2023 09:23:24 +0300 Subject: More clenup Signed-off-by: Trial97 --- launcher/net/ApiDownload.cpp | 3 --- launcher/net/ApiUpload.cpp | 1 - launcher/net/Download.cpp | 13 ------------- launcher/net/Download.h | 12 +----------- launcher/net/NetRequest.cpp | 15 ++++----------- launcher/net/NetRequest.h | 2 ++ launcher/net/Upload.cpp | 3 --- launcher/net/Upload.h | 2 +- 8 files changed, 8 insertions(+), 43 deletions(-) (limited to 'launcher/net/Upload.cpp') diff --git a/launcher/net/ApiDownload.cpp b/launcher/net/ApiDownload.cpp index 5dfaa51d..aaa8ff65 100644 --- a/launcher/net/ApiDownload.cpp +++ b/launcher/net/ApiDownload.cpp @@ -28,7 +28,6 @@ namespace Net { auto ApiDownload::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Download::Ptr { auto dl = makeShared(); - dl->logCat = taskDownloadLogC; dl->m_url = url; dl->setObjectName(QString("CACHE:") + url.toString()); dl->m_options = options; @@ -41,7 +40,6 @@ auto ApiDownload::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> D auto ApiDownload::makeByteArray(QUrl url, std::shared_ptr output, Options options) -> Download::Ptr { auto dl = makeShared(); - dl->logCat = taskDownloadLogC; dl->m_url = url; dl->setObjectName(QString("BYTES:") + url.toString()); dl->m_options = options; @@ -52,7 +50,6 @@ auto ApiDownload::makeByteArray(QUrl url, std::shared_ptr output, Op auto ApiDownload::makeFile(QUrl url, QString path, Options options) -> Download::Ptr { auto dl = makeShared(); - dl->logCat = taskDownloadLogC; dl->m_url = url; dl->setObjectName(QString("FILE:") + url.toString()); dl->m_options = options; diff --git a/launcher/net/ApiUpload.cpp b/launcher/net/ApiUpload.cpp index ec4f14af..c1221b76 100644 --- a/launcher/net/ApiUpload.cpp +++ b/launcher/net/ApiUpload.cpp @@ -28,7 +28,6 @@ namespace Net { Upload::Ptr ApiUpload::makeByteArray(QUrl url, std::shared_ptr output, QByteArray m_post_data) { auto up = makeShared(); - up->logCat = taskUploadLogC; up->m_url = std::move(url); up->m_sink.reset(new ByteArraySink(output)); up->m_post_data = std::move(m_post_data); diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index dc316878..d25447b2 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -47,25 +47,14 @@ #include "ChecksumValidator.h" #include "MetaCacheSink.h" -#if defined(LAUNCHER_APPLICATION) -#include "Application.h" -#endif - -#include "BuildConfig.h" - -#include "net/Logging.h" #include "net/NetAction.h" -#include "MMCTime.h" -#include "StringUtils.h" - namespace Net { #if defined(LAUNCHER_APPLICATION) auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Download::Ptr { auto dl = makeShared(); - dl->logCat = taskDownloadLogC; dl->m_url = url; dl->setObjectName(QString("CACHE:") + url.toString()); dl->m_options = options; @@ -79,7 +68,6 @@ auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Down auto Download::makeByteArray(QUrl url, std::shared_ptr output, Options options) -> Download::Ptr { auto dl = makeShared(); - dl->logCat = taskDownloadLogC; dl->m_url = url; dl->setObjectName(QString("BYTES:") + url.toString()); dl->m_options = options; @@ -90,7 +78,6 @@ auto Download::makeByteArray(QUrl url, std::shared_ptr output, Optio auto Download::makeFile(QUrl url, QString path, Options options) -> Download::Ptr { auto dl = makeShared(); - dl->logCat = taskDownloadLogC; dl->m_url = url; dl->setObjectName(QString("FILE:") + url.toString()); dl->m_options = options; diff --git a/launcher/net/Download.h b/launcher/net/Download.h index 113875ee..dc3ccacf 100644 --- a/launcher/net/Download.h +++ b/launcher/net/Download.h @@ -38,12 +38,7 @@ #pragma once -#include - #include "HttpMetaCache.h" -#include "NetAction.h" -#include "Sink.h" -#include "Validator.h" #include "QObjectPtr.h" #include "net/NetRequest.h" @@ -51,12 +46,9 @@ namespace Net { class Download : public NetRequest { Q_OBJECT - public: using Ptr = shared_qobject_ptr; - - public: - ~Download() override = default; + explicit Download() : NetRequest() { logCat = taskDownloadLogC; }; #if defined(LAUNCHER_APPLICATION) static auto makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions) -> Download::Ptr; @@ -65,8 +57,6 @@ class Download : public NetRequest { static auto makeByteArray(QUrl url, std::shared_ptr output, Options options = Option::NoOptions) -> Download::Ptr; static auto makeFile(QUrl url, QString path, Options options = Option::NoOptions) -> Download::Ptr; - void init() override{}; - protected: virtual QNetworkReply* getReply(QNetworkRequest&) override; }; diff --git a/launcher/net/NetRequest.cpp b/launcher/net/NetRequest.cpp index 3f7cdbaa..30457712 100644 --- a/launcher/net/NetRequest.cpp +++ b/launcher/net/NetRequest.cpp @@ -43,17 +43,10 @@ #include #include -#include "ByteArraySink.h" -#include "ChecksumValidator.h" -#include "MetaCacheSink.h" - #if defined(LAUNCHER_APPLICATION) #include "Application.h" #endif -#include "BuildConfig.h" - -#include "net/Logging.h" #include "net/NetAction.h" #include "MMCTime.h" @@ -70,7 +63,7 @@ void NetRequest::executeTask() { init(); - setStatus(tr("Downloading %1").arg(StringUtils::truncateUrlHumanFriendly(m_url, 80))); + setStatus(tr("Requesting %1").arg(StringUtils::truncateUrlHumanFriendly(m_url, 80))); if (getState() == Task::State::AbortedByUser) { qCWarning(logCat) << getUid().toString() << "Attempt to start an aborted Request:" << m_url.toString(); @@ -314,9 +307,7 @@ void NetRequest::downloadReadyRead() } } -} // namespace Net - -auto Net::NetRequest::abort() -> bool +auto NetRequest::abort() -> bool { if (m_reply) { m_reply->abort(); @@ -325,3 +316,5 @@ auto Net::NetRequest::abort() -> bool } return true; } + +} // namespace Net diff --git a/launcher/net/NetRequest.h b/launcher/net/NetRequest.h index 0105a60c..6ebdcab0 100644 --- a/launcher/net/NetRequest.h +++ b/launcher/net/NetRequest.h @@ -51,6 +51,8 @@ namespace Net { class NetRequest : public NetAction { Q_OBJECT + protected: + explicit NetRequest() : NetAction(){}; public: using Ptr = shared_qobject_ptr; diff --git a/launcher/net/Upload.cpp b/launcher/net/Upload.cpp index 245a52ae..726572e5 100644 --- a/launcher/net/Upload.cpp +++ b/launcher/net/Upload.cpp @@ -42,8 +42,6 @@ #include #include "ByteArraySink.h" -#include "net/Logging.h" - namespace Net { QNetworkReply* Upload::getReply(QNetworkRequest& request) @@ -55,7 +53,6 @@ QNetworkReply* Upload::getReply(QNetworkRequest& request) Upload::Ptr Upload::makeByteArray(QUrl url, std::shared_ptr output, QByteArray m_post_data) { auto up = makeShared(); - up->logCat = taskUploadLogC; up->m_url = std::move(url); up->m_sink.reset(new ByteArraySink(output)); up->m_post_data = std::move(m_post_data); diff --git a/launcher/net/Upload.h b/launcher/net/Upload.h index 51ce410b..f920e556 100644 --- a/launcher/net/Upload.h +++ b/launcher/net/Upload.h @@ -43,9 +43,9 @@ namespace Net { class Upload : public NetRequest { Q_OBJECT - public: using Ptr = shared_qobject_ptr; + explicit Upload() : NetRequest() { logCat = taskUploadLogC; }; static Upload::Ptr makeByteArray(QUrl url, std::shared_ptr output, QByteArray m_post_data); -- cgit