LCOV - code coverage report
Current view: top level - muse/framework/languages/internal - languagesservice.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 328 0
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2025 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : #include "languagesservice.h"
      23              : 
      24              : #include <QBuffer>
      25              : #include <QCryptographicHash>
      26              : #include <QDir>
      27              : #include <QGuiApplication>
      28              : #include <QJsonDocument>
      29              : #include <QJsonObject>
      30              : #include <QJsonParseError>
      31              : #include <QQmlEngine>
      32              : #include <QTranslator>
      33              : 
      34              : #include "languageserrors.h"
      35              : 
      36              : #include "multiwindows/resourcelockguard.h"
      37              : 
      38              : #include "global/io/buffer.h"
      39              : #include "global/serialization/zipreader.h"
      40              : #include "global/translation.h"
      41              : #include "global/log.h"
      42              : 
      43              : using namespace Qt::Literals::StringLiterals;
      44              : 
      45              : using namespace muse;
      46              : using namespace muse::languages;
      47              : using namespace muse::network;
      48              : 
      49              : static const std::string LANGUAGES_RESOURCE_NAME("LANGUAGES");
      50              : 
      51            0 : void LanguagesService::init()
      52              : {
      53            0 :     TRACEFUNC;
      54              : 
      55            0 :     fileSystem()->makePath(configuration()->languagesUserAppDataPath());
      56              : 
      57            0 :     loadLanguages();
      58              : 
      59            0 :     ValCh<QString> languageCode = configuration()->currentLanguageCode();
      60            0 :     setCurrentLanguage(languageCode.val);
      61              : 
      62              :     // Remember the active language code so the "Restart required" text can go away if the language
      63              :     // is changed and later reverted in the same session. Cannot use m_currentLanguage.code
      64              :     // because m_currentLanguage holds the effective language: if the language code is "system",
      65              :     // m_currentLanguage will hold "en-us" for example (whatever the system is set to).
      66            0 :     QString activeLanguageCode = languageCode.val;
      67            0 :     languageCode.ch.onReceive(this, [this, activeLanguageCode](const QString& newLanguageCode) {
      68              :         //! NOTE To change the language at the moment, a restart is required
      69            0 :         m_restartRequiredToApplyLanguage = newLanguageCode != activeLanguageCode;
      70            0 :         m_restartRequiredToApplyLanguageChanged.send(m_restartRequiredToApplyLanguage);
      71            0 :     });
      72              : 
      73            0 :     m_inited = true;
      74            0 : }
      75              : 
      76            0 : const LanguagesHash& LanguagesService::languages() const
      77              : {
      78            0 :     return m_languagesHash;
      79              : }
      80              : 
      81            0 : Language LanguagesService::language(const QString& languageCode) const
      82              : {
      83            0 :     QString effectiveLanguageCode = this->effectiveLanguageCode(languageCode);
      84            0 :     if (effectiveLanguageCode.isEmpty()) {
      85            0 :         return {};
      86              :     }
      87              : 
      88            0 :     if (effectiveLanguageCode == PLACEHOLDER_LANGUAGE_CODE) {
      89            0 :         return m_placeholderLanguage;
      90              :     }
      91              : 
      92            0 :     return m_languagesHash[effectiveLanguageCode];
      93            0 : }
      94              : 
      95            0 : const Language& LanguagesService::currentLanguage() const
      96              : {
      97            0 :     return m_currentLanguage;
      98              : }
      99              : 
     100            0 : async::Notification LanguagesService::currentLanguageChanged() const
     101              : {
     102            0 :     return m_currentLanguageChanged;
     103              : }
     104              : 
     105            0 : const Language& LanguagesService::placeholderLanguage() const
     106              : {
     107            0 :     return m_placeholderLanguage;
     108              : }
     109              : 
     110            0 : bool LanguagesService::hasPlaceholderLanguage() const
     111              : {
     112            0 :     for (const QString& resourceName : configuration()->languageResourceNames()) {
     113            0 :         if (!fileSystem()->exists(configuration()->builtinLanguageFilePath(resourceName, PLACEHOLDER_LANGUAGE_CODE))) {
     114            0 :             return false;
     115              :         }
     116            0 :     }
     117              : 
     118            0 :     return true;
     119              : }
     120              : 
     121            0 : void LanguagesService::loadLanguages()
     122              : {
     123            0 :     TRACEFUNC;
     124              : 
     125            0 :     RetVal<ByteArray> languagesJson;
     126              :     {
     127            0 :         mi::ReadResourceLockGuard lock_guard(multiwindowsProvider(), LANGUAGES_RESOURCE_NAME);
     128            0 :         const io::path_t appLanguagesJsonPath = configuration()->appLanguagesJsonPath();
     129            0 :         if (!appLanguagesJsonPath.empty()) {
     130            0 :             languagesJson = fileSystem()->readFile(appLanguagesJsonPath);
     131              :         }
     132              : 
     133            0 :         if (!languagesJson.ret) {
     134            0 :             languagesJson = fileSystem()->readFile(configuration()->builtinLanguagesJsonPath());
     135              :         }
     136            0 :     }
     137            0 :     if (!languagesJson.ret) {
     138            0 :         LOGE() << "Failed to read languages.json: " << languagesJson.ret.toString();
     139            0 :         return;
     140              :     }
     141              : 
     142            0 :     QJsonParseError err;
     143            0 :     QJsonDocument jsonDoc = QJsonDocument::fromJson(languagesJson.val.toQByteArrayNoCopy(), &err);
     144            0 :     if (err.error != QJsonParseError::NoError || !jsonDoc.isObject()) {
     145            0 :         LOGE() << "Failed to parse languages.json: " << err.errorString();
     146            0 :         return;
     147              :     }
     148              : 
     149            0 :     QJsonObject languagesObject = jsonDoc.object();
     150            0 :     for (auto it = languagesObject.begin(); it != languagesObject.end(); ++it) {
     151            0 :         QJsonObject langObject = it.value().toObject();
     152              : 
     153            0 :         Language lang;
     154            0 :         lang.code = it.key();
     155            0 :         lang.name = langObject.value("name").toString();
     156            0 :         lang.fallbackLanguages = langObject.value("fallbackLanguages").toVariant().toStringList();
     157              : 
     158            0 :         m_languagesHash.insert(it.key(), lang);
     159            0 :     }
     160              : 
     161            0 :     m_placeholderLanguage.code = PLACEHOLDER_LANGUAGE_CODE;
     162            0 :     m_placeholderLanguage.name = "«Placeholder translations»";
     163            0 : }
     164              : 
     165            0 : void LanguagesService::setCurrentLanguage(const QString& languageCode)
     166              : {
     167            0 :     TRACEFUNC;
     168              : 
     169            0 :     QString effectiveLanguageCode = this->effectiveLanguageCode(languageCode);
     170            0 :     if (effectiveLanguageCode.isEmpty()) {
     171            0 :         LOGE() << "Unsupported language code: " << languageCode;
     172            0 :         return;
     173              :     }
     174              : 
     175            0 :     if (effectiveLanguageCode == m_currentLanguage.code) {
     176            0 :         return;
     177              :     }
     178              : 
     179            0 :     Language& lang = languageCode == PLACEHOLDER_LANGUAGE_CODE
     180            0 :                      ? m_placeholderLanguage
     181            0 :                      : m_languagesHash[effectiveLanguageCode];
     182              : 
     183            0 :     if (!lang.isLoaded()) {
     184            0 :         loadLanguage(lang);
     185              :     }
     186              : 
     187            0 :     for (QTranslator* t : std::as_const(m_translators)) {
     188            0 :         QCoreApplication::instance()->removeTranslator(t);
     189            0 :         delete t;
     190              :     }
     191            0 :     m_translators.clear();
     192            0 :     m_translators.reserve(lang.files.size());
     193              : 
     194            0 :     auto installTranslatorsForLanguage = [this](const Language& l) {
     195            0 :         for (const io::path_t& file : l.files) {
     196            0 :             QTranslator* translator = new QTranslator();
     197            0 :             bool ok = translator->load(file.toQString());
     198            0 :             if (ok) {
     199            0 :                 QCoreApplication::instance()->installTranslator(translator);
     200            0 :                 m_translators.push_back(translator);
     201              :             } else {
     202            0 :                 LOGE() << "Error loading translator " << file.toQString();
     203            0 :                 delete translator;
     204              :             }
     205              :         }
     206            0 :     };
     207              : 
     208              :     // Install translators for fallback languages in reverse order:
     209              :     // Qt searches the most recently installed translator first,
     210              :     // and the least recently installed translator last.
     211            0 :     for (auto it = lang.fallbackLanguages.crbegin(); it != lang.fallbackLanguages.crend(); ++it) {
     212            0 :         Language& fallbackLang = m_languagesHash[*it];
     213            0 :         installTranslatorsForLanguage(fallbackLang);
     214              :     }
     215              : 
     216            0 :     installTranslatorsForLanguage(lang);
     217              : 
     218            0 :     QLocale locale(lang.code);
     219            0 :     QLocale::setDefault(locale);
     220            0 :     qGuiApp->setLayoutDirection(locale.textDirection());
     221              : 
     222            0 :     lang.direction = locale.textDirection();
     223              : 
     224              :     // Currently, no need to retranslate the UI on language change, because we require restart
     225              : 
     226            0 :     m_currentLanguage = lang;
     227            0 :     m_currentLanguageChanged.notify();
     228            0 : }
     229              : 
     230            0 : QString LanguagesService::effectiveLanguageCode(QString languageCode) const
     231              : {
     232            0 :     languageCode.replace(u'-', u'_');
     233              : 
     234            0 :     auto tryCode = [this](const QString& code) -> QString {
     235            0 :         if (m_languagesHash.contains(code)) {
     236            0 :             return code;
     237              :         }
     238              : 
     239              :         static const std::map<QString, QString> SPECIAL_CASES {
     240            0 :             { u"ca_valencia"_s, u"ca@valencia"_s },
     241            0 :             { u"ca_ES_valencia"_s, u"ca@valencia"_s },
     242            0 :             { u"en_AU"_s, u"en_GB"_s },
     243            0 :             { u"en_NZ"_s, u"en_GB"_s },
     244            0 :             { u"en"_s, u"en_US"_s },
     245            0 :             { u"hi"_s, u"hi_IN"_s },
     246            0 :             { u"mn"_s, u"mn_MN"_s },
     247            0 :             { u"zh"_s, u"zh_CN"_s }
     248            0 :         };
     249              : 
     250            0 :         auto it = SPECIAL_CASES.find(code);
     251            0 :         if (it != SPECIAL_CASES.cend()) {
     252            0 :             return it->second;
     253              :         }
     254              : 
     255              :         // Not found
     256            0 :         return QString();
     257            0 :     };
     258              : 
     259            0 :     if (languageCode.isEmpty() || languageCode == SYSTEM_LANGUAGE_CODE) {
     260            0 :         const QStringList systemLanguages = QLocale::system().uiLanguages();
     261            0 :         LOGI() << "System languages: " << systemLanguages;
     262              : 
     263            0 :         for (QString code : systemLanguages) {
     264            0 :             code.replace(u'-', u'_');
     265              : 
     266              :             // Prefer Swedish (Modern) over Swedish (Traditional)
     267              :             // when using system language.
     268            0 :             if (code == u"sv_SE"_s) {
     269            0 :                 code = u"sv"_s;
     270              :             }
     271              : 
     272            0 :             code = tryCode(code);
     273            0 :             if (!code.isEmpty()) {
     274            0 :                 return code;
     275              :             }
     276            0 :         }
     277              : 
     278              :         // Not found
     279            0 :         return QString();
     280            0 :     }
     281              : 
     282            0 :     if (languageCode == PLACEHOLDER_LANGUAGE_CODE) {
     283            0 :         return languageCode;
     284              :     }
     285              : 
     286            0 :     return tryCode(languageCode);
     287              : }
     288              : 
     289            0 : Ret LanguagesService::loadLanguage(Language& lang)
     290              : {
     291            0 :     Ret ret = doLoadLanguage(lang);
     292            0 :     if (!ret) {
     293            0 :         LOGE() << "Failed to load language " << lang.code << ": " << ret.toString();
     294            0 :         return ret;
     295              :     }
     296              : 
     297            0 :     for (const QString& fallbackCode : lang.fallbackLanguages) {
     298            0 :         Language& fallbackLang = m_languagesHash[fallbackCode];
     299              : 
     300            0 :         if (!fallbackLang.isLoaded()) {
     301            0 :             ret = doLoadLanguage(fallbackLang);
     302            0 :             if (!ret) {
     303            0 :                 LOGE() << "Failed to load fallback language " << fallbackLang.code << ": " << ret.toString();
     304            0 :                 return ret;
     305              :             }
     306              :         }
     307              :     }
     308              : 
     309            0 :     return muse::make_ok();
     310            0 : }
     311              : 
     312            0 : Ret LanguagesService::doLoadLanguage(Language& lang)
     313              : {
     314            0 :     io::path_t languagesAppDataPath = configuration()->languagesAppDataPath();
     315            0 :     io::path_t languagesUserAppDataPath = configuration()->languagesUserAppDataPath();
     316              : 
     317            0 :     QString languageSuffix = QString("_%1.qm").arg(lang.code);
     318              : 
     319            0 :     std::string filter = ("*" + languageSuffix).toStdString();
     320              : 
     321            0 :     RetVal<io::paths_t> appFilePaths = fileSystem()->scanFiles(languagesAppDataPath, { filter });
     322            0 :     if (!appFilePaths.ret) {
     323            0 :         LOGE() << "Failed to scan files for language " << lang.code << ": " << appFilePaths.ret.toString();
     324            0 :         return appFilePaths.ret;
     325              :     }
     326              : 
     327            0 :     for (const io::path_t& appFilePath : appFilePaths.val) {
     328            0 :         io::path_t filename = io::filename(appFilePath);
     329            0 :         io::path_t userFilePath = languagesUserAppDataPath.appendingComponent(filename);
     330              : 
     331            0 :         QFileInfo appFileInfo(appFilePath.toQString());
     332            0 :         QFileInfo userFileInfo(userFilePath.toQString());
     333            0 :         bool useUserPath = userFileInfo.exists() && userFileInfo.lastModified() > appFileInfo.lastModified();
     334              : 
     335            0 :         QString resourceName = filename.toQString().chopped(languageSuffix.size());
     336            0 :         lang.files[resourceName] = useUserPath ? userFilePath : appFilePath;
     337            0 :     }
     338              : 
     339            0 :     for (const QString& resourceName : configuration()->languageResourceNames()) {
     340            0 :         if (!lang.files.contains(resourceName)) {
     341            0 :             LOGE() << "Could not find resource " << resourceName << " for language " << lang.code;
     342            0 :             return muse::make_ret(muse::Ret::Code::InternalError);
     343              :         }
     344            0 :     }
     345              : 
     346            0 :     return muse::make_ok();
     347            0 : }
     348              : 
     349            0 : Progress LanguagesService::update(const QString& languageCode)
     350              : {
     351            0 :     if (m_languageUpdateInProgress) {
     352            0 :         LOGW() << "Language update already in progress";
     353            0 :         return {};
     354              :     }
     355              : 
     356            0 :     m_languageUpdateInProgress = true;
     357              : 
     358            0 :     QString effectiveLanguageCode = this->effectiveLanguageCode(languageCode);
     359            0 :     IF_ASSERT_FAILED(!effectiveLanguageCode.isEmpty()) {
     360            0 :         return {};
     361              :     }
     362              : 
     363            0 :     Language& lang = m_languagesHash[effectiveLanguageCode];
     364            0 :     if (!lang.isLoaded()) {
     365            0 :         loadLanguage(lang);
     366              :     }
     367              : 
     368            0 :     if (!m_networkManager) {
     369            0 :         m_networkManager = networkManagerCreator()->makeNetworkManager();
     370              :     }
     371              : 
     372            0 :     Progress progress;
     373            0 :     progress.start();
     374            0 :     progress.progress(0, 0, muse::trc("global", "Checking for updates…"));
     375              : 
     376            0 :     downloadServerLanguagesInfo(
     377              :         effectiveLanguageCode,
     378            0 :         [this, effectiveLanguageCode, progress]( const RetVal<QJsonObject>& res) mutable {
     379            0 :         if (!res.ret) {
     380            0 :             LOGE() << "Failed to download server languages info: " << res.ret.toString();
     381            0 :             progress.finish(res.ret);
     382            0 :             return;
     383              :         }
     384              : 
     385            0 :         QJsonObject serverLanguagesInfo = res.val;
     386            0 :         QStringList languagesToUpdate = this->languagesToUpdate(effectiveLanguageCode, serverLanguagesInfo);
     387              : 
     388            0 :         if (languagesToUpdate.isEmpty()) {
     389            0 :             m_languageUpdateInProgress = false;
     390            0 :             progress.finish(make_ret(Err::AlreadyUpToDate));
     391            0 :             return;
     392              :         }
     393              : 
     394            0 :         doUpdateLanguages(languagesToUpdate, progress, [this, progress](const Ret& ret) mutable {
     395            0 :             m_languageUpdateInProgress = false;
     396            0 :             progress.finish(ret);
     397              : 
     398            0 :             if (ret) {
     399            0 :                 m_restartRequiredToApplyLanguage = true;
     400            0 :                 m_restartRequiredToApplyLanguageChanged.send(true);
     401              :             }
     402            0 :         });
     403            0 :     });
     404              : 
     405            0 :     return progress;
     406            0 : }
     407              : 
     408            0 : bool LanguagesService::restartRequiredToApplyLanguage() const
     409              : {
     410            0 :     return m_restartRequiredToApplyLanguage;
     411              : }
     412              : 
     413            0 : async::Channel<bool> LanguagesService::restartRequiredToApplyLanguageChanged() const
     414              : {
     415            0 :     return m_restartRequiredToApplyLanguageChanged;
     416              : }
     417              : 
     418            0 : void LanguagesService::downloadServerLanguagesInfo(const QString& languageCode, std::function<void(const RetVal<QJsonObject>&)> finished)
     419              : {
     420            0 :     auto buff = std::make_shared<QBuffer>();
     421            0 :     RetVal<Progress> progress = m_networkManager->get(configuration()->languagesUpdateUrl().toString(), buff);
     422            0 :     if (!progress.ret) {
     423            0 :         finished(RetVal<QJsonObject>::make_ret(progress.ret));
     424            0 :         return;
     425              :     }
     426              : 
     427            0 :     progress.val.finished().onReceive(this, [languageCode, buff, finished](const ProgressResult& res) {
     428            0 :         if (!res.ret) {
     429            0 :             finished(RetVal<QJsonObject>::make_ret(res.ret));
     430            0 :             return;
     431              :         }
     432              : 
     433            0 :         QJsonParseError err;
     434            0 :         QJsonDocument jsonDoc = QJsonDocument::fromJson(buff->data(), &err);
     435            0 :         if (err.error != QJsonParseError::NoError || !jsonDoc.isObject()) {
     436            0 :             finished(RetVal<QJsonObject>::make_ret(make_ret(Err::ErrorInvalidServerLanguagesInfo)));
     437            0 :             return;
     438              :         }
     439              : 
     440            0 :         finished(RetVal<QJsonObject>::make_ok(jsonDoc.object()));
     441            0 :     });
     442            0 : }
     443              : 
     444            0 : QStringList LanguagesService::languagesToUpdate(const QString& mainLanguageCode, const QJsonObject& serverLanguagesInfo) const
     445              : {
     446            0 :     QStringList languagesToUpdate;
     447              : 
     448            0 :     auto languageNeedsUpdate = [this, &serverLanguagesInfo](const QString& code) {
     449            0 :         const Language& lang = m_languagesHash[code];
     450            0 :         const QJsonObject languageObject = serverLanguagesInfo.value(code).toObject();
     451              : 
     452            0 :         for (const QString& resource : configuration()->languageResourceNames()) {
     453            0 :             RetVal<QString> hash = fileHash(lang.files[resource]);
     454            0 :             QString latestHash = languageObject.value(resource).toObject().value("hash").toString();
     455              : 
     456            0 :             if (!hash.ret || hash.val != latestHash) {
     457            0 :                 return true;
     458              :             }
     459            0 :         }
     460              : 
     461            0 :         return false;
     462            0 :     };
     463              : 
     464            0 :     if (languageNeedsUpdate(mainLanguageCode)) {
     465            0 :         languagesToUpdate.append(mainLanguageCode);
     466              :     }
     467              : 
     468            0 :     for (const QString& fallbackCode : m_languagesHash[mainLanguageCode].fallbackLanguages) {
     469            0 :         if (languageNeedsUpdate(fallbackCode)) {
     470            0 :             languagesToUpdate.append(fallbackCode);
     471              :         }
     472            0 :     }
     473              : 
     474            0 :     return languagesToUpdate;
     475            0 : }
     476              : 
     477            0 : void LanguagesService::doUpdateLanguages(const QStringList& languageCodes, Progress overallProgress,
     478              :                                          std::function<void(const Ret&)> overallFinished)
     479              : {
     480            0 :     assert(!languageCodes.empty());
     481              : 
     482              :     // Can't create recursion with self-capturing lambda, because we can't
     483              :     // capture any local variable by reference (they will have been destroyed by
     484              :     // the time the lambda is executed). So we pass the lambda as a parameter
     485              :     // instead. The type of this parameter cannot be written directly, as it
     486              :     // would be a recursive type.
     487              :     auto updateLanguage
     488            0 :         = [this, languageCodes, overallProgress, overallFinished](int languageIndex, auto updateNextLanguage) mutable -> void {
     489            0 :         auto progressCallback
     490            0 :             = [languageCodes, overallProgress, languageIndex](int64_t current, int64_t total, const std::string& msg) mutable {
     491            0 :             const int64_t overallTotal = languageCodes.size() * 10000;
     492            0 :             const int64_t overallCurrent = languageIndex * 10000 + current * 10000 / total;
     493            0 :             overallProgress.progress(overallCurrent, overallTotal, msg);
     494              :         };
     495              : 
     496            0 :         auto finished
     497            0 :             = [languageCodes, overallFinished, languageIndex, updateNextLanguage](const Ret& ret) mutable {
     498            0 :             if (ret) {
     499            0 :                 if (++languageIndex < languageCodes.size()) {
     500            0 :                     updateNextLanguage(languageIndex, updateNextLanguage);
     501              :                 } else {
     502            0 :                     overallFinished(make_ok());
     503              :                 }
     504              :             } else {
     505            0 :                 overallFinished(ret);
     506              :             }
     507              :         };
     508              : 
     509            0 :         doUpdateLanguage(languageCodes[languageIndex], progressCallback, finished);
     510            0 :     };
     511              : 
     512            0 :     updateLanguage(0, updateLanguage);
     513            0 : }
     514              : 
     515            0 : void LanguagesService::doUpdateLanguage(const QString& languageCode,
     516              :                                         std::function<void(int64_t current, int64_t total, const std::string&)> progressCallback,
     517              :                                         std::function<void(const Ret&)> finished)
     518              : {
     519            0 :     auto qbuff = std::make_shared<QBuffer>();
     520            0 :     const QUrl url = configuration()->languageFileServerUrl(languageCode);
     521            0 :     RetVal<Progress> downloadProgress = m_networkManager->get(url, qbuff);
     522            0 :     if (!downloadProgress.ret) {
     523            0 :         finished(make_ret(Err::ErrorDownloadLanguage));
     524            0 :         return;
     525              :     }
     526              : 
     527            0 :     const std::string downloadingMsg = muse::qtrc("global", "Downloading %1…").arg(languageCode).toStdString();
     528            0 :     progressCallback(0, 1, downloadingMsg);
     529              : 
     530            0 :     downloadProgress.val.progressChanged().onReceive(this,
     531            0 :                                                      [progressCallback, downloadingMsg](int64_t current, int64_t total, const std::string&) {
     532            0 :         progressCallback(current, total,
     533            0 :                          downloadingMsg);
     534            0 :     });
     535              : 
     536            0 :     downloadProgress.val.finished().onReceive(this, [this, progressCallback, finished, languageCode, qbuff](const ProgressResult& res) {
     537            0 :         if (!res.ret) {
     538            0 :             finished(make_ret(Err::ErrorDownloadLanguage));
     539            0 :             return;
     540              :         }
     541              : 
     542            0 :         progressCallback(1, 1, muse::qtrc("global", "Unpacking %1…").arg(languageCode).toStdString());
     543              : 
     544            0 :         Ret ret = unpackAndWriteLanguage(qbuff->data());
     545            0 :         finished(ret);
     546            0 :     });
     547            0 : }
     548              : 
     549            0 : Ret LanguagesService::unpackAndWriteLanguage(const QByteArray& zipData)
     550              : {
     551            0 :     TRACEFUNC;
     552              : 
     553            0 :     ByteArray ba = ByteArray::fromQByteArrayNoCopy(zipData);
     554            0 :     io::Buffer buff(&ba);
     555            0 :     ZipReader zipReader(&buff);
     556              : 
     557              :     {
     558            0 :         mi::WriteResourceLockGuard lock_guard(multiwindowsProvider(), LANGUAGES_RESOURCE_NAME);
     559              : 
     560            0 :         for (const auto& info : zipReader.fileInfoList()) {
     561            0 :             io::path_t userFilePath = configuration()->languagesUserAppDataPath().appendingComponent(info.filePath);
     562            0 :             Ret ret = fileSystem()->writeFile(userFilePath, zipReader.fileData(info.filePath.toStdString()));
     563            0 :             if (!ret) {
     564            0 :                 return make_ret(Err::ErrorWriteLanguage);
     565              :             }
     566            0 :         }
     567            0 :     }
     568              : 
     569            0 :     return make_ok();
     570            0 : }
     571              : 
     572            0 : RetVal<QString> LanguagesService::fileHash(const io::path_t& path) const
     573              : {
     574            0 :     RetVal<ByteArray> fileBytes;
     575              :     {
     576            0 :         mi::ReadResourceLockGuard lock_guard(multiwindowsProvider(), LANGUAGES_RESOURCE_NAME);
     577            0 :         fileBytes = fileSystem()->readFile(path);
     578            0 :     }
     579            0 :     if (!fileBytes.ret) {
     580            0 :         return fileBytes.ret;
     581              :     }
     582              : 
     583            0 :     QCryptographicHash hasher(QCryptographicHash::Sha1);
     584            0 :     hasher.reset();
     585            0 :     hasher.addData(fileBytes.val.toQByteArrayNoCopy());
     586            0 :     return RetVal<QString>::make_ok(QString(hasher.result().toHex()));
     587            0 : }
        

Generated by: LCOV version 2.0-1