1 //=============================================================================
2 // MusE Score
3 // Linux Music Score Editor
4 //
5 // The webview is shown on startup with a local file inviting user
6 // to start connecting with the community. They can press start and
7 // MuseScore will go online. If no connection, display a can't connect message
8 // On next startup, if no connection, the panel is closed. If connection, the
9 // MuseScore goes online directly. If the autoclose panel is reopen, the user
10 // can retry; retry should not close the panel.
11 //
12 // Copyright (C) 2011 Werner Schweer and others
13 //
14 // This program is free software; you can redistribute it and/or modify
15 // it under the terms of the GNU General Public License version 2.
16 //
17 // This program is distributed in the hope that it will be useful,
18 // but WITHOUT ANY WARRANTY; without even the implied warranty of
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 // GNU General Public License for more details.
21 //
22 // You should have received a copy of the GNU General Public License
23 // along with this program; if not, write to the Free Software
24 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 //=============================================================================
26
27 #include "webpage.h"
28 #include "musescore.h"
29 #include "preferences.h"
30 #include "libmscore/score.h"
31
32
33 namespace Ms {
34
35 static const char* staticUrl = "http://connect.musescore.com";
36
37 //---------------------------------------------------------
38 // MyNetworkAccessManager
39 //---------------------------------------------------------
40
createRequest(Operation op,const QNetworkRequest & req,QIODevice * outgoingData)41 QNetworkReply * MyNetworkAccessManager::createRequest(Operation op,
42 const QNetworkRequest & req,
43 QIODevice * outgoingData)
44 {
45 QNetworkRequest new_req(req);
46 new_req.setRawHeader("User-Agent", QString("MuseScore %1").arg(VERSION).toAscii());
47 new_req.setRawHeader("Accept-Language", QString("%1;q=0.8,en-US;q=0.6,en;q=0.4").arg(mscore->getLocaleISOCode()).toAscii());
48 return QNetworkAccessManager::createRequest(op, new_req, outgoingData);
49 }
50
51 //---------------------------------------------------------
52 // MyWebPage
53 //---------------------------------------------------------
54
MyWebPage(QObject * parent)55 MyWebPage::MyWebPage(QObject *parent)
56 : QWebPage(parent)
57 {
58 // Enable plugin support
59 settings()->setAttribute(QWebSettings::PluginsEnabled, true);
60 }
61
62 //---------------------------------------------------------
63 // createPlugin
64 //---------------------------------------------------------
65
createPlugin(const QString &,const QUrl &,const QStringList &,const QStringList &)66 QObject* MyWebPage::createPlugin(
67 const QString &/*classid*/,
68 const QUrl &/*url*/,
69 const QStringList &/*paramNames*/,
70 const QStringList &/*paramValues*/)
71 {
72 // Create the widget using QUiLoader.
73 // This means that the widgets don't need to be registered
74 // with the meta object system.
75 // On the other hand, non-gui objects can't be created this
76 // way. When we'd like to create non-visual objects in
77 // Html to use them via JavaScript, we'd use a different
78 // mechanism than this.
79 #if 0
80 if (classid == "WebScoreView") {
81 WebScoreView* sv = new WebScoreView(view());
82 int idx = paramNames.indexOf("score");
83 if (idx != -1) {
84 QString score = paramValues[idx];
85 sv->setScore(paramValues[idx]);
86 }
87 else {
88 qDebug("create WebScoreView: property score not found(%d)",
89 paramNames.size());
90 }
91 return sv;
92 }
93 #endif
94 return 0;
95
96 /*QUiLoader loader;
97 return loader.createWidget(classid, view());*/
98 }
99
100 //---------------------------------------------------------
101 // MyWebView
102 //---------------------------------------------------------
103
MyWebView(QWidget * parent)104 MyWebView::MyWebView(QWidget *parent):
105 QWebView(parent),
106 m_page(this)
107 {
108 // Set the page of our own PageView class, MyPageView,
109 // because only objects of this class will handle
110 // object-tags correctly.
111
112 m_page.setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
113 QNetworkAccessManager *networkManager = new MyNetworkAccessManager(this);
114 #ifndef QT_NO_OPENSSL
115 connect(networkManager,SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)),this, SLOT(ignoreSSLErrors(QNetworkReply*,QList<QSslError>)));
116 #endif
117 m_page.setNetworkAccessManager(networkManager);
118 setPage(&m_page);
119 setZoomFactor(guiScaling);
120
121 //set cookie jar for persistent cookies
122 CookieJar* jar = new CookieJar(QString(dataPath + "/cookies.txt"));
123 page()->networkAccessManager()->setCookieJar(jar);
124
125 progressBar = 0;
126 connect(this, SIGNAL(linkClicked(const QUrl&)), SLOT(link(const QUrl&)));
127 }
128
129 //---------------------------------------------------------
130 // ~MyWebView
131 //---------------------------------------------------------
132
~MyWebView()133 MyWebView::~MyWebView()
134 {
135 disconnect(this, SIGNAL(loadFinished(bool)), this, SLOT(stopBusy(bool)));
136 }
137
138 #ifndef QT_NO_OPENSSL
139 /**
140 Slot connected to the sslErrors signal of QNetworkAccessManager
141 When this slot is called, call ignoreSslErrors method of QNetworkReply
142 */
ignoreSSLErrors(QNetworkReply * reply,QList<QSslError> sslErrors)143 void MyWebView::ignoreSSLErrors(QNetworkReply *reply, QList<QSslError> sslErrors)
144 {
145 foreach (const QSslError &error, sslErrors)
146 qDebug("Ignore SSL error: %d %s", error.error(), qPrintable(error.errorString()));
147 reply->ignoreSslErrors(sslErrors);
148 }
149 #endif
150
151 //---------------------------------------------------------
152 // stopBusy
153 //---------------------------------------------------------
154
stopBusy(bool val)155 void MyWebView::stopBusy(bool val)
156 {
157 if (!val) {
158 setHtml(QString("<html><head>"
159 "<script type=\"text/javascript\">"
160 "function closePermanently() { mscore.closeWebPanelPermanently(); return false;}"
161 "</script>"
162 "<link rel=\"stylesheet\" href=\"data/webview.css\" type=\"text/css\" /></head>"
163 "<body>"
164 "<div id=\"content\">"
165 "<div id=\"middle\">"
166 " <div class=\"title\" align=\"center\"><h2>%1</h2></div>"
167 " <ul><li>%2</li></ul>"
168 " <div align=\"center\"><a class=\"button\" href=\"#\" onclick=\"return panel.load();\">%3</a></div>"
169 " <div align=\"center\"><a class=\"close\" href=\"#\" onclick=\"return closePermanently();\">%4</div>"
170 "</div></div>"
171 "</body></html>")
172 .arg(tr("Could not\nconnect"))
173 .arg(tr("To connect with the community,\nyou need to have internet\nconnection enabled") )
174 .arg(tr("Retry"))
175 .arg(tr("Close this permanently")).replace("\n", "<br/>"),
176 QUrl("qrc:/"));
177 }
178 mscore->hideProgressBar();
179 setCursor(Qt::ArrowCursor);
180 }
181
182 //---------------------------------------------------------
183 // setBusy
184 //---------------------------------------------------------
185
setBusy()186 void MyWebView::setBusy()
187 {
188 setCursor(Qt::WaitCursor);
189 }
190
191 //---------------------------------------------------------
192 // link
193 //---------------------------------------------------------
194
link(const QUrl & url)195 void MyWebView::link(const QUrl& url)
196 {
197 QString path(url.path());
198 QFileInfo fi(path);
199 if (fi.suffix() == "mscz" || fi.suffix() == "xml"
200 || fi.suffix() == "musicxml" || fi.suffix() == "mxl")
201 mscore->loadFile(url);
202 else if(url.host().startsWith("connect."))
203 load(QNetworkRequest(url));
204 else
205 QDesktopServices::openUrl(url);
206 }
207
208 //---------------------------------------------------------
209 // sizeHint
210 //---------------------------------------------------------
211
sizeHint() const212 QSize MyWebView::sizeHint() const
213 {
214 return QSize(300 , 300);
215 }
216
217 //---------------------------------------------------------
218 // WebPageDockWidget
219 //---------------------------------------------------------
220
WebPageDockWidget(MuseScore *,QWidget * parent)221 WebPageDockWidget::WebPageDockWidget(MuseScore* /*mscore*/, QWidget* parent)
222 : QDockWidget(parent)
223 {
224 setWindowTitle("MuseScore Connect");
225 setFloating(false);
226 setFeatures(QDockWidget::DockWidgetClosable);
227
228 setObjectName("webpage");
229 setAllowedAreas(Qt::LeftDockWidgetArea);
230
231 #if defined(WIN_PORTABLE)
232 QWebEngineProfile* defaultProfile = QWebEngineProfile::defaultProfile();
233 defaultProfile->setCachePath(QDir::cleanPath(QString("%1/../../../Data/settings/QWebEngine").arg(QCoreApplication::applicationDirPath())));
234 defaultProfile->setPersistentStoragePath(QDir::cleanPath(QString("%1/../../../Data/settings/QWebEngine").arg(QCoreApplication::applicationDirPath())));
235 #endif
236 web = new MyWebView;
237 web->setContextMenuPolicy(Qt::PreventContextMenu);
238 QWebFrame* frame = web->webPage()->mainFrame();
239 connect(frame, SIGNAL(javaScriptWindowObjectCleared()), this, SLOT(addToJavascript()));
240
241 connect(web, SIGNAL(loadFinished(bool)), web, SLOT(stopBusy(bool)));
242 web->setBusy();
243 web->load(QNetworkRequest(webUrl()));
244
245 setWidget(web);
246
247 //removing every widget from the tabbing order until support for
248 //accessibility is provided
249 QList<QWidget*> widgets = this->findChildren<QWidget*>();
250 for(int i = 0; i < widgets.size(); i++){
251 QWidget* currentWidget = widgets.at(i);
252 switch (currentWidget->focusPolicy()){
253 case Qt::TabFocus:
254 currentWidget->setFocusPolicy(Qt::NoFocus);
255 break;
256 case Qt::WheelFocus:
257 case Qt::StrongFocus:
258 currentWidget->setFocusPolicy(Qt::ClickFocus);
259 break;
260 case Qt::ClickFocus:
261 case Qt::NoFocus:
262 break;
263 }
264 }
265 }
266
267 //---------------------------------------------------------
268 // addToJavascript
269 //---------------------------------------------------------
270
addToJavascript()271 void WebPageDockWidget::addToJavascript()
272 {
273 QWebFrame* frame = web->webPage()->mainFrame();
274 frame->addToJavaScriptWindowObject("panel", this);
275 frame->addToJavaScriptWindowObject("mscore", mscore);
276 }
277
currentScore()278 QObject* WebPageDockWidget::currentScore() {
279 QObject* score = mscore->currentScore();
280 return score;
281 }
282
283 //---------------------------------------------------------
284 // load
285 //---------------------------------------------------------
286
load()287 void WebPageDockWidget::load()
288 {
289 web->setBusy();
290 web->load(QNetworkRequest(webUrl()));
291 }
292
saveCurrentScoreOnline(QString action,QVariantMap parameters,QString fileFieldName)293 bool WebPageDockWidget::saveCurrentScoreOnline(QString action, QVariantMap parameters, QString fileFieldName)
294 {
295 qDebug("saveCurrentOnline");
296 QWebPage * page = web->webPage();
297 QNetworkAccessManager* manager = page->networkAccessManager();
298
299 QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
300
301 QMap<QString, QVariant>::const_iterator i = parameters.constBegin();
302 while (i != parameters.constEnd()) {
303 QHttpPart part;
304 part.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant(QString("form-data; name=\"%1\"").arg(i.key())));
305 part.setBody(i.value().toString().toLatin1());
306 multiPart->append(part);
307 //qDebug("%s ", qPrintable(i.key()));
308 //qDebug("%s ", qPrintable(i.value().toString()));
309 ++i;
310 }
311
312 if(!fileFieldName.isEmpty()) {
313 QDir dir;
314 QFile *file = new QFile(dir.tempPath() + "/temp.mscz");
315 Score* score = mscore->currentScore();
316 if(score) {
317 mscore->saveAs(score, true, file->fileName(), "mscz");
318 }
319 else {
320 delete multiPart;
321 return false;
322 }
323 QHttpPart filePart;
324 filePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream"));
325 filePart.setRawHeader("Content-Transfer-Encoding", "binary");
326 filePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant(QString("form-data; name=\"%1\"; filename=\"temp.mscz\"").arg(fileFieldName)));
327 file->open(QIODevice::ReadOnly);
328 filePart.setBodyDevice(file);
329 file->setParent(multiPart); // we cannot delete the file now, so delete it with the multiPart
330 multiPart->append(filePart);
331 }
332
333 QUrl url(action);
334 QNetworkRequest request(url);
335
336 //QNetworkAccessManager manager;
337 QNetworkReply *reply = manager->post(request, multiPart);
338 multiPart->setParent(reply); // delete the multiPart with the reply
339 // here connect signals etc.
340 connect(reply, SIGNAL(finished()),
341 this, SLOT(saveOnlineFinished()));
342
343 return true;
344 }
345
saveOnlineFinished()346 void WebPageDockWidget::saveOnlineFinished() {
347 qDebug("Save online finished");
348 // delete file
349 QDir dir;
350 QFile file(dir.tempPath() + "/temp.mscz");
351 file.remove();
352
353 QNetworkReply *reply = (QNetworkReply *)sender();
354 // Reading attributes of the reply
355 // e.g. the HTTP status code
356 int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
357 QString message = reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
358
359 // no error received?
360 if (reply->error() == QNetworkReply::NoError) {
361 //deal with redirect
362 if (300 <= httpStatus && httpStatus < 400) {
363 qDebug("Redirecting to: %s", qPrintable(reply->url().toString()));
364 web->load(QNetworkRequest(reply->url()));
365 }
366 else if (httpStatus == 200) {
367 //Reading bytes form the reply
368 QByteArray bytes = reply->readAll();
369 QString string(bytes);
370 web->setHtml(string);
371 }
372 else {
373 qDebug("Unknown HTTP status: %d - %s", httpStatus, qPrintable(message));
374 }
375 }
376 else { //error received
377 qDebug("Save online error %d, HTTP status: %d - %s", reply->error(), httpStatus, qPrintable(message));
378 }
379 reply->deleteLater();
380 }
381
setCurrentScoreSource(QString)382 bool WebPageDockWidget::setCurrentScoreSource(QString /*source*/)
383 {
384 Score* score = mscore->currentScore();
385 if(score) {
386 score->metaTags().insert("source", "");
387 return true;
388 }
389 else {
390 return false;
391 }
392 }
393
394 //---------------------------------------------------------
395 // webUrl
396 //---------------------------------------------------------
webUrl()397 QUrl WebPageDockWidget::webUrl()
398 {
399 return QUrl(staticUrl);
400 }
401
402 //---------------------------------------------------------
403 // CookieJar
404 //
405 // Once the QNetworkCookieJar object is deleted, all cookies it held will be
406 // discarded as well. If you want to save the cookies, you should derive from
407 // this class and implement the saving to disk to your own storage format.
408 // (From QNetworkCookieJar documentation.)
409 //---------------------------------------------------------
410
CookieJar(QString path,QObject * parent)411 CookieJar::CookieJar(QString path, QObject *parent)
412 : QNetworkCookieJar(parent)
413 {
414 file = path;
415 QFile cookieFile(this->file);
416
417 if (cookieFile.exists() && cookieFile.open(QIODevice::ReadOnly)) {
418 QList<QNetworkCookie> list;
419 QByteArray line;
420
421 while(!(line = cookieFile.readLine()).isNull()) {
422 list.append(QNetworkCookie::parseCookies(line));
423 }
424 setAllCookies(list);
425 }
426 else {
427 if (MScore::debugMode)
428 qDebug() << "Can't open "<< this->file << " to read cookies";
429 }
430 }
431
432 //---------------------------------------------------------
433 // ~CookieJar
434 //---------------------------------------------------------
435
~CookieJar()436 CookieJar::~CookieJar()
437 {
438 QList <QNetworkCookie> cookieList;
439 cookieList = allCookies();
440
441 QFile file(this->file);
442
443 if(!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
444 if (MScore::debugMode)
445 qDebug() << "Can't open "<< this->file << " to save cookies";
446 return;
447 }
448
449 QTextStream out(&file);
450 for(int i = 0 ; i < cookieList.size() ; i++) {
451 //get cookie data
452 QNetworkCookie cookie = cookieList.at(i);
453 if (!cookie.isSessionCookie()) {
454 QByteArray line = cookieList.at(i).toRawForm(QNetworkCookie::Full);
455 out << line << "\n";
456 }
457 }
458 file.close();
459 }
460
461 #if 0
462 //---------------------------------------------------------
463 // WebScoreView
464 //---------------------------------------------------------
465
466 WebScoreView::WebScoreView(QWidget* parent)
467 : ScoreView(parent)
468 {
469 networkManager = 0;
470 }
471
472 WebScoreView::WebScoreView(const WebScoreView& wsv)
473 : ScoreView((QWidget*)(wsv.parent()))
474 {
475 networkManager = 0;
476 }
477
478 //---------------------------------------------------------
479 // setScore
480 //---------------------------------------------------------
481
482 void WebScoreView::setScore(const QString& url)
483 {
484 if (!networkManager) {
485 networkManager = new QNetworkAccessManager(this);
486 connect(networkManager, SIGNAL(finished(QNetworkReply*)),
487 SLOT(networkFinished(QNetworkReply*)));
488 }
489 networkManager->get(QNetworkRequest(QUrl(url)));
490 }
491
492 //---------------------------------------------------------
493 // networkFinished
494 //---------------------------------------------------------
495
496 void WebScoreView::networkFinished(QNetworkReply* reply)
497 {
498 if (reply->error() != QNetworkReply::NoError) {
499 if (MScore::debugMode)
500 qDebug("Error while checking update [%s]", qPrintable(reply->errorString()));
501 return;
502 }
503 QByteArray ha = reply->rawHeader("Content-Disposition");
504 QString s(ha);
505 QString name;
506 QRegExp re(".*filename=\"(.*)\"");
507 if (s.isEmpty() || re.indexIn(s) == -1)
508 name = "unknown.mscz";
509 else
510 name = re.cap(1);
511
512 QByteArray data = reply->readAll();
513 QString tmpName = QDir::tempPath () + "/"+ name;
514 QFile f(tmpName);
515 f.open(QIODevice::WriteOnly);
516 f.write(data);
517 f.close();
518
519 Score* score = mscore->readScore(tmpName);
520 if (!score) {
521 qDebug("readScore failed");
522 return;
523 }
524 ScoreView::setScore(score);
525 update();
526 }
527
528 #endif
529 }
530
531