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