1 //=============================================================================
2 //  MuseScore
3 //  Music Composition & Notation
4 //
5 //  Copyright (C) 2002-2013 Werner Schweer
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 //  as published by the Free Software Foundation and appearing in
10 //  the file LICENCE.GPL
11 //=============================================================================
12 
13 #include "resourceManager.h"
14 #include "musescore.h"
15 #include "extension.h"
16 #include "libmscore/utils.h"
17 #include "stringutils.h"
18 #include "thirdparty/qzip/qzipreader_p.h"
19 
20 namespace Ms {
21 
22 extern QString dataPath;
23 extern QString mscoreGlobalShare;
24 
ResourceManager(QWidget * parent)25 ResourceManager::ResourceManager(QWidget *parent) :
26       QDialog(parent)
27       {
28       setObjectName("ResourceManager");
29       setupUi(this);
30       setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
31       QDir dir;
32       dir.mkpath(dataPath + "/locale");
33       displayExtensions();
34       displayLanguages();
35       languagesTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
36       languagesTable->verticalHeader()->hide();
37       extensionsTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
38       extensionsTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Fixed);
39       extensionsTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed);
40       extensionsTable->verticalHeader()->hide();
41       extensionsTable->setColumnWidth(1, 50);
42       extensionsTable->setColumnWidth(1, 100);
43       MuseScore::restoreGeometry(this);
44       }
45 
46 //---------------------------------------------------------
47 //   ExtensionFileSize
48 //---------------------------------------------------------
49 
ExtensionFileSize(const int i)50 ExtensionFileSize::ExtensionFileSize(const int i)
51    : QTableWidgetItem(stringutils::convertFileSizeToHumanReadable(i), QTableWidgetItem::UserType)
52      , _size(i)
53       {}
54 
55 //---------------------------------------------------------
56 //   operator<
57 //---------------------------------------------------------
58 
operator <(const QTableWidgetItem & nextItem) const59 bool ExtensionFileSize::operator<(const QTableWidgetItem& nextItem) const
60       {
61       if (nextItem.type() != type())
62             return false;
63       return getSize() < static_cast<const ExtensionFileSize&>(nextItem).getSize();
64       }
65 
66 //---------------------------------------------------------
67 //   LanguageFileSize
68 //---------------------------------------------------------
69 
LanguageFileSize(const double d)70 LanguageFileSize::LanguageFileSize(const double d)
71    : QTableWidgetItem(ResourceManager::tr("%1 KB").arg(d), QTableWidgetItem::UserType)
72      , _size(d)
73       {}
74 
75 //---------------------------------------------------------
76 //   operator<
77 //---------------------------------------------------------
78 
operator <(const QTableWidgetItem & nextItem) const79 bool LanguageFileSize::operator<(const QTableWidgetItem& nextItem) const
80       {
81       if (nextItem.type() != type())
82             return false;
83       return getSize() < static_cast<const LanguageFileSize&>(nextItem).getSize();
84       }
85 
86 //---------------------------------------------------------
87 //   selectLanguagesTab
88 //---------------------------------------------------------
89 
selectLanguagesTab()90 void ResourceManager::selectLanguagesTab()
91       {
92       tabs->setCurrentIndex(tabs->indexOf(languages));
93       }
94 
95 //---------------------------------------------------------
96 //   selectExtensionsTab
97 //---------------------------------------------------------
98 
selectExtensionsTab()99 void ResourceManager::selectExtensionsTab()
100       {
101       tabs->setCurrentIndex(tabs->indexOf(extensions));
102       }
103 
104 
105 //---------------------------------------------------------
106 //   displayExtensions
107 //---------------------------------------------------------
108 
displayExtensions()109 void ResourceManager::displayExtensions()
110       {
111       DownloadUtils js(this);
112       js.setTarget(baseAddr() + "extensions/details.json");
113       js.download();
114       QByteArray json = js.returnData();
115 
116       // parse the json file
117       QJsonParseError err;
118       QJsonDocument result = QJsonDocument::fromJson(json, &err);
119       if (err.error != QJsonParseError::NoError || !result.isObject()) {
120             qDebug("An error occurred during parsing");
121             return;
122             }
123       int rowCount = result.object().keys().size();
124       rowCount -= 2; //version and type
125       extensionsTable->setRowCount(rowCount);
126 
127       int row = 0;
128       int col = 0;
129       QPushButton* buttonInstall;
130       QPushButton* buttonUninstall;
131       extensionsTable->verticalHeader()->show();
132 
133       QStringList exts = result.object().keys();
134       for (QString key : exts) {
135             if (!result.object().value(key).isObject())
136                   continue;
137             QJsonObject value = result.object().value(key).toObject();
138             col = 0;
139             QString test = value.value("file_name").toString();
140             if (test.length() == 0)
141                   continue;
142 
143             QString filename = value.value("file_name").toString();
144             QString name = value.value("name").toString();
145             int fileSize = value.value("file_size").toInt();
146             QString hashValue = value.value("hash").toString();
147             QString version = value.value("version").toString();
148 
149             extensionsTable->setItem(row, col++, new QTableWidgetItem(name));
150             extensionsTable->setItem(row, col++, new QTableWidgetItem(version));
151             extensionsTable->setItem(row, col++, new ExtensionFileSize(fileSize));
152             buttonInstall = new QPushButton(tr("Install"));
153             buttonUninstall = new QPushButton(tr("Uninstall"));
154 
155             connect(buttonInstall, SIGNAL(clicked()), this, SLOT(downloadExtension()));
156             connect(buttonUninstall, SIGNAL(clicked()), this, SLOT(uninstallExtension()));
157             buttonInstall->setProperty("path", "extensions/" + filename);
158             buttonInstall->setProperty("hash", hashValue);
159             buttonInstall->setProperty("rowId", row);
160             buttonUninstall->setProperty("extensionId", key);
161             buttonUninstall->setProperty("rowId", row);
162 
163             // get the installed version of the extension if any
164             if (Extension::isInstalled(key)) {
165                   buttonUninstall->setDisabled(false);
166                   QString installedVersion = Extension::getLatestVersion(key);
167                   if (compareVersion(installedVersion, version)) {
168                         buttonInstall->setText(tr("Update"));
169                         }
170                   else {
171                         buttonInstall->setText(tr("Up to date"));
172                         buttonInstall->setDisabled(true);
173                         }
174                   }
175             else {
176                   buttonUninstall->setDisabled(true);
177                   }
178             extensionsTable->setIndexWidget(extensionsTable->model()->index(row, col++), buttonInstall);
179             extensionsTable->setIndexWidget(extensionsTable->model()->index(row, col++), buttonUninstall);
180             row++;
181             }
182       }
183 
184 //---------------------------------------------------------
185 //   displayLanguages
186 //---------------------------------------------------------
187 
displayLanguages()188 void ResourceManager::displayLanguages()
189       {
190       // Download details.json
191       DownloadUtils js(this);
192       js.setTarget(baseAddr() + "languages/details.json");
193       js.download();
194       QByteArray json = js.returnData();
195       qDebug() << json;
196 
197       // parse the json file
198       QJsonParseError err;
199       QJsonDocument result = QJsonDocument::fromJson(json, &err);
200       if (err.error != QJsonParseError::NoError || !result.isObject()) {
201             qDebug("An error occurred during parsing");
202             return;
203             }
204       int rowCount = result.object().keys().size();
205       rowCount -= 2; //version and type
206       languagesTable->setRowCount(rowCount);
207 
208       int row = 0;
209       int col = 0;
210 #if (!defined (_MSCVER) && !defined (_MSC_VER))
211       QPushButton* updateButtons[rowCount];
212 #else
213       // MSVC does not support VLA. Replace with std::vector. If profiling determines that the
214       //    heap allocation is slow, an optimization might be used.
215       std::vector<QPushButton*> updateButtons(rowCount);
216 #endif
217       QPushButton* temp;
218       languagesTable->verticalHeader()->show();
219 
220       // move current language to first row
221       QStringList langs = result.object().keys();
222       QString currentLocaleISOCode = mscore->getLocaleISOCode();
223       int index = langs.indexOf(currentLocaleISOCode);
224       if (index < 0 && currentLocaleISOCode.size() > 2) {
225             currentLocaleISOCode = currentLocaleISOCode.left(2);
226             index = langs.indexOf(currentLocaleISOCode);
227             }
228       QString currentLanguageKey = "";
229       if (index >= 0) {
230             currentLanguageKey = langs.takeAt(index);
231             langs.prepend(currentLanguageKey);
232             }
233 
234       for (QString key : langs) {
235             if (!result.object().value(key).isObject())
236                   continue;
237             QJsonObject object = result.object().value(key).toObject();
238             col = 0;
239             QString test = object.value("file_name").toString();
240             if (test.length() == 0)
241                   continue;
242 
243             QString filename = object.value("file_name").toString();
244             QString name = object.value("name").toString();
245             double fileSize = object.value("file_size").toString().toDouble();
246             QString hashValue = object.value("hash").toString();
247 
248             languagesTable->setItem(row, col++, new QTableWidgetItem(name));
249             languagesTable->setItem(row, col++, new QTableWidgetItem(filename));
250             languagesTable->setItem(row, col++, new LanguageFileSize(fileSize));
251             updateButtons[row] = new QPushButton(tr("Update"));
252 
253             temp = updateButtons[row];
254             languageButtonMap[temp] = "languages/" + filename;
255             languageButtonHashMap[temp] = hashValue;
256 
257             languagesTable->setIndexWidget(languagesTable->model()->index(row, col++), temp);
258 
259             if (key == currentLanguageKey)
260                   currentLanguageButton = temp;
261 
262             // get hash mscore and instruments
263             QJsonObject mscoreObject = object.value("mscore").toObject();
264             QString hashMscore = mscoreObject.value("hash").toString();
265             QString filenameMscore = mscoreObject.value("file_name").toString();
266 
267             bool mscoreUpToDate = verifyLanguageFile(filenameMscore, hashMscore);
268 
269             QJsonObject instrumentsObject = object.value("instruments").toObject();
270             QString hashInstruments = instrumentsObject.value("hash").toString();
271             QString filenameInstruments = instrumentsObject.value("file_name").toString();
272 
273             bool instrumentsUpToDate = verifyLanguageFile(filenameInstruments, hashInstruments);
274 
275             QJsonObject toursObject = object.value("tours").toObject();
276             QString hashTours = toursObject.value("hash").toString();
277             QString filenameTours = toursObject.value("file_name").toString();
278 
279             bool toursUpToDate = verifyLanguageFile(filenameTours, hashTours);
280 
281             if (mscoreUpToDate && instrumentsUpToDate && toursUpToDate) { // compare local file with distant hash
282                   temp->setText(tr("Up to date"));
283                   temp->setDisabled(1);
284                   }
285             else {
286                   connect(temp, SIGNAL(clicked()), this, SLOT(downloadLanguage()));
287                   }
288             row++;
289             }
290       }
291 
292 //---------------------------------------------------------
293 //   verifyLanguageFile
294 //---------------------------------------------------------
295 
verifyLanguageFile(QString filename,QString hash)296 bool ResourceManager::verifyLanguageFile(QString filename, QString hash)
297       {
298       QString local = dataPath + "/locale/" + filename;
299       QString global = mscoreGlobalShare + "locale/" + filename;
300       QFileInfo fileLocal(local);
301       QFileInfo fileGlobal(global);
302       if(!fileLocal.exists() || (fileLocal.lastModified() <= fileGlobal.lastModified()) )
303             local = mscoreGlobalShare + "locale/" + filename;
304 
305       return verifyFile(local, hash);
306       }
307 
308 //---------------------------------------------------------
309 //   downloadLanguage
310 //---------------------------------------------------------
311 
downloadLanguage()312 void ResourceManager::downloadLanguage()
313       {
314       QPushButton *button = static_cast<QPushButton*>( sender() );
315       QString languageFileName = languageButtonMap[button];
316       QString hash = languageButtonHashMap[button];
317       button->setText(tr("Updating"));
318       button->setDisabled(true);
319 
320       QString baseAddress = baseAddr() + languageFileName;
321       DownloadUtils dl(this);
322       dl.setTarget(baseAddress);
323       QString localPath = dataPath + "/locale/" + languageFileName.split('/')[1];
324       dl.setLocalFile(localPath);
325       dl.download();
326       if (!dl.saveFile() || !verifyFile(localPath, hash)) {
327             button->setText(tr("Failed, try again"));
328             button->setEnabled(1);
329             }
330       else {
331             // unzip and delete
332             MQZipReader zipFile(localPath);
333             QFileInfo zfi(localPath);
334             QString destinationDir(zfi.absolutePath());
335             QVector<MQZipReader::FileInfo> allFiles = zipFile.fileInfoList();
336             bool success = true;
337             foreach (MQZipReader::FileInfo fi, allFiles) {
338                   const QString absPath = destinationDir + "/" + fi.filePath;
339                   if (fi.isFile) {
340                         QFile f(absPath);
341                         if (!f.open(QIODevice::WriteOnly)) {
342                               success = false;
343                               break;
344                               }
345                         f.write(zipFile.fileData(fi.filePath));
346                         f.setPermissions(fi.permissions);
347                         f.close();
348                         }
349                   }
350             zipFile.close();
351             if (success) {
352                   QFile::remove(localPath);
353                   button->setText(tr("Up to date"));
354                   //  retranslate the UI if current language is updated
355                   if (button == currentLanguageButton)
356                         setMscoreLocale(localeName());
357                   }
358             else {
359                   button->setText(tr("Failed, try again"));
360                   button->setEnabled(1);
361                   }
362             }
363       }
364 
365 //---------------------------------------------------------
366 //   downloadExtension
367 //---------------------------------------------------------
368 
downloadExtension()369 void ResourceManager::downloadExtension()
370       {
371       QPushButton* button = static_cast<QPushButton*>(sender());
372       QString path  = button->property("path").toString();
373       QString hash = button->property("hash").toString();
374       button->setText(tr("Updating"));
375       button->setDisabled(true);
376       QString baseAddress = baseAddr() + path;
377       DownloadUtils dl(this);
378       dl.setTarget(baseAddress);
379       QString localPath = QDir::tempPath() + "/" + path.split('/')[1];
380       QFile::remove(localPath);
381       dl.setLocalFile(localPath);
382       dl.download(true);
383       bool saveFileRes = dl.saveFile();
384       bool verifyFileRes = saveFileRes && verifyFile(localPath, hash);
385       if(!verifyFileRes) {
386             QFile::remove(localPath);
387             button->setText(tr("Failed, try again"));
388             button->setEnabled(true);
389 
390             QMessageBox msgBox;
391             msgBox.setStandardButtons(QMessageBox::Ok);
392             msgBox.setTextFormat(Qt::RichText);
393             msgBox.setWindowTitle(tr("Extensions Installation Failed"));
394             if (!saveFileRes) //failed to save file on disk
395                   msgBox.setText(tr("Unable to save the extension file on disk"));
396             else //failed to verify package, so size or hash sum is incorrect
397                   msgBox.setText(tr("Unable to download, save and verify the package.\nCheck your internet connection."));
398             msgBox.exec();
399             }
400       else {
401             bool result = mscore->importExtension(localPath);
402             if (result) {
403                   QFile::remove(localPath);
404                   button->setText(tr("Up to date"));
405                   // find uninstall button and make it visible
406                   int rowId = button->property("rowId").toInt();
407                   QPushButton* uninstallButton = static_cast<QPushButton*>(extensionsTable->indexWidget(extensionsTable->model()->index(rowId, 4)));
408                   uninstallButton->setDisabled(false);
409                   }
410             else {
411                   button->setText(tr("Failed, try again"));
412                   button->setEnabled(1);
413                   }
414             }
415       }
416 
417 //---------------------------------------------------------
418 //   uninstallExtension
419 //---------------------------------------------------------
420 
uninstallExtension()421 void ResourceManager::uninstallExtension()
422       {
423       QPushButton* uninstallButton = static_cast<QPushButton*>(sender());
424       QString extensionId = uninstallButton->property("extensionId").toString();
425       if (mscore->uninstallExtension(extensionId)) {
426             // find uninstall button and make it visible
427             int rowId = uninstallButton->property("rowId").toInt();
428             QPushButton* installButton = static_cast<QPushButton*>(extensionsTable->indexWidget(extensionsTable->model()->index(rowId, 3)));
429             installButton->setText("Install");
430             installButton->setDisabled(false);
431             uninstallButton->setDisabled(true);
432             }
433       }
434 
435 //---------------------------------------------------------
436 //   verifyFile
437 //---------------------------------------------------------
438 
verifyFile(QString path,QString hash)439 bool ResourceManager::verifyFile(QString path, QString hash)
440       {
441       QFile file(path);
442       QCryptographicHash localHash(QCryptographicHash::Sha1);
443       if(file.open(QIODevice::ReadOnly)) {
444             localHash.reset();
445             localHash.addData(file.readAll());
446             QString hashValue2 = QString(localHash.result().toHex());
447             if(hash == hashValue2)
448                   return true;
449             }
450       return false;
451       }
452 
453 //---------------------------------------------------------
454 //   hideEvent
455 //---------------------------------------------------------
456 
hideEvent(QHideEvent * event)457 void ResourceManager::hideEvent(QHideEvent* event)
458       {
459       MuseScore::saveGeometry(this);
460       QWidget::hideEvent(event);
461       }
462 
463 }
464