LCOV - code coverage report
Current view: top level - src/engraving/infrastructure - mscreader.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 57.2 % 292 167
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-Studio-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2021 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 "mscreader.h"
      23              : 
      24              : #include "io/file.h"
      25              : #include "io/fileinfo.h"
      26              : #include "io/dir.h"
      27              : #include "serialization/zipreader.h"
      28              : #include "serialization/xmlstreamreader.h"
      29              : #include "engraving/engravingerrors.h"
      30              : 
      31              : #include "log.h"
      32              : 
      33              : //! NOTE The current implementation resolves files by extension.
      34              : //! This will probably be changed in the future.
      35              : 
      36              : using namespace muse;
      37              : using namespace muse::io;
      38              : using namespace mu;
      39              : using namespace mu::engraving;
      40              : 
      41         1009 : MscReader::MscReader(const Params& params)
      42         1009 :     : m_params(params)
      43              : {
      44         1009 : }
      45              : 
      46         1012 : MscReader::~MscReader()
      47              : {
      48         1012 :     close();
      49         1012 : }
      50              : 
      51            3 : void MscReader::setParams(const Params& params)
      52              : {
      53            3 :     IF_ASSERT_FAILED(!isOpened()) {
      54            0 :         return;
      55              :     }
      56              : 
      57            3 :     if (m_reader) {
      58            0 :         delete m_reader;
      59            0 :         m_reader = nullptr;
      60              :     }
      61              : 
      62            3 :     m_params = params;
      63              : }
      64              : 
      65            0 : const MscReader::Params& MscReader::params() const
      66              : {
      67            0 :     return m_params;
      68              : }
      69              : 
      70         1012 : Ret MscReader::open()
      71              : {
      72         1012 :     return reader()->open(m_params.device, m_params.filePath);
      73              : }
      74              : 
      75         1012 : void MscReader::close()
      76              : {
      77         1012 :     if (m_reader) {
      78         1012 :         m_reader->close();
      79              : 
      80         1012 :         delete m_reader;
      81         1012 :         m_reader = nullptr;
      82              :     }
      83         1012 : }
      84              : 
      85         1011 : bool MscReader::isOpened() const
      86              : {
      87         1011 :     return m_reader ? m_reader->isOpened() : false;
      88              : }
      89              : 
      90         1543 : bool MscReader::isContainer() const
      91              : {
      92         1543 :     return m_reader ? m_reader->isContainer() : false;
      93              : }
      94              : 
      95         4510 : MscReader::IReader* MscReader::reader() const
      96              : {
      97         4510 :     if (!m_reader) {
      98         1012 :         switch (m_params.mode) {
      99           14 :         case MscIoMode::Zip:
     100           14 :             m_reader = new ZipFileReader();
     101           14 :             break;
     102          998 :         case MscIoMode::Dir:
     103          998 :             m_reader = new DirReader();
     104          998 :             break;
     105            0 :         case MscIoMode::XmlFile:
     106            0 :             m_reader = new XmlFileReader();
     107            0 :             break;
     108            0 :         case MscIoMode::Unknown:
     109            0 :             UNREACHABLE;
     110              :             break;
     111              :         }
     112              :     }
     113              : 
     114         4510 :     return m_reader;
     115              : }
     116              : 
     117         1132 : bool MscReader::fileExists(const String& fileName) const
     118              : {
     119         1132 :     return reader()->fileExists(fileName);
     120              : }
     121              : 
     122         2116 : ByteArray MscReader::fileData(const String& fileName) const
     123              : {
     124         2116 :     return reader()->fileData(fileName);
     125              : }
     126              : 
     127           62 : ByteArray MscReader::readStyleFile() const
     128              : {
     129           62 :     if (!fileExists(u"score_style.mss")) {
     130            1 :         return ByteArray();
     131              :     }
     132           61 :     return fileData(u"score_style.mss");
     133              : }
     134              : 
     135         1012 : String MscReader::mainFileName() const
     136              : {
     137         1012 :     if (!m_params.mainFileName.isEmpty()) {
     138            0 :         return m_params.mainFileName;
     139              :     }
     140              : 
     141         1012 :     String name = u"score.mscx";
     142         1012 :     if (m_params.filePath.empty()) {
     143            2 :         return name;
     144              :     }
     145              : 
     146         1010 :     String completeBaseName = FileInfo(m_params.filePath).completeBaseName();
     147         1010 :     if (completeBaseName.isEmpty()) {
     148            0 :         return name;
     149              :     }
     150              : 
     151         1010 :     return completeBaseName + u".mscx";
     152         1012 : }
     153              : 
     154         1012 : ByteArray MscReader::readScoreFile() const
     155              : {
     156         1012 :     String mscxFileName = mainFileName();
     157         1012 :     ByteArray data = fileData(mscxFileName);
     158         1012 :     if (data.empty() && reader()->isContainer()) {
     159            0 :         StringList files = reader()->fileList();
     160            0 :         for (const String& name : files) {
     161              :             // mscx file in the root dir
     162            0 :             if (!name.contains(u'/') && name.endsWith(u".mscx", muse::CaseInsensitive)) {
     163            0 :                 mscxFileName = name;
     164            0 :                 break;
     165              :             }
     166              :         }
     167            0 :     }
     168              : 
     169         2024 :     return fileData(mscxFileName);
     170         1012 : }
     171              : 
     172           62 : std::vector<String> MscReader::excerptFileNames() const
     173              : {
     174           62 :     if (!reader()->isContainer()) {
     175            0 :         NOT_SUPPORTED << " not container";
     176            0 :         return std::vector<String>();
     177              :     }
     178              : 
     179           62 :     std::vector<String> names;
     180           62 :     StringList files = reader()->fileList();
     181          469 :     for (const String& filePath : files) {
     182          407 :         if (filePath.startsWith(u"Excerpts/") && filePath.endsWith(u".mscx", muse::CaseInsensitive)) {
     183           11 :             names.push_back(FileInfo(filePath).completeBaseName());
     184              :         }
     185              :     }
     186           62 :     return names;
     187           62 : }
     188              : 
     189           11 : ByteArray MscReader::readExcerptStyleFile(const String& excerptFileName) const
     190              : {
     191           11 :     String fileName = excerptFileName + u".mss";
     192           22 :     return fileData(u"Excerpts/" + excerptFileName + u"/" + fileName);
     193           11 : }
     194              : 
     195           11 : ByteArray MscReader::readExcerptFile(const String& excerptFileName) const
     196              : {
     197           11 :     String fileName = excerptFileName + u".mscx";
     198           22 :     return fileData(u"Excerpts/" + excerptFileName + u"/" + fileName);
     199           11 : }
     200              : 
     201           62 : ByteArray MscReader::readChordListFile() const
     202              : {
     203           62 :     if (!fileExists(u"chordlist.xml")) {
     204           62 :         return ByteArray();
     205              :     }
     206            0 :     return fileData(u"chordlist.xml");
     207              : }
     208              : 
     209            3 : ByteArray MscReader::readThumbnailFile() const
     210              : {
     211            3 :     return fileData(u"Thumbnails/thumbnail.png");
     212              : }
     213              : 
     214            1 : ByteArray MscReader::readImageFile(const String& fileName) const
     215              : {
     216            1 :     return fileData(u"Pictures/" + fileName);
     217              : }
     218              : 
     219           63 : std::vector<String> MscReader::imageFileNames() const
     220              : {
     221           63 :     if (!reader()->isContainer()) {
     222              :         // NOT_SUPPORTED << " not container";
     223            0 :         return std::vector<String>();
     224              :     }
     225              : 
     226           63 :     std::vector<String> names;
     227           63 :     StringList files = reader()->fileList();
     228          474 :     for (const String& filePath : files) {
     229          411 :         if (filePath.startsWith(u"Pictures/")) {
     230            1 :             names.push_back(FileInfo(filePath).fileName());
     231              :         }
     232              :     }
     233           63 :     return names;
     234           63 : }
     235              : 
     236            0 : ByteArray MscReader::readAudioSettingsJsonFile(const muse::io::path_t& pathPrefix) const
     237              : {
     238            0 :     return fileData(pathPrefix.toString() + u"audiosettings.json");
     239              : }
     240              : 
     241            0 : ByteArray MscReader::readViewSettingsJsonFile(const muse::io::path_t& pathPrefix) const
     242              : {
     243            0 :     return fileData(pathPrefix.toString() + u"viewsettings.json");
     244              : }
     245              : 
     246         1008 : muse::ByteArray MscReader::readAutomationJsonFile() const
     247              : {
     248         1008 :     if (!fileExists(u"automation.json")) {
     249         1003 :         return ByteArray();
     250              :     }
     251              : 
     252            5 :     return fileData(u"automation.json");
     253              : }
     254              : 
     255              : // =======================================================================
     256              : // Readers
     257              : // =======================================================================
     258              : 
     259           28 : MscReader::ZipFileReader::~ZipFileReader()
     260              : {
     261           14 :     delete m_zip;
     262           14 :     if (m_selfDeviceOwner) {
     263           11 :         delete m_device;
     264              :     }
     265           28 : }
     266              : 
     267           14 : Ret MscReader::ZipFileReader::open(IODevice* device, const path_t& filePath)
     268              : {
     269           14 :     m_device = device;
     270           14 :     if (!m_device) {
     271           11 :         if (!FileInfo::exists(filePath)) {
     272            0 :             LOGE() << "path does not exist: " << filePath;
     273            0 :             return make_ret(Err::FileNotFound, filePath);
     274              :         }
     275              : 
     276           11 :         m_device = new File(filePath);
     277           11 :         m_selfDeviceOwner = true;
     278              :     }
     279              : 
     280           14 :     if (!m_device->isOpen()) {
     281           14 :         if (!m_device->open(IODevice::ReadOnly)) {
     282            0 :             LOGE() << "failed open file: " << filePath;
     283            0 :             return make_ret(Err::FileOpenError, filePath);
     284              :         }
     285              :     }
     286              : 
     287           14 :     m_zip = new ZipReader(m_device);
     288              : 
     289           14 :     return true;
     290              : }
     291              : 
     292           14 : void MscReader::ZipFileReader::close()
     293              : {
     294           14 :     if (m_zip) {
     295           14 :         m_zip->close();
     296              :     }
     297              : 
     298           14 :     if (m_device) {
     299           14 :         m_device->close();
     300              :     }
     301           14 : }
     302              : 
     303           13 : bool MscReader::ZipFileReader::isOpened() const
     304              : {
     305           13 :     return m_device ? m_device->isOpen() : false;
     306              : }
     307              : 
     308           53 : bool MscReader::ZipFileReader::isContainer() const
     309              : {
     310           53 :     return true;
     311              : }
     312              : 
     313           27 : StringList MscReader::ZipFileReader::fileList() const
     314              : {
     315           27 :     IF_ASSERT_FAILED(m_zip) {
     316            0 :         return StringList();
     317              :     }
     318              : 
     319           27 :     StringList files;
     320           27 :     std::vector<ZipReader::FileInfo> fileInfoList = m_zip->fileInfoList();
     321           27 :     if (m_zip->hasError()) {
     322            0 :         LOGE() << "failed read meta";
     323              :     }
     324              : 
     325          281 :     for (const ZipReader::FileInfo& fi : fileInfoList) {
     326          254 :         if (fi.isFile) {
     327          246 :             files << fi.filePath.toString();
     328              :         }
     329              :     }
     330              : 
     331           27 :     return files;
     332           27 : }
     333              : 
     334           39 : bool MscReader::ZipFileReader::fileExists(const String& fileName) const
     335              : {
     336           39 :     IF_ASSERT_FAILED(m_zip) {
     337            0 :         return false;
     338              :     }
     339              : 
     340           39 :     return m_zip->fileExists(fileName.toStdString());
     341              : }
     342              : 
     343           70 : ByteArray MscReader::ZipFileReader::fileData(const String& fileName) const
     344              : {
     345           70 :     IF_ASSERT_FAILED(m_zip) {
     346            0 :         return ByteArray();
     347              :     }
     348              : 
     349           70 :     ByteArray data = m_zip->fileData(fileName.toStdString());
     350           70 :     if (m_zip->hasError()) {
     351            0 :         LOGE() << "failed read data for filename " << fileName;
     352            0 :         return ByteArray();
     353              :     }
     354           70 :     return data;
     355           70 : }
     356              : 
     357          998 : Ret MscReader::DirReader::open(IODevice* device, const path_t& filePath)
     358              : {
     359          998 :     if (device) {
     360            0 :         NOT_SUPPORTED;
     361            0 :         return false;
     362              :     }
     363              : 
     364          998 :     if (!FileInfo::exists(filePath)) {
     365            0 :         LOGE() << "path does not exist: " << filePath;
     366            0 :         return make_ret(Err::FileNotFound, filePath);
     367              :     }
     368              : 
     369          998 :     m_rootPath = containerPath(filePath);
     370              : 
     371          998 :     return muse::make_ok();
     372              : }
     373              : 
     374          998 : void MscReader::DirReader::close()
     375              : {
     376              :     // noop
     377          998 : }
     378              : 
     379          995 : bool MscReader::DirReader::isOpened() const
     380              : {
     381          995 :     return FileInfo::exists(m_rootPath);
     382              : }
     383              : 
     384         1615 : bool MscReader::DirReader::isContainer() const
     385              : {
     386              :     //! NOTE We will assume that if there is `/META-INF/container.xml` in the root directory,
     387              :     //! then we read from the container (a directory with a certain structure)
     388         1615 :     return FileInfo::exists(m_rootPath + "/META-INF/container.xml");
     389              : }
     390              : 
     391           98 : StringList MscReader::DirReader::fileList() const
     392              : {
     393           98 :     RetVal<io::paths_t> rv = Dir::scanFiles(m_rootPath, {}, ScanMode::FilesInCurrentDirAndSubdirs);
     394           98 :     if (!rv.ret) {
     395            0 :         LOGE() << "failed scan dir: " << m_rootPath << ", err: " << rv.ret.toString();
     396            0 :         return StringList();
     397              :     }
     398              : 
     399           98 :     StringList files;
     400          670 :     for (const muse::io::path_t& p : rv.val) {
     401          572 :         String filePath = p.toString();
     402          572 :         files << filePath.mid(m_rootPath.size() + 1);
     403          572 :     }
     404              : 
     405           98 :     return files;
     406           98 : }
     407              : 
     408         1093 : bool MscReader::DirReader::fileExists(const String& fileName) const
     409              : {
     410         1093 :     muse::io::path_t filePath = m_rootPath + "/" + fileName;
     411         2186 :     return File::exists(filePath);
     412         1093 : }
     413              : 
     414         2046 : ByteArray MscReader::DirReader::fileData(const String& fileName) const
     415              : {
     416         2046 :     muse::io::path_t filePath = m_rootPath + "/" + fileName;
     417         2046 :     File file(filePath);
     418         2046 :     if (!file.open(IODevice::ReadOnly)) {
     419            0 :         LOGE() << "failed open file: " << filePath;
     420            0 :         return ByteArray();
     421              :     }
     422              : 
     423         2046 :     return file.readAll();
     424         2046 : }
     425              : 
     426            0 : Ret MscReader::XmlFileReader::open(IODevice* device, const path_t& filePath)
     427              : {
     428            0 :     m_device = device;
     429            0 :     if (!m_device) {
     430            0 :         if (!FileInfo::exists(filePath)) {
     431            0 :             LOGE() << "path does not exist: " << filePath;
     432            0 :             return make_ret(Err::FileNotFound, filePath);
     433              :         }
     434              : 
     435            0 :         m_device = new File(filePath);
     436            0 :         m_selfDeviceOwner = true;
     437              :     }
     438              : 
     439            0 :     if (!m_device->isOpen()) {
     440            0 :         if (!m_device->open(IODevice::ReadOnly)) {
     441            0 :             LOGE() << "failed open file: " << filePath;
     442            0 :             return make_ret(Err::FileOpenError, filePath);
     443              :         }
     444              :     }
     445              : 
     446            0 :     return muse::make_ok();
     447              : }
     448              : 
     449            0 : void MscReader::XmlFileReader::close()
     450              : {
     451            0 :     if (m_device) {
     452            0 :         m_device->close();
     453              :     }
     454            0 : }
     455              : 
     456            0 : bool MscReader::XmlFileReader::isOpened() const
     457              : {
     458            0 :     return m_device ? m_device->isOpen() : false;
     459              : }
     460              : 
     461            0 : bool MscReader::XmlFileReader::isContainer() const
     462              : {
     463            0 :     return true;
     464              : }
     465              : 
     466            0 : StringList MscReader::XmlFileReader::fileList() const
     467              : {
     468            0 :     if (!m_device) {
     469            0 :         return StringList();
     470              :     }
     471              : 
     472            0 :     StringList files;
     473              : 
     474            0 :     m_device->seek(0);
     475            0 :     XmlStreamReader xml(m_device);
     476            0 :     while (xml.readNextStartElement()) {
     477            0 :         if (xml.name() != "files") {
     478            0 :             xml.skipCurrentElement();
     479            0 :             continue;
     480              :         }
     481              : 
     482            0 :         while (xml.readNextStartElement()) {
     483            0 :             if (xml.name() != "file") {
     484            0 :                 xml.skipCurrentElement();
     485            0 :                 continue;
     486              :             }
     487              : 
     488            0 :             String fileName = xml.attribute("name");
     489            0 :             files << fileName;
     490            0 :             xml.skipCurrentElement();
     491            0 :         }
     492              :     }
     493              : 
     494            0 :     return files;
     495            0 : }
     496              : 
     497            0 : bool MscReader::XmlFileReader::fileExists(const String& fileName) const
     498              : {
     499            0 :     if (!m_device) {
     500            0 :         return false;
     501              :     }
     502              : 
     503            0 :     m_device->seek(0);
     504            0 :     XmlStreamReader xml(m_device);
     505            0 :     while (xml.readNextStartElement()) {
     506            0 :         if ("files" != xml.name()) {
     507            0 :             xml.skipCurrentElement();
     508            0 :             continue;
     509              :         }
     510              : 
     511            0 :         while (xml.readNextStartElement()) {
     512            0 :             if ("file" != xml.name()) {
     513            0 :                 xml.skipCurrentElement();
     514            0 :                 continue;
     515              :             }
     516              : 
     517            0 :             if (fileName == xml.attribute("name")) {
     518            0 :                 return true;
     519              :             }
     520              :         }
     521              :     }
     522              : 
     523            0 :     return false;
     524            0 : }
     525              : 
     526            0 : ByteArray MscReader::XmlFileReader::fileData(const String& fileName) const
     527              : {
     528            0 :     if (!m_device) {
     529            0 :         return ByteArray();
     530              :     }
     531              : 
     532            0 :     m_device->seek(0);
     533            0 :     XmlStreamReader xml(m_device);
     534            0 :     while (xml.readNextStartElement()) {
     535            0 :         if (xml.name() != "files") {
     536            0 :             xml.skipCurrentElement();
     537            0 :             continue;
     538              :         }
     539              : 
     540            0 :         while (xml.readNextStartElement()) {
     541            0 :             if (xml.name() != "file") {
     542            0 :                 xml.skipCurrentElement();
     543            0 :                 continue;
     544              :             }
     545              : 
     546            0 :             String file = xml.attribute("name");
     547            0 :             if (file != fileName) {
     548            0 :                 xml.skipCurrentElement();
     549            0 :                 continue;
     550              :             }
     551              : 
     552            0 :             String cdata = xml.readText();
     553            0 :             ByteArray ba = cdata.trimmed().toUtf8();
     554            0 :             return ba;
     555            0 :         }
     556              :     }
     557              : 
     558            0 :     return ByteArray();
     559            0 : }
        

Generated by: LCOV version 2.0-1