LCOV - code coverage report
Current view: top level - muse/framework/network/internal - networkmanager.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 157 0
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2021 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : #include "networkmanager.h"
      23              : 
      24              : #include <QNetworkAccessManager>
      25              : #include <QNetworkReply>
      26              : #include <QUrl>
      27              : 
      28              : #include "networkerrors.h"
      29              : 
      30              : #include "log.h"
      31              : 
      32              : using namespace muse;
      33              : using namespace muse::network;
      34              : using namespace muse::async;
      35              : 
      36            0 : static bool openDevice(QIODevicePtr& device, QIODevice::OpenModeFlag flags)
      37              : {
      38            0 :     IF_ASSERT_FAILED(device) {
      39            0 :         return false;
      40              :     }
      41              : 
      42            0 :     if (device->isOpen()) {
      43            0 :         device->close();
      44              :     }
      45              : 
      46            0 :     return device->open(flags);
      47              : }
      48              : 
      49            0 : static void closeDevice(QIODevicePtr& device)
      50              : {
      51            0 :     if (device && device->isOpen()) {
      52            0 :         device->close();
      53              :     }
      54            0 : }
      55              : 
      56            0 : static Ret retFromReply(const QNetworkReply* reply)
      57              : {
      58            0 :     if (!reply) {
      59            0 :         return make_ret(Err::NetworkError);
      60              :     }
      61              : 
      62            0 :     Ret ret = muse::make_ok();
      63              : 
      64            0 :     if (reply->error() == QNetworkReply::TimeoutError) {
      65            0 :         ret = make_ret(Err::Timeout);
      66            0 :     } else if (reply->error() == QNetworkReply::OperationCanceledError) {
      67            0 :         ret = make_ret(Err::Abort);
      68            0 :     } else if (reply->error() != QNetworkReply::NoError) {
      69            0 :         ret.setCode(static_cast<int>(Err::NetworkError));
      70              :     }
      71              : 
      72            0 :     QString errorString = reply->errorString();
      73            0 :     if (!errorString.isEmpty()) {
      74            0 :         ret.setText(errorString.toStdString());
      75              :     }
      76              : 
      77            0 :     QVariant status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
      78            0 :     if (status.isValid()) {
      79            0 :         ret.setData("status", status.toInt());
      80              :     }
      81              : 
      82            0 :     QVariant statusText = reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute);
      83            0 :     if (statusText.isValid()) {
      84            0 :         ret.setData("statusText", statusText.toString().toStdString());
      85              :     }
      86              : 
      87            0 :     QVariantMap headers;
      88            0 :     const QList<QNetworkReply::RawHeaderPair> pairs = reply->rawHeaderPairs();
      89            0 :     for (const QNetworkReply::RawHeaderPair& pair : pairs) {
      90            0 :         const QString name = QString::fromUtf8(pair.first).toLower();
      91            0 :         const QString value = QString::fromUtf8(pair.second);
      92            0 :         if (headers.contains(name)) {
      93            0 :             headers[name] = headers[name].toString() + QStringLiteral(", ") + value;
      94              :         } else {
      95            0 :             headers.insert(name, value);
      96              :         }
      97            0 :     }
      98            0 :     ret.setData("headers", headers);
      99              : 
     100            0 :     return ret;
     101            0 : }
     102              : 
     103            0 : NetworkManager::NetworkManager(QObject* parent)
     104            0 :     : QObject(parent)
     105              : {
     106            0 :     m_manager = new QNetworkAccessManager(this);
     107            0 :     m_manager->setTransferTimeout(); // Use Qt's default timeout (30s)
     108            0 : }
     109              : 
     110            0 : RetVal<Progress> NetworkManager::get(const QUrl& url, IncomingDevicePtr incomingData, const RequestHeaders& headers)
     111              : {
     112            0 :     return execRequest(GET_REQUEST, url, incomingData, NoOutgoingDevice(), headers);
     113              : }
     114              : 
     115            0 : RetVal<Progress> NetworkManager::head(const QUrl& url, const RequestHeaders& headers)
     116              : {
     117            0 :     return execRequest(HEAD_REQUEST, url, nullptr, NoOutgoingDevice(), headers);
     118              : }
     119              : 
     120            0 : RetVal<Progress> NetworkManager::post(const QUrl& url, OutgoingDeviceVar outgoingData, IncomingDevicePtr incomingData,
     121              :                                       const RequestHeaders& headers)
     122              : {
     123            0 :     return execRequest(POST_REQUEST, url, incomingData, outgoingData, headers);
     124              : }
     125              : 
     126            0 : RetVal<Progress> NetworkManager::put(const QUrl& url, OutgoingDeviceVar outgoingData, IncomingDevicePtr incomingData,
     127              :                                      const RequestHeaders& headers)
     128              : {
     129            0 :     return execRequest(PUT_REQUEST, url, incomingData, outgoingData, headers);
     130              : }
     131              : 
     132            0 : RetVal<Progress> NetworkManager::patch(const QUrl& url, OutgoingDeviceVar outgoingData, IncomingDevicePtr incomingData,
     133              :                                        const RequestHeaders& headers)
     134              : {
     135            0 :     return execRequest(PATCH_REQUEST, url, incomingData, outgoingData, headers);
     136              : }
     137              : 
     138            0 : RetVal<Progress> NetworkManager::del(const QUrl& url, IncomingDevicePtr incomingData, const RequestHeaders& headers)
     139              : {
     140            0 :     return execRequest(DELETE_REQUEST, url, incomingData, NoOutgoingDevice(), headers);
     141              : }
     142              : 
     143            0 : RetVal<Progress> NetworkManager::execRequest(RequestType requestType, const QUrl& url,
     144              :                                              IncomingDevicePtr incomingData,
     145              :                                              OutgoingDeviceVar outgoingData,
     146              :                                              const RequestHeaders& headers)
     147              : {
     148            0 :     if (std::holds_alternative<QIODevicePtr>(outgoingData)) {
     149            0 :         if (!openDevice(std::get<QIODevicePtr>(outgoingData), QIODevice::ReadOnly)) {
     150            0 :             return RetVal<Progress>::make_ret((int)Err::FiledOpenIODeviceRead);
     151              :         }
     152              :     }
     153              : 
     154            0 :     if (incomingData) {
     155            0 :         if (!openDevice(incomingData, QIODevice::WriteOnly)) {
     156            0 :             return RetVal<Progress>::make_ret((int)Err::FiledOpenIODeviceWrite);
     157              :         }
     158              :     }
     159              : 
     160            0 :     const QNetworkRequest request = prepareRequest(url, headers);
     161            0 :     QNetworkReply* reply = sendRequest(requestType, request, outgoingData);
     162            0 :     IF_ASSERT_FAILED(reply) {
     163            0 :         return RetVal<Progress>::make_ret((int)Err::NetworkError);
     164              :     }
     165              : 
     166            0 :     const size_t requestId = m_lastRequestId++;
     167            0 :     RequestData& requestData = m_requestDataMap[requestId];
     168            0 :     requestData.incomingData = incomingData;
     169            0 :     requestData.outgoingData = outgoingData;
     170            0 :     requestData.reply = reply;
     171            0 :     requestData.progress.start();
     172            0 :     requestData.progress.canceled().onNotify(this, [this, requestId]() {
     173            0 :         auto it = m_requestDataMap.find(requestId);
     174            0 :         IF_ASSERT_FAILED(it != m_requestDataMap.end() && it->second.reply) {
     175            0 :             return;
     176              :         }
     177              : 
     178            0 :         it->second.reply->abort();
     179              :     });
     180              : 
     181            0 :     if (!std::holds_alternative<NoOutgoingDevice>(outgoingData)) {
     182            0 :         connect(reply, &QNetworkReply::uploadProgress, this, [this, requestId](qint64 curr, qint64 total) {
     183            0 :             auto it = m_requestDataMap.find(requestId);
     184            0 :             IF_ASSERT_FAILED(it != m_requestDataMap.end()) {
     185            0 :                 return;
     186              :             }
     187              : 
     188            0 :             it->second.progress.progress(curr, total);
     189              :         });
     190              :     }
     191              : 
     192            0 :     if (incomingData) {
     193            0 :         connect(reply, &QNetworkReply::downloadProgress, this, [this, requestId](qint64 curr, qint64 total) {
     194            0 :             auto it = m_requestDataMap.find(requestId);
     195            0 :             IF_ASSERT_FAILED(it != m_requestDataMap.end()) {
     196            0 :                 return;
     197              :             }
     198              : 
     199            0 :             it->second.progress.progress(curr, total);
     200              :         });
     201              : 
     202            0 :         connect(reply, &QNetworkReply::readyRead, this, [this, requestId]() {
     203            0 :             auto it = m_requestDataMap.find(requestId);
     204            0 :             IF_ASSERT_FAILED(it != m_requestDataMap.end()) {
     205            0 :                 return;
     206              :             }
     207              : 
     208            0 :             RequestData& data = it->second;
     209            0 :             IF_ASSERT_FAILED(data.incomingData && data.reply) {
     210            0 :                 return;
     211              :             }
     212              : 
     213            0 :             const QByteArray chunk = data.reply->readAll();
     214            0 :             if (data.incomingData->write(chunk) != chunk.size()) {
     215            0 :                 LOGE() << "failed to store incoming data: " << data.incomingData->errorString();
     216            0 :                 data.reply->abort();
     217              :             }
     218            0 :         });
     219              :     }
     220              : 
     221            0 :     connect(reply, &QNetworkReply::finished, this, [this, requestId]() {
     222            0 :         auto it = m_requestDataMap.find(requestId);
     223            0 :         IF_ASSERT_FAILED(it != m_requestDataMap.end() && it->second.reply) {
     224            0 :             return;
     225              :         }
     226              : 
     227            0 :         RequestData& data = it->second;
     228            0 :         data.progress.canceled().disconnect(this);
     229              : 
     230            0 :         if (std::holds_alternative<QIODevicePtr>(data.outgoingData)) {
     231            0 :             closeDevice(std::get<QIODevicePtr>(data.outgoingData));
     232              :         }
     233              : 
     234            0 :         closeDevice(data.incomingData);
     235              : 
     236            0 :         const Ret ret = retFromReply(data.reply);
     237            0 :         data.reply->deleteLater();
     238              : 
     239            0 :         Progress progress = data.progress;
     240            0 :         m_requestDataMap.erase(it);
     241            0 :         progress.finish(ret);
     242            0 :     });
     243              : 
     244            0 :     return RetVal<Progress>::make_ok(requestData.progress);
     245            0 : }
     246              : 
     247            0 : QNetworkRequest NetworkManager::prepareRequest(const QUrl& url, const RequestHeaders& headers) const
     248              : {
     249            0 :     QNetworkRequest request(url);
     250            0 :     request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, true);
     251              : 
     252            0 :     RequestHeaders _headers = headers;
     253            0 :     if (_headers.isEmpty()) {
     254            0 :         _headers = configuration()->defaultHeaders();
     255              :     }
     256              : 
     257            0 :     for (auto it = _headers.knownHeaders.cbegin(); it != _headers.knownHeaders.cend(); ++it) {
     258            0 :         request.setHeader(it.key(), it.value());
     259              :     }
     260              : 
     261            0 :     for (auto it = _headers.rawHeaders.cbegin(); it != _headers.rawHeaders.cend(); ++it) {
     262            0 :         request.setRawHeader(it.key(), it.value());
     263              :     }
     264              : 
     265            0 :     return request;
     266            0 : }
     267              : 
     268            0 : QNetworkReply* NetworkManager::sendRequest(RequestType type, const QNetworkRequest& request, const OutgoingDeviceVar& device)
     269              : {
     270            0 :     switch (type) {
     271            0 :     case GET_REQUEST: return m_manager->get(request);
     272            0 :     case HEAD_REQUEST: return m_manager->head(request);
     273            0 :     case DELETE_REQUEST: return m_manager->deleteResource(request);
     274            0 :     case PUT_REQUEST: {
     275            0 :         if (std::holds_alternative<QIODevicePtr>(device)) {
     276            0 :             return m_manager->put(request, std::get<QIODevicePtr>(device).get());
     277            0 :         } else if (std::holds_alternative<QHttpMultiPartPtr>(device)) {
     278            0 :             return m_manager->put(request, std::get<QHttpMultiPartPtr>(device).get());
     279              :         }
     280            0 :         break;
     281              :     }
     282            0 :     case PATCH_REQUEST: {
     283            0 :         if (std::holds_alternative<QIODevicePtr>(device)) {
     284            0 :             return m_manager->sendCustomRequest(request, "PATCH", std::get<QIODevicePtr>(device).get());
     285            0 :         } else if (std::holds_alternative<QHttpMultiPartPtr>(device)) {
     286            0 :             return m_manager->sendCustomRequest(request, "PATCH", std::get<QHttpMultiPartPtr>(device).get());
     287              :         }
     288            0 :         break;
     289              :     }
     290            0 :     case POST_REQUEST: {
     291            0 :         if (std::holds_alternative<QIODevicePtr>(device)) {
     292            0 :             return m_manager->post(request, std::get<QIODevicePtr>(device).get());
     293            0 :         } else if (std::holds_alternative<QHttpMultiPartPtr>(device)) {
     294            0 :             return m_manager->post(request, std::get<QHttpMultiPartPtr>(device).get());
     295              :         }
     296            0 :         break;
     297              :     }
     298              :     }
     299              : 
     300            0 :     UNREACHABLE;
     301              :     return nullptr;
     302              : }
        

Generated by: LCOV version 2.0-1