1 //=============================================================================
2 // MuseScore
3 // Music Composition & Notation
4 //
5 // Copyright (C) 2020 MuseScore BVBA and others
6 //
7 // This program is free software; you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License version 2.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 //=============================================================================
19
20 #include "avsomrnetrecognizer.h"
21
22 #include <QFile>
23 #include <QNetworkAccessManager>
24 #include <QNetworkReply>
25 #include <QHttpPart>
26 #include <QEventLoop>
27 #include <QTimer>
28 #include <QJsonDocument>
29 #include <QJsonParseError>
30
31 #include "avslog.h"
32
33 namespace {
34 static const QString HOST_API_OMR("https://musescore.com/api/omr");
35 static const int STATUS_OK{200};
36 static const int STATUS_NOT_FOUND{404};
37 static const int STATUS_PROCESSING{411};
38 static const int STATUS_NOT_SUPPORTED{422};
39
40 static const int CHECK_COUNT{1000};
41 static const int CHECK_INTERVAL_MS{300};
42 }
43
44 using namespace Ms::Avs;
45
AvsOmrNetRecognizer()46 AvsOmrNetRecognizer::AvsOmrNetRecognizer()
47 {
48 _net = new QNetworkAccessManager();
49 }
50
~AvsOmrNetRecognizer()51 AvsOmrNetRecognizer::~AvsOmrNetRecognizer()
52 {
53 delete _net;
54 }
55
56 //---------------------------------------------------------
57 // type
58 //---------------------------------------------------------
59
type() const60 QString AvsOmrNetRecognizer::type() const
61 {
62 return "cloud";
63 }
64
65 //---------------------------------------------------------
66 // isAvailable
67 //---------------------------------------------------------
68
isAvailable() const69 bool AvsOmrNetRecognizer::isAvailable() const
70 {
71 //! TODO add allow check
72 return true;
73 }
74
75 //---------------------------------------------------------
76 // recognize
77 //---------------------------------------------------------
78
recognize(const QString & filePath,QByteArray * avsFileData,const OnStep & onStep)79 bool AvsOmrNetRecognizer::recognize(const QString& filePath, QByteArray* avsFileData, const OnStep &onStep)
80 {
81 QString getUrl;
82 LOGI() << "begin send file: " << filePath;
83
84 auto step = [&onStep](Step::Type t, uint16_t perc, uint16_t percMax, Ret ret = Ret::Ok) {
85 if (!ret)
86 LOGE() << "failed step: " << t << ", err: " << ret.formatedText();
87 else
88 LOGI() << "success step: " << t << ", err: " << ret.formatedText();
89
90 if (onStep)
91 onStep(Step(t, perc, percMax, ret));
92 };
93
94 step(Step::PrepareStart, 1, 10);
95 Ret ret = send(filePath, &getUrl);
96 step(Step::PrepareFinish, 10, 10, ret);
97 if (!ret)
98 return false;
99
100 step(Step::ProcessingStart, 11, 90);
101 QString fileUrl;
102 ret = check(getUrl, &fileUrl);
103 step(Step::ProcessingFinish, 90, 90, ret);
104 if (!ret)
105 return false;
106
107 step(Step::LoadStart, 91, 100);
108 ret = load(fileUrl, avsFileData);
109 step(Step::LoadFinish, 100, 100, ret);
110 if (!ret)
111 return false;
112
113 return true;
114 }
115
116 //---------------------------------------------------------
117 // send
118 //---------------------------------------------------------
119
netRetToRet(const NetRet & nr) const120 Ret AvsOmrNetRecognizer::netRetToRet(const NetRet& nr) const
121 {
122 if (nr.httpStatus < 100) {
123 return Ret::NetworkError;
124 }
125
126 if (nr.httpStatus >= 200 && nr.httpStatus < 300) {
127 return Ret::Ok;
128 }
129 else if (nr.httpStatus == STATUS_NOT_SUPPORTED) {
130 return Ret::FileNotSupported;
131 }
132
133 return Ret::ServerError;
134 }
135
136 //---------------------------------------------------------
137 // send
138 //---------------------------------------------------------
139
send(const QString & filePath,QString * getUrl)140 Ret AvsOmrNetRecognizer::send(const QString& filePath, QString *getUrl)
141 {
142 QFile *file = new QFile(filePath);
143 if (!file->open(QIODevice::ReadOnly)) {
144 LOGE() << "failed open file: " << filePath;
145 delete file;
146 return Ret::FailedReadFile;
147 }
148
149 //request
150 QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
151
152 QHttpPart part;
153 part.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream"));
154 part.setHeader(QNetworkRequest::ContentDispositionHeader, QString("form-data; name=\"file\"; filename=\"%1\"").arg(QFileInfo(filePath).fileName()));
155
156 part.setBodyDevice(file);
157 file->setParent(multiPart); // we cannot delete the file now, so delete it with the multiPart
158
159 multiPart->append(part);
160
161 QNetworkRequest req;
162 req.setUrl(QUrl(HOST_API_OMR));
163 req.setRawHeader("accept", "application/json");
164 QNetworkReply* rep = _net->post(req, multiPart);
165 multiPart->setParent(rep);
166
167 QByteArray ba;
168 NetRet netRet = doExecReply(rep, &ba);
169
170 LOGI() << "send status: " << netRet.httpStatus << ", data: " << ba;
171
172 if (netRet.httpStatus != STATUS_OK) {
173 LOGE() << "failed send status: " << netRet.httpStatus << ", file: " << filePath;
174 return netRetToRet(netRet);
175 }
176
177 QVariantMap info = parseInfo(ba);
178 if (getUrl)
179 *getUrl = info.value("url").toString();
180
181 return Ret::Ok;
182 }
183
184 //---------------------------------------------------------
185 // doExecReply
186 //---------------------------------------------------------
187
doExecReply(QNetworkReply * reply,QByteArray * ba)188 AvsOmrNetRecognizer::NetRet AvsOmrNetRecognizer::doExecReply(QNetworkReply* reply, QByteArray *ba)
189 {
190 NetRet ret;
191 QEventLoop loop;
192 QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
193 loop.exec();
194
195 ret.replyError = static_cast<int>(reply->error());
196 ret.httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
197
198 if (ba)
199 *ba = reply->readAll();
200
201 reply->close();
202 delete reply;
203
204 return ret;
205 }
206
207 //---------------------------------------------------------
208 // parseInfo
209 //---------------------------------------------------------
210
parseInfo(const QByteArray & ba) const211 QVariantMap AvsOmrNetRecognizer::parseInfo(const QByteArray& ba) const
212 {
213 QVariantMap info;
214
215 QJsonParseError err;
216 QJsonDocument doc = QJsonDocument::fromJson(ba, &err);
217 if (err.error != QJsonParseError::NoError) {
218 LOGE() << "failed parse reply, err: " << err.errorString();
219 return QVariantMap();
220 }
221
222 QJsonObject replyObj = doc.object();
223 QJsonObject infoObj = replyObj.value("info").toObject();
224 info["url"] = infoObj.value("url").toString();
225
226 return info;
227 }
228
229 //---------------------------------------------------------
230 // check
231 //---------------------------------------------------------
232
check(const QString & url,QString * fileUrl)233 Ret AvsOmrNetRecognizer::check(const QString& url, QString *fileUrl)
234 {
235 NetRet netRet;
236 QByteArray data;
237
238 for (size_t t = 0; t < CHECK_COUNT; ++t) {
239 netRet = doCheck(url, &data);
240 LOGI() << "[" << t << "] status: " << netRet.httpStatus;
241 if (netRet.httpStatus != STATUS_PROCESSING)
242 break;
243
244 // sleep
245 QEventLoop loop;
246 QTimer timer;
247 timer.setSingleShot(true);
248 QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
249 timer.start(CHECK_INTERVAL_MS);
250 loop.exec();
251 }
252
253 if (netRet.httpStatus != STATUS_OK)
254 return netRetToRet(netRet);
255
256 QVariantMap info = parseInfo(data);
257 if (fileUrl)
258 *fileUrl = info.value("url").toString();
259
260 return Ret::Ok;
261 }
262
263 //---------------------------------------------------------
264 // doCheck
265 //---------------------------------------------------------
266
doCheck(const QString & url,QByteArray * ba)267 AvsOmrNetRecognizer::NetRet AvsOmrNetRecognizer::doCheck(const QString& url, QByteArray *ba)
268 {
269 QNetworkRequest req;
270 req.setUrl(QUrl(url));
271 QNetworkReply* reply = _net->get(req);
272 return doExecReply(reply, ba);
273 }
274
275 //---------------------------------------------------------
276 // load
277 //---------------------------------------------------------
278
load(const QString & url,QByteArray * ba)279 Ret AvsOmrNetRecognizer::load(const QString& url, QByteArray* ba)
280 {
281 QNetworkRequest req;
282 req.setUrl(QUrl(url));
283 QNetworkReply* reply = _net->get(req);
284
285 NetRet netRet = doExecReply(reply, ba);
286 return netRetToRet(netRet);
287 }
288