LCOV - code coverage report
Current view: top level - src/musesounds/internal - musesoundsrepository.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 96 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 "musesoundsrepository.h"
      24              : 
      25              : #include "serialization/json.h"
      26              : 
      27              : #include <QBuffer>
      28              : #include <QUrlQuery>
      29              : 
      30              : using namespace mu::musesounds;
      31              : using namespace muse;
      32              : using namespace muse::network;
      33              : 
      34            0 : static std::string platformMuseSoundsAppName()
      35              : {
      36              : #if defined(Q_OS_WIN) | defined(Q_OS_MAC)
      37              :     return "museHub";
      38              : #else
      39            0 :     return "museSoundManager";
      40              : #endif
      41              : }
      42              : 
      43            0 : static muse::UriQuery correctThumbnailSize(const UriQuery& uri)
      44              : {
      45            0 :     String uriStr = String::fromStdString(uri.toString());
      46              : 
      47              :     // Thumbnails can live under different CDN folders (library-images, bundle-images,
      48              :     // platform-subscription-images, etc.), so resize by host rather than a fixed folder name
      49            0 :     static const String host(u"muse-cdn.com/");
      50            0 :     const size_t hostIdx = uriStr.indexOf(host);
      51            0 :     if (hostIdx != muse::nidx) {
      52            0 :         uriStr.insert(hostIdx + host.size(), u"cdn-cgi/image/w=240,q=80,f=webp/");
      53              :     }
      54              : 
      55            0 :     return UriQuery(uriStr.toStdString());
      56            0 : }
      57              : 
      58            0 : static QUrl soundsRequestUrl(const QUrl& baseUrl)
      59              : {
      60            0 :     QLocale locale = QLocale();
      61            0 :     String localeStr = locale.bcp47Name() + "-" + QLocale::territoryToCode(locale.territory());
      62              : 
      63            0 :     JsonObject variables;
      64            0 :     variables["locale"] = localeStr;
      65              : 
      66            0 :     QUrlQuery query;
      67            0 :     query.addQueryItem("pqId", "musescore-studio-sounds-page-v1");
      68            0 :     query.addQueryItem("variables",
      69            0 :                        QString::fromUtf8(JsonDocument(variables).toJson(JsonDocument::Format::Compact).toQByteArray()));
      70              : 
      71            0 :     QUrl url = baseUrl;
      72            0 :     url.setQuery(query);
      73              : 
      74            0 :     return url;
      75            0 : }
      76              : 
      77            0 : void MuseSoundsRepository::init()
      78              : {
      79            0 :     TRACEFUNC;
      80              : 
      81            0 :     QUrl url = soundsRequestUrl(QUrl(QString::fromStdString(configuration()->soundsUri().toString())));
      82            0 :     RequestHeaders headers = configuration()->headers();
      83              : 
      84            0 :     auto receivedData = std::make_shared<QBuffer>();
      85              : 
      86            0 :     if (!m_networkManager) {
      87            0 :         m_networkManager = networkManagerCreator()->makeNetworkManager();
      88              :     }
      89              : 
      90            0 :     RetVal<Progress> progress = m_networkManager->get(url, receivedData, headers);
      91            0 :     if (!progress.ret) {
      92            0 :         LOGE() << progress.ret.toString();
      93            0 :         return;
      94              :     }
      95              : 
      96            0 :     progress.val.finished().onReceive(this, [this, receivedData](const muse::ProgressResult& res) {
      97            0 :         if (!res.ret) {
      98            0 :             LOGE() << "Unable to download MuseSounds list: " << res.ret.toString();
      99            0 :             return;
     100              :         }
     101              : 
     102            0 :         RetVal<SoundCatalogInfoList> result;
     103              : 
     104            0 :         std::string err;
     105            0 :         JsonDocument soundsInfoDoc = JsonDocument::fromJson(ByteArray::fromQByteArray(receivedData->data()), &err);
     106            0 :         if (!err.empty()) {
     107            0 :             result.ret = make_ret(Ret::Code::InternalError, err);
     108              :         } else {
     109            0 :             result.ret = make_ok();
     110            0 :             result.val = parseSounds(soundsInfoDoc);
     111              :         }
     112              : 
     113            0 :         m_soundsCatalogs = result.val;
     114            0 :         m_soundsCatalogsChanged.notify();
     115            0 :     });
     116            0 : }
     117              : 
     118            0 : const SoundCatalogInfoList& MuseSoundsRepository::soundsCatalogs() const
     119              : {
     120            0 :     return m_soundsCatalogs;
     121              : }
     122              : 
     123            0 : async::Notification MuseSoundsRepository::soundsCatalogsChanged() const
     124              : {
     125            0 :     return m_soundsCatalogsChanged;
     126              : }
     127              : 
     128            0 : SoundCatalogInfoList MuseSoundsRepository::parseSounds(const JsonDocument& soundsDoc) const
     129              : {
     130            0 :     SoundCatalogInfoList result;
     131              : 
     132            0 :     JsonObject obj = soundsDoc.rootObject();
     133            0 :     JsonObject data = !obj.empty() ? obj.value("data").toObject() : JsonObject();
     134            0 :     JsonObject productsSearch = !data.empty() ? data.value("product_pages_configuration").toObject() : JsonObject();
     135            0 :     JsonArray catalogs = !productsSearch.empty() ? productsSearch.value("museScoreStudioPageSections").toArray() : JsonArray();
     136              : 
     137            0 :     std::string museSoundsAppName = platformMuseSoundsAppName();
     138              : 
     139            0 :     for (size_t catalogIdx = 0; catalogIdx < catalogs.size(); ++catalogIdx) {
     140            0 :         JsonObject catalogObj = catalogs.at(catalogIdx).toObject();
     141            0 :         if (catalogObj.empty()) {
     142            0 :             continue;
     143              :         }
     144              : 
     145            0 :         SoundCatalogInfo catalog;
     146            0 :         catalog.title = catalogObj.value("title").toString();
     147              : 
     148            0 :         JsonArray soundsItems = catalogObj.value("productCards").toArray();
     149            0 :         if (soundsItems.empty()) {
     150            0 :             continue;
     151              :         }
     152              : 
     153            0 :         for (size_t soundIdx = 0; soundIdx < soundsItems.size(); ++soundIdx) {
     154            0 :             JsonObject soundItemObj = soundsItems.at(soundIdx).toObject();
     155            0 :             if (soundItemObj.empty()) {
     156            0 :                 continue;
     157              :             }
     158              : 
     159            0 :             JsonObject productObj = soundItemObj.value("product").toObject();
     160            0 :             if (productObj.empty()) {
     161            0 :                 continue;
     162              :             }
     163              : 
     164            0 :             JsonObject compatibleWithObj = productObj.value("compatibleWith").toObject();
     165            0 :             if (compatibleWithObj.empty()) {
     166              :                 // Products without compatibleWith info are only manageable through MuseHub
     167            0 :                 if (museSoundsAppName != "museHub") {
     168            0 :                     continue;
     169              :                 }
     170            0 :             } else if (compatibleWithObj.contains(museSoundsAppName) && !compatibleWithObj.value(museSoundsAppName).toBool()) {
     171            0 :                 continue;
     172              :             }
     173              : 
     174            0 :             SoundLibraryInfo soundLibrary;
     175            0 :             soundLibrary.title = productObj.value("title").toString();
     176            0 :             soundLibrary.subtitle = productObj.value("subtitle").toString();
     177            0 :             soundLibrary.thumbnail = correctThumbnailSize(UriQuery(soundItemObj.value("iconImageUrl").toStdString()));
     178            0 :             soundLibrary.code = productObj.value("code").toString();
     179            0 :             soundLibrary.uri = configuration()->soundPageUri(soundLibrary.code);
     180              : 
     181            0 :             catalog.soundLibraries.emplace_back(soundLibrary);
     182            0 :         }
     183              : 
     184            0 :         if (catalog.soundLibraries.empty()) {
     185            0 :             continue;
     186              :         }
     187              : 
     188            0 :         result.emplace_back(catalog);
     189            0 :     }
     190              : 
     191            0 :     return result;
     192            0 : }
        

Generated by: LCOV version 2.0-1