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