LCOV - code coverage report
Current view: top level - src/musesounds/internal - musesoundscheckupdateservice.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 74 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              : 
      23              : #include "musesoundscheckupdateservice.h"
      24              : 
      25              : #include <QBuffer>
      26              : #include <QJsonParseError>
      27              : #include <QJsonObject>
      28              : #include <QJsonValue>
      29              : #include <QJsonArray>
      30              : #include <QJsonDocument>
      31              : 
      32              : #include "musesoundserrors.h"
      33              : 
      34              : #include "defer.h"
      35              : #include "log.h"
      36              : 
      37              : static const muse::UriQuery MUSEHUB_APP_URI("musehub://?from=musescore");
      38              : static const muse::UriQuery MUSEHUB_APP_V1_URI("muse-hub://?from=musescore");
      39              : 
      40              : using namespace mu::musesounds;
      41              : using namespace muse;
      42              : using namespace muse::update;
      43              : using namespace muse::network;
      44              : using namespace muse::async;
      45              : 
      46            0 : Ret MuseSoundsCheckUpdateService::needCheckForUpdate() const
      47              : {
      48            0 :     if (!configuration()->needCheckForMuseSoundsUpdate()) {
      49            0 :         return false;
      50              :     }
      51              : 
      52              : #ifdef Q_OS_WIN
      53              :     return true;
      54              : #elif defined(Q_OS_MAC)
      55              :     //! NOTE: If there is installed MuseHub, but we can't open it, then we shouldn't check update
      56              :     static const std::string MUSEHUB_APP_IDENTIFIER = "com.muse.hub";
      57              :     bool isMuseHubExists = platformInteractive()->isAppExists(MUSEHUB_APP_IDENTIFIER);
      58              :     if (isMuseHubExists) {
      59              :         bool canOpenMuseHubByUniversalUrl = platformInteractive()->canOpenApp(MUSEHUB_APP_URI);
      60              :         return canOpenMuseHubByUniversalUrl;
      61              :     }
      62              : 
      63              :     return true;
      64              : #else
      65            0 :     return false;
      66              : #endif
      67              : }
      68              : 
      69            0 : Promise<RetVal<ReleaseInfo> > MuseSoundsCheckUpdateService::checkForUpdate()
      70              : {
      71            0 :     return Promise<muse::RetVal<ReleaseInfo> >([this](auto resolve, auto) {
      72            0 :         m_lastCheckResult.val = ReleaseInfo();
      73            0 :         m_lastCheckResult.ret = muse::make_ok();
      74              : 
      75            0 :         if (configuration()->museSoundsCheckForUpdateTestMode()) {
      76            0 :             return resolve(m_lastCheckResult);
      77              :         }
      78              : 
      79            0 :         auto buff = std::make_shared<QBuffer>();
      80            0 :         QUrl url = configuration()->checkForMuseSoundsUpdateUrl();
      81              : 
      82            0 :         if (!m_networkManager) {
      83            0 :             m_networkManager = networkManagerCreator()->makeNetworkManager();
      84              :         }
      85              : 
      86            0 :         RetVal<Progress> progress = m_networkManager->get(url, buff);
      87              : 
      88            0 :         if (!progress.ret) {
      89            0 :             m_lastCheckResult.ret = progress.ret;
      90            0 :             return resolve(m_lastCheckResult);
      91              :         }
      92              : 
      93            0 :         progress.val.finished().onReceive(this, [this, buff, resolve](const ProgressResult& res) {
      94            0 :             DEFER {
      95            0 :                 (void)resolve(m_lastCheckResult);
      96              :             };
      97              : 
      98            0 :             if (!res.ret) {
      99            0 :                 m_lastCheckResult.ret = res.ret;
     100            0 :                 return;
     101              :             }
     102              : 
     103            0 :             m_lastCheckResult = parseRelease(buff->data());
     104            0 :         });
     105              : 
     106            0 :         return muse::async::Promise<muse::RetVal<ReleaseInfo> >::dummy_result();
     107            0 :     });
     108              : }
     109              : 
     110            0 : const RetVal<ReleaseInfo>& MuseSoundsCheckUpdateService::lastCheckResult() const
     111              : {
     112            0 :     return m_lastCheckResult;
     113              : }
     114              : 
     115            0 : RetVal<ReleaseInfo> MuseSoundsCheckUpdateService::parseRelease(const QByteArray& json) const
     116              : {
     117            0 :     RetVal<ReleaseInfo> result;
     118              : 
     119            0 :     QJsonParseError err;
     120            0 :     QJsonDocument jsonDoc = QJsonDocument::fromJson(json, &err);
     121            0 :     if (err.error != QJsonParseError::NoError || !jsonDoc.isObject()) {
     122            0 :         LOGE() << "parse error: " << err.errorString();
     123            0 :         result.ret = make_ret(Err::NoUpdate);
     124            0 :         return result;
     125              :     }
     126              : 
     127              :     /*
     128              :     {
     129              :         "version": String,
     130              :         "image_url": String,  // it can be base64 data, like "data:image/png;base64,iVBORw0KGgoA......"
     131              :         "content": {
     132              :             "locale_code": {
     133              :                 "notes": String,
     134              :                 "features": [String]
     135              :                 "action_title": String // title of action button
     136              :             }
     137              :         },
     138              : 
     139              :         // open app or web page url, try in order,
     140              :         // like this ["musehub://?from=musescore", "muse-hub://?from=musescore", "https://www.musehub.com"]
     141              :         "actions": {
     142              :             "windows": [String],
     143              :             "macos": [String],
     144              :             "linux": [String]
     145              :         }
     146              :     }
     147              :     */
     148              : 
     149            0 :     QJsonObject releaseObj = jsonDoc.object();
     150              : 
     151            0 :     if (releaseObj.empty()) {
     152            0 :         result.ret = make_ret(Err::NoUpdate);
     153            0 :         return result;
     154              :     }
     155              : 
     156            0 :     QString languageCode = languagesConfiguration()->currentLanguageCode().val;
     157            0 :     QString locale(QLocale(languageCode).bcp47Name());
     158              : 
     159            0 :     QJsonObject contentObj = releaseObj.value("content").toObject();
     160              : 
     161            0 :     if (!contentObj.contains(locale)) {
     162            0 :         static const QString DEFAULT_LOCALE = "en";
     163            0 :         locale = DEFAULT_LOCALE;
     164              : 
     165            0 :         if (!contentObj.contains(locale)) {
     166            0 :             LOGE() << "failed parse, no release content";
     167            0 :             result.ret = make_ret(Ret::Code::InternalError);
     168            0 :             return result;
     169              :         }
     170              :     }
     171              : 
     172            0 :     QJsonObject contentLocaleObj = contentObj.value(locale).toObject();
     173              : 
     174            0 :     result.ret = make_ok();
     175              : 
     176            0 :     result.val.version = releaseObj.value("version").toString().toStdString();
     177            0 :     result.val.imageUrl = releaseObj.value("image_url").toString().toStdString();
     178              : 
     179            0 :     result.val.notes = contentLocaleObj.value("notes").toString().toStdString();
     180            0 :     ValList featuresList;
     181            0 :     QJsonArray features = contentLocaleObj.value("features").toArray();
     182            0 :     for (const QJsonValue& feature : features) {
     183            0 :         featuresList.push_back(Val(feature.toString().toStdString()));
     184            0 :     }
     185            0 :     result.val.additionInfo.insert({ "features", Val(featuresList) });
     186              : 
     187            0 :     result.val.actionTitle = contentLocaleObj.value("action_title").toString().toStdString();
     188            0 :     result.val.cancelTitle = contentLocaleObj.value("cancel_title").toString().toStdString();
     189              : 
     190            0 :     QJsonObject actionsObj = releaseObj.value("actions").toObject();
     191              : 
     192              : #ifdef Q_OS_WIN
     193              :     QJsonArray actionsArr = actionsObj.value("windows").toArray();
     194              :     for (const QJsonValue& a : actionsArr) {
     195              :         result.val.actions.push_back(Val(a.toString().toStdString()));
     196              :     }
     197              : 
     198              :     // def
     199              :     if (result.val.actions.empty()) {
     200              :         result.val.actions.push_back(Val(MUSEHUB_APP_URI.toString()));
     201              :         result.val.actions.push_back(Val(MUSEHUB_APP_V1_URI.toString()));
     202              :         result.val.actions.push_back(Val(globalConfiguration()->museHubWebUrl()));
     203              :     }
     204              : 
     205              : #elif defined(Q_OS_MAC)
     206              :     QJsonArray actionsArr = actionsObj.value("macos").toArray();
     207              :     for (const QJsonValue& a : actionsArr) {
     208              :         result.val.actions.push_back(Val(a.toString().toStdString()));
     209              :     }
     210              : 
     211              :     // def
     212              :     if (result.val.actions.empty()) {
     213              :         result.val.actions.push_back(Val(MUSEHUB_APP_URI.toString()));
     214              :         result.val.actions.push_back(Val(globalConfiguration()->museHubWebUrl()));
     215              :     }
     216              : #else
     217            0 :     QJsonArray actionsArr = actionsObj.value("linux").toArray();
     218            0 :     for (const QJsonValue& a : actionsArr) {
     219            0 :         result.val.actions.push_back(Val(a.toString().toStdString()));
     220            0 :     }
     221              : 
     222              :     // def
     223            0 :     if (result.val.actions.empty()) {
     224            0 :         result.val.actions.push_back(Val(globalConfiguration()->museHubWebUrl()));
     225              :     }
     226              : #endif
     227              : 
     228            0 :     return result;
     229            0 : }
        

Generated by: LCOV version 2.0-1