Skip to content

Commit d149a96

Browse files
Add async NetworkManager
1 parent 29348e9 commit d149a96

14 files changed

+526
-21
lines changed

src/framework/network/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,14 @@ set(MODULE_SRC
2727
${CMAKE_CURRENT_LIST_DIR}/networkerrors.h
2828
${CMAKE_CURRENT_LIST_DIR}/networktypes.h
2929
${CMAKE_CURRENT_LIST_DIR}/deprecated/inetworkmanager.h
30+
${CMAKE_CURRENT_LIST_DIR}/inetworkmanager.h
3031
${CMAKE_CURRENT_LIST_DIR}/inetworkmanagercreator.h
3132
${CMAKE_CURRENT_LIST_DIR}/inetworkconfiguration.h
3233

3334
${CMAKE_CURRENT_LIST_DIR}/internal/deprecated/networkmanager.cpp
3435
${CMAKE_CURRENT_LIST_DIR}/internal/deprecated/networkmanager.h
36+
${CMAKE_CURRENT_LIST_DIR}/internal/networkmanager.cpp
37+
${CMAKE_CURRENT_LIST_DIR}/internal/networkmanager.h
3538
${CMAKE_CURRENT_LIST_DIR}/internal/networkmanagercreator.cpp
3639
${CMAKE_CURRENT_LIST_DIR}/internal/networkmanagercreator.h
3740
${CMAKE_CURRENT_LIST_DIR}/internal/networkconfiguration.cpp
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* SPDX-License-Identifier: GPL-3.0-only
3+
* MuseScore-CLA-applies
4+
*
5+
* MuseScore
6+
* Music Composition & Notation
7+
*
8+
* Copyright (C) 2025 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+
23+
#pragma once
24+
25+
#include "global/types/ret.h"
26+
#include "global/progress.h"
27+
28+
#include "networktypes.h"
29+
30+
class QUrl;
31+
32+
namespace muse::network {
33+
class INetworkManager
34+
{
35+
public:
36+
virtual ~INetworkManager() = default;
37+
38+
virtual RetVal<Progress> get(const QUrl& url, IncomingDevicePtr incomingData, const RequestHeaders& headers = RequestHeaders()) = 0;
39+
virtual RetVal<Progress> head(const QUrl& url, const RequestHeaders& headers = RequestHeaders()) = 0;
40+
virtual RetVal<Progress> post(const QUrl& url, OutgoingDeviceVar outgoingData, IncomingDevicePtr incomingData,
41+
const RequestHeaders& headers = RequestHeaders()) = 0;
42+
virtual RetVal<Progress> put(const QUrl& url, OutgoingDeviceVar outgoingData, IncomingDevicePtr incomingData,
43+
const RequestHeaders& headers = RequestHeaders()) = 0;
44+
virtual RetVal<Progress> patch(const QUrl& url, OutgoingDeviceVar outgoingData, IncomingDevicePtr incomingData,
45+
const RequestHeaders& headers = RequestHeaders()) = 0;
46+
virtual RetVal<Progress> del(const QUrl& url, IncomingDevicePtr incomingData, const RequestHeaders& headers = RequestHeaders()) = 0;
47+
};
48+
49+
using INetworkManagerPtr = std::shared_ptr<INetworkManager>;
50+
}

src/framework/network/inetworkmanagercreator.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* MuseScore
66
* Music Composition & Notation
77
*
8-
* Copyright (C) 2021 MuseScore Limited and others
8+
* Copyright (C) 2025 MuseScore Limited and others
99
*
1010
* This program is free software: you can redistribute it and/or modify
1111
* it under the terms of the GNU General Public License version 3 as
@@ -19,10 +19,11 @@
1919
* You should have received a copy of the GNU General Public License
2020
* along with this program. If not, see <https://www.gnu.org/licenses/>.
2121
*/
22-
#ifndef MUSE_NETWORK_INETWORKMANAGERCREATOR_H
23-
#define MUSE_NETWORK_INETWORKMANAGERCREATOR_H
22+
23+
#pragma once
2424

2525
#include "modularity/imoduleinterface.h"
26+
#include "inetworkmanager.h"
2627
#include "deprecated/inetworkmanager.h"
2728

2829
namespace muse::network {
@@ -33,8 +34,7 @@ class INetworkManagerCreator : MODULE_EXPORT_INTERFACE
3334
public:
3435
virtual ~INetworkManagerCreator() = default;
3536

37+
virtual INetworkManagerPtr makeNetworkManager() const = 0;
3638
virtual deprecated::INetworkManagerPtr makeDeprecatedNetworkManager() const = 0;
3739
};
3840
}
39-
40-
#endif // MUSE_NETWORK_INETWORKMANAGERCREATOR_H
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
/*
2+
* SPDX-License-Identifier: GPL-3.0-only
3+
* MuseScore-CLA-applies
4+
*
5+
* MuseScore
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+
using namespace muse;
31+
using namespace muse::network;
32+
using namespace muse::async;
33+
34+
static bool openDevice(QIODevicePtr& device, QIODevice::OpenModeFlag flags)
35+
{
36+
IF_ASSERT_FAILED(device) {
37+
return false;
38+
}
39+
40+
if (device->isOpen()) {
41+
device->close();
42+
}
43+
44+
return device->open(flags);
45+
}
46+
47+
static void closeDevice(QIODevicePtr& device)
48+
{
49+
if (device && device->isOpen()) {
50+
device->close();
51+
}
52+
}
53+
54+
static Ret retFromReply(const QNetworkReply* reply)
55+
{
56+
if (!reply) {
57+
return make_ret(Err::NetworkError);
58+
}
59+
60+
Ret ret = muse::make_ok();
61+
62+
if (reply->error() == QNetworkReply::TimeoutError) {
63+
ret = make_ret(Err::Timeout);
64+
} else if (reply->error() == QNetworkReply::OperationCanceledError) {
65+
ret = make_ret(Err::Abort);
66+
} else if (reply->error() != QNetworkReply::NoError) {
67+
ret.setCode(static_cast<int>(Err::NetworkError));
68+
}
69+
70+
QString errorString = reply->errorString();
71+
if (!errorString.isEmpty()) {
72+
ret.setText(errorString.toStdString());
73+
}
74+
75+
QVariant status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
76+
if (status.isValid()) {
77+
ret.setData("status", status.toInt());
78+
}
79+
80+
return ret;
81+
}
82+
83+
NetworkManager::NetworkManager(QObject* parent)
84+
: QObject(parent)
85+
{
86+
m_manager = new QNetworkAccessManager(this);
87+
m_manager->setTransferTimeout(); // Use Qt's default timeout (30s)
88+
}
89+
90+
RetVal<Progress> NetworkManager::get(const QUrl& url, IncomingDevicePtr incomingData, const RequestHeaders& headers)
91+
{
92+
return execRequest(GET_REQUEST, url, incomingData, NoOutgoingDevice(), headers);
93+
}
94+
95+
RetVal<Progress> NetworkManager::head(const QUrl& url, const RequestHeaders& headers)
96+
{
97+
return execRequest(HEAD_REQUEST, url, nullptr, NoOutgoingDevice(), headers);
98+
}
99+
100+
RetVal<Progress> NetworkManager::post(const QUrl& url, OutgoingDeviceVar outgoingData, IncomingDevicePtr incomingData,
101+
const RequestHeaders& headers)
102+
{
103+
return execRequest(POST_REQUEST, url, incomingData, outgoingData, headers);
104+
}
105+
106+
RetVal<Progress> NetworkManager::put(const QUrl& url, OutgoingDeviceVar outgoingData, IncomingDevicePtr incomingData,
107+
const RequestHeaders& headers)
108+
{
109+
return execRequest(PUT_REQUEST, url, incomingData, outgoingData, headers);
110+
}
111+
112+
RetVal<Progress> NetworkManager::patch(const QUrl& url, OutgoingDeviceVar outgoingData, IncomingDevicePtr incomingData,
113+
const RequestHeaders& headers)
114+
{
115+
return execRequest(PATCH_REQUEST, url, incomingData, outgoingData, headers);
116+
}
117+
118+
RetVal<Progress> NetworkManager::del(const QUrl& url, IncomingDevicePtr incomingData, const RequestHeaders& headers)
119+
{
120+
return execRequest(DELETE_REQUEST, url, incomingData, NoOutgoingDevice(), headers);
121+
}
122+
123+
RetVal<Progress> NetworkManager::execRequest(RequestType requestType, const QUrl& url,
124+
IncomingDevicePtr incomingData,
125+
OutgoingDeviceVar outgoingData,
126+
const RequestHeaders& headers)
127+
{
128+
if (std::holds_alternative<QIODevicePtr>(outgoingData)) {
129+
if (!openDevice(std::get<QIODevicePtr>(outgoingData), QIODevice::ReadOnly)) {
130+
return RetVal<Progress>::make_ret((int)Err::FiledOpenIODeviceRead);
131+
}
132+
}
133+
134+
if (incomingData) {
135+
if (!openDevice(incomingData, QIODevice::WriteOnly)) {
136+
return RetVal<Progress>::make_ret((int)Err::FiledOpenIODeviceWrite);
137+
}
138+
}
139+
140+
const QNetworkRequest request = prepareRequest(url, headers);
141+
QNetworkReply* reply = sendRequest(requestType, request, outgoingData);
142+
IF_ASSERT_FAILED(reply) {
143+
return RetVal<Progress>::make_ret((int)Err::NetworkError);
144+
}
145+
146+
size_t requestId = 0;
147+
if (!m_requestDataMap.empty()) {
148+
requestId = m_requestDataMap.rbegin()->first + 1;
149+
}
150+
151+
RequestData& requestData = m_requestDataMap[requestId];
152+
requestData.incomingData = incomingData;
153+
requestData.outgoingData = outgoingData;
154+
requestData.reply = reply;
155+
requestData.progress.start();
156+
requestData.progress.canceled().onNotify(this, [this, requestId]() {
157+
m_requestDataMap[requestId].reply->abort();
158+
});
159+
160+
if (!std::holds_alternative<NoOutgoingDevice>(outgoingData)) {
161+
connect(reply, &QNetworkReply::uploadProgress, this, [this, requestId](qint64 curr, qint64 total) {
162+
m_requestDataMap[requestId].progress.progress(curr, total);
163+
});
164+
}
165+
166+
if (incomingData) {
167+
connect(reply, &QNetworkReply::downloadProgress, this, [this, requestId](qint64 curr, qint64 total) {
168+
m_requestDataMap[requestId].progress.progress(curr, total);
169+
});
170+
171+
connect(reply, &QNetworkReply::readyRead, this, [this, requestId]() {
172+
RequestData& data = m_requestDataMap[requestId];
173+
data.incomingData->write(data.reply->readAll());
174+
});
175+
}
176+
177+
connect(reply, &QNetworkReply::finished, this, [this, requestId]() {
178+
auto it = m_requestDataMap.find(requestId);
179+
IF_ASSERT_FAILED(it != m_requestDataMap.end()) {
180+
return;
181+
}
182+
183+
RequestData& data = it->second;
184+
data.reply->disconnect();
185+
data.progress.canceled().disconnect(this);
186+
187+
if (std::holds_alternative<QIODevicePtr>(data.outgoingData)) {
188+
closeDevice(std::get<QIODevicePtr>(data.outgoingData));
189+
}
190+
191+
closeDevice(data.incomingData);
192+
193+
const Ret ret = retFromReply(data.reply);
194+
Progress progress = data.progress;
195+
m_requestDataMap.erase(it);
196+
progress.finish(ret);
197+
});
198+
199+
return RetVal<Progress>::make_ok(requestData.progress);
200+
}
201+
202+
QNetworkRequest NetworkManager::prepareRequest(const QUrl& url, const RequestHeaders& headers) const
203+
{
204+
QNetworkRequest request(url);
205+
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, true);
206+
207+
RequestHeaders _headers = headers;
208+
if (_headers.isEmpty()) {
209+
_headers = configuration()->defaultHeaders();
210+
}
211+
212+
for (auto it = _headers.knownHeaders.cbegin(); it != _headers.knownHeaders.cend(); ++it) {
213+
request.setHeader(it.key(), it.value());
214+
}
215+
216+
for (auto it = _headers.rawHeaders.cbegin(); it != _headers.rawHeaders.cend(); ++it) {
217+
request.setRawHeader(it.key(), it.value());
218+
}
219+
220+
return request;
221+
}
222+
223+
QNetworkReply* NetworkManager::sendRequest(RequestType type, const QNetworkRequest& request, const OutgoingDeviceVar& device)
224+
{
225+
switch (type) {
226+
case GET_REQUEST: return m_manager->get(request);
227+
case HEAD_REQUEST: return m_manager->head(request);
228+
case DELETE_REQUEST: return m_manager->deleteResource(request);
229+
case PUT_REQUEST: {
230+
if (std::holds_alternative<QIODevicePtr>(device)) {
231+
return m_manager->put(request, std::get<QIODevicePtr>(device).get());
232+
} else if (std::holds_alternative<QHttpMultiPartPtr>(device)) {
233+
return m_manager->put(request, std::get<QHttpMultiPartPtr>(device).get());
234+
}
235+
break;
236+
}
237+
case PATCH_REQUEST: {
238+
if (std::holds_alternative<QIODevicePtr>(device)) {
239+
return m_manager->sendCustomRequest(request, "PATCH", std::get<QIODevicePtr>(device).get());
240+
} else if (std::holds_alternative<QHttpMultiPartPtr>(device)) {
241+
return m_manager->sendCustomRequest(request, "PATCH", std::get<QHttpMultiPartPtr>(device).get());
242+
}
243+
break;
244+
}
245+
case POST_REQUEST: {
246+
if (std::holds_alternative<QIODevicePtr>(device)) {
247+
return m_manager->post(request, std::get<QIODevicePtr>(device).get());
248+
} else if (std::holds_alternative<QHttpMultiPartPtr>(device)) {
249+
return m_manager->post(request, std::get<QHttpMultiPartPtr>(device).get());
250+
}
251+
break;
252+
}
253+
}
254+
255+
UNREACHABLE;
256+
return nullptr;
257+
}

0 commit comments

Comments
 (0)