LCOV - code coverage report
Current view: top level - src/engraving/infrastructure - mscwriter.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 42.6 % 237 101
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 "mscwriter.h"
      23              : 
      24              : #include <vector>
      25              : 
      26              : #include "containers.h"
      27              : #include "io/buffer.h"
      28              : #include "io/file.h"
      29              : #include "io/fileinfo.h"
      30              : #include "io/dir.h"
      31              : #include "serialization/xmlstreamwriter.h"
      32              : #include "serialization/zipwriter.h"
      33              : #include "serialization/textstream.h"
      34              : 
      35              : #include "log.h"
      36              : 
      37              : using namespace mu;
      38              : using namespace muse;
      39              : using namespace muse::io;
      40              : using namespace mu::engraving;
      41              : 
      42            4 : MscWriter::MscWriter(const Params& params)
      43            4 :     : m_params(params)
      44              : {
      45            4 : }
      46              : 
      47            4 : MscWriter::~MscWriter()
      48              : {
      49            4 :     close();
      50            4 : }
      51              : 
      52            0 : void MscWriter::setParams(const Params& params)
      53              : {
      54            0 :     IF_ASSERT_FAILED(!isOpened()) {
      55            0 :         return;
      56              :     }
      57              : 
      58            0 :     if (m_writer) {
      59            0 :         m_hadError = m_writer->hasError();
      60            0 :         delete m_writer;
      61            0 :         m_writer = nullptr;
      62              :     }
      63              : 
      64            0 :     m_params = params;
      65              : }
      66              : 
      67            0 : const MscWriter::Params& MscWriter::params() const
      68              : {
      69            0 :     return m_params;
      70              : }
      71              : 
      72            4 : Ret MscWriter::open()
      73              : {
      74            4 :     return writer()->open(m_params.device, m_params.filePath);
      75              : }
      76              : 
      77            7 : void MscWriter::close()
      78              : {
      79            7 :     if (m_writer) {
      80            4 :         if (m_writer->isOpened()) {
      81            4 :             writeMeta();
      82            4 :             m_writer->close();
      83              :         }
      84              : 
      85            4 :         m_hadError = m_writer->hasError();
      86            4 :         delete m_writer;
      87            4 :         m_writer = nullptr;
      88              :     }
      89            7 : }
      90              : 
      91            3 : bool MscWriter::isOpened() const
      92              : {
      93            3 :     return m_writer ? m_writer->isOpened() : false;
      94              : }
      95              : 
      96            0 : bool MscWriter::hasError() const
      97              : {
      98            0 :     return m_writer ? m_writer->hasError() : m_hadError;
      99              : }
     100              : 
     101           17 : MscWriter::IWriter* MscWriter::writer() const
     102              : {
     103           17 :     if (!m_writer) {
     104            4 :         switch (m_params.mode) {
     105            4 :         case MscIoMode::Zip:
     106            4 :             m_writer = new ZipFileWriter();
     107            4 :             break;
     108            0 :         case MscIoMode::Dir:
     109            0 :             m_writer = new DirWriter();
     110            0 :             break;
     111            0 :         case MscIoMode::XmlFile:
     112            0 :             m_writer = new XmlFileWriter();
     113            0 :             break;
     114            0 :         case MscIoMode::Unknown:
     115            0 :             UNREACHABLE;
     116              :             break;
     117              :         }
     118              :     }
     119              : 
     120           17 :     return m_writer;
     121              : }
     122              : 
     123           13 : bool MscWriter::addFileData(const String& fileName, const ByteArray& data)
     124              : {
     125           13 :     if (!writer()->addFileData(fileName, data)) {
     126            0 :         LOGE() << "failed write file: " << fileName;
     127            0 :         return false;
     128              :     }
     129              : 
     130           13 :     m_meta.addFile(fileName);
     131              : 
     132           13 :     return true;
     133              : }
     134              : 
     135            3 : void MscWriter::writeStyleFile(const ByteArray& data)
     136              : {
     137            3 :     addFileData(u"score_style.mss", data);
     138            3 : }
     139              : 
     140            4 : String MscWriter::mainFileName() const
     141              : {
     142            4 :     if (!m_params.mainFileName.isEmpty()) {
     143            0 :         return m_params.mainFileName;
     144              :     }
     145              : 
     146            4 :     String name = u"score.mscx";
     147            4 :     if (m_params.filePath.empty()) {
     148            3 :         return name;
     149              :     }
     150              : 
     151            1 :     String completeBaseName = FileInfo(m_params.filePath).completeBaseName();
     152            1 :     if (completeBaseName.isEmpty()) {
     153            0 :         return name;
     154              :     }
     155              : 
     156            1 :     return completeBaseName + u".mscx";
     157            4 : }
     158              : 
     159            4 : void MscWriter::writeScoreFile(const ByteArray& data)
     160              : {
     161            4 :     addFileData(mainFileName(), data);
     162            4 : }
     163              : 
     164            0 : void MscWriter::addExcerptStyleFile(const String& excerptFileName, const ByteArray& data)
     165              : {
     166            0 :     String fileName = excerptFileName + u".mss";
     167            0 :     addFileData(u"Excerpts/" + excerptFileName + u"/" + fileName, data);
     168            0 : }
     169              : 
     170            0 : void MscWriter::addExcerptFile(const String& excerptFileName, const ByteArray& data)
     171              : {
     172            0 :     String fileName = excerptFileName + u".mscx";
     173            0 :     addFileData(u"Excerpts/" + excerptFileName + u"/" + fileName, data);
     174            0 : }
     175              : 
     176            0 : void MscWriter::writeChordListFile(const ByteArray& data)
     177              : {
     178            0 :     addFileData(u"chordlist.xml", data);
     179            0 : }
     180              : 
     181            1 : void MscWriter::writeThumbnailFile(const ByteArray& data)
     182              : {
     183            1 :     addFileData(u"Thumbnails/thumbnail.png", data);
     184            1 : }
     185              : 
     186            1 : void MscWriter::addImageFile(const String& fileName, const ByteArray& data)
     187              : {
     188            1 :     addFileData(u"Pictures/" + fileName, data);
     189            1 : }
     190              : 
     191            0 : void MscWriter::writeAudioSettingsJsonFile(const ByteArray& data, const muse::io::path_t& pathPrefix)
     192              : {
     193            0 :     addFileData(pathPrefix.toString() + u"audiosettings.json", data);
     194            0 : }
     195              : 
     196            0 : void MscWriter::writeViewSettingsJsonFile(const ByteArray& data, const muse::io::path_t& pathPrefix)
     197              : {
     198            0 :     addFileData(pathPrefix.toString() + u"viewsettings.json", data);
     199            0 : }
     200              : 
     201            0 : void MscWriter::writeAutomationJsonFile(const muse::ByteArray& data)
     202              : {
     203            0 :     addFileData(u"automation.json", data);
     204            0 : }
     205              : 
     206            4 : void MscWriter::writeMeta()
     207              : {
     208            4 :     if (m_meta.isWritten) {
     209            0 :         return;
     210              :     }
     211              : 
     212            4 :     writeContainer(m_meta.files);
     213              : 
     214            4 :     m_meta.isWritten = true;
     215              : }
     216              : 
     217            4 : void MscWriter::writeContainer(const std::vector<String>& paths)
     218              : {
     219            4 :     ByteArray data;
     220            4 :     auto buf = Buffer::opened(IODevice::WriteOnly, &data);
     221            4 :     XmlStreamWriter xml(&buf);
     222            4 :     xml.startDocument();
     223            4 :     xml.startElement("container");
     224            4 :     xml.startElement("rootfiles");
     225              : 
     226           13 :     for (const String& f : paths) {
     227           27 :         xml.element("rootfile", { { "full-path", f } });
     228              :     }
     229              : 
     230            4 :     xml.endElement();
     231            4 :     xml.endElement();
     232            4 :     xml.flush();
     233              : 
     234            4 :     addFileData(u"META-INF/container.xml", data);
     235           13 : }
     236              : 
     237           13 : bool MscWriter::Meta::contains(const String& file) const
     238              : {
     239           13 :     if (std::find(files.begin(), files.end(), file) != files.end()) {
     240            0 :         return true;
     241              :     }
     242           13 :     return false;
     243              : }
     244              : 
     245           13 : void MscWriter::Meta::addFile(const String& file)
     246              : {
     247           13 :     if (!contains(file)) {
     248           13 :         files.push_back(file);
     249              :     }
     250           13 : }
     251              : 
     252              : // =======================================================================
     253              : // Writers
     254              : // =======================================================================
     255              : 
     256            8 : MscWriter::ZipFileWriter::~ZipFileWriter()
     257              : {
     258            4 :     delete m_zip;
     259            8 : }
     260              : 
     261            4 : Ret MscWriter::ZipFileWriter::open(io::IODevice* device, const path_t&)
     262              : {
     263            4 :     IF_ASSERT_FAILED(device) {
     264            0 :         return make_ret(Ret::Code::InternalError);
     265              :     }
     266            4 :     m_device = device;
     267              : 
     268            4 :     if (!m_device->isOpen()) {
     269            4 :         if (!m_device->open(IODevice::WriteOnly)) {
     270            0 :             LOGE() << "failed to open device for writing";
     271            0 :             return make_ret(m_device->error(), m_device->errorString());
     272              :         }
     273              :     }
     274              : 
     275            4 :     m_zip = new ZipWriter(m_device);
     276              : 
     277            4 :     return true;
     278              : }
     279              : 
     280            4 : void MscWriter::ZipFileWriter::close()
     281              : {
     282            4 :     if (m_zip) {
     283            4 :         m_zip->close();
     284              :     }
     285              : 
     286            4 :     if (m_device) {
     287            4 :         m_device->close();
     288              :     }
     289            4 : }
     290              : 
     291            7 : bool MscWriter::ZipFileWriter::isOpened() const
     292              : {
     293            7 :     return m_device ? m_device->isOpen() : false;
     294              : }
     295              : 
     296            4 : bool MscWriter::ZipFileWriter::hasError() const
     297              : {
     298            4 :     return (m_device ? m_device->hasError() : false) || (m_zip ? m_zip->hasError() : false);
     299              : }
     300              : 
     301           13 : bool MscWriter::ZipFileWriter::addFileData(const String& fileName, const ByteArray& data)
     302              : {
     303           13 :     IF_ASSERT_FAILED(m_zip) {
     304            0 :         return false;
     305              :     }
     306              : 
     307           13 :     m_zip->addFile(fileName.toStdString(), data);
     308           13 :     if (m_zip->hasError()) {
     309            0 :         LOGE() << "failed write files to zip";
     310            0 :         return false;
     311              :     }
     312              : 
     313           13 :     return true;
     314              : }
     315              : 
     316            0 : Ret MscWriter::DirWriter::open(io::IODevice* device, const muse::io::path_t& filePath)
     317              : {
     318            0 :     IF_ASSERT_FAILED(!device) {
     319            0 :         m_hasError = true;
     320            0 :         return make_ret(Ret::Code::InternalError);
     321              :     }
     322              : 
     323            0 :     if (filePath.empty()) {
     324            0 :         LOGE() << "file path is empty";
     325            0 :         m_hasError = true;
     326            0 :         return false;
     327              :     }
     328              : 
     329            0 :     m_rootPath = containerPath(filePath);
     330              : 
     331            0 :     Dir dir(m_rootPath);
     332            0 :     Ret ret = dir.removeRecursively();
     333            0 :     if (!ret) {
     334            0 :         LOGE() << "failed clear dir: " << dir.absolutePath();
     335            0 :         m_hasError = true;
     336            0 :         return ret;
     337              :     }
     338              : 
     339            0 :     ret = dir.mkpath(dir.absolutePath());
     340            0 :     if (!ret) {
     341            0 :         LOGE() << "failed make path: " << dir.absolutePath();
     342            0 :         m_hasError = true;
     343            0 :         return ret;
     344              :     }
     345              : 
     346            0 :     return true;
     347            0 : }
     348              : 
     349            0 : void MscWriter::DirWriter::close()
     350              : {
     351              :     // noop
     352            0 : }
     353              : 
     354            0 : bool MscWriter::DirWriter::isOpened() const
     355              : {
     356            0 :     return FileInfo::exists(m_rootPath);
     357              : }
     358              : 
     359            0 : bool MscWriter::DirWriter::hasError() const
     360              : {
     361            0 :     return m_hasError;
     362              : }
     363              : 
     364            0 : bool MscWriter::DirWriter::addFileData(const String& fileName, const ByteArray& data)
     365              : {
     366            0 :     muse::io::path_t filePath = m_rootPath + "/" + fileName;
     367              : 
     368            0 :     Dir fileDir(FileInfo(filePath).absolutePath());
     369            0 :     if (!fileDir.exists()) {
     370            0 :         if (!fileDir.mkpath(fileDir.absolutePath())) {
     371            0 :             LOGE() << "failed make path: " << fileDir.absolutePath();
     372            0 :             m_hasError = true;
     373            0 :             return false;
     374              :         }
     375              :     }
     376              : 
     377            0 :     const Ret ret = File::writeFile(filePath, data);
     378            0 :     if (!ret) {
     379            0 :         LOGE() << "failed to write file: " << filePath;
     380            0 :         m_hasError = true;
     381            0 :         return false;
     382              :     }
     383              : 
     384            0 :     return true;
     385            0 : }
     386              : 
     387            0 : MscWriter::XmlFileWriter::~XmlFileWriter()
     388              : {
     389            0 :     delete m_stream;
     390            0 : }
     391              : 
     392            0 : Ret MscWriter::XmlFileWriter::open(io::IODevice* device, const path_t&)
     393              : {
     394            0 :     IF_ASSERT_FAILED(device) {
     395            0 :         return make_ret(Ret::Code::InternalError);
     396              :     }
     397            0 :     m_device = device;
     398              : 
     399            0 :     if (!m_device->isOpen()) {
     400            0 :         if (!m_device->open(IODevice::WriteOnly)) {
     401            0 :             LOGE() << "failed to open device for writing";
     402            0 :             return make_ret(m_device->error(), m_device->errorString());
     403              :         }
     404              :     }
     405              : 
     406            0 :     m_stream = new TextStream(m_device);
     407              : 
     408              :     // Write header
     409            0 :     *m_stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
     410            0 :     *m_stream << "<files>\n";
     411              : 
     412            0 :     return true;
     413              : }
     414              : 
     415            0 : void MscWriter::XmlFileWriter::close()
     416              : {
     417            0 :     if (m_stream) {
     418            0 :         *m_stream << "</files>\n";
     419            0 :         m_stream->flush();
     420            0 :         m_device->close();
     421              :     }
     422            0 : }
     423              : 
     424            0 : bool MscWriter::XmlFileWriter::isOpened() const
     425              : {
     426            0 :     return m_device ? m_device->isOpen() : false;
     427              : }
     428              : 
     429            0 : bool MscWriter::XmlFileWriter::hasError() const
     430              : {
     431            0 :     return m_device ? m_device->hasError() : false;
     432              : }
     433              : 
     434            0 : bool MscWriter::XmlFileWriter::addFileData(const String& fileName, const ByteArray& data)
     435              : {
     436            0 :     if (!m_stream) {
     437            0 :         return false;
     438              :     }
     439              : 
     440            0 :     static const std::vector<String> supportedExts = { u"mscx", u"json", u"mss" };
     441            0 :     String ext = FileInfo::suffix(fileName);
     442            0 :     if (!muse::contains(supportedExts, ext)) {
     443            0 :         NOT_SUPPORTED << fileName;
     444            0 :         return true; // not error
     445              :     }
     446              : 
     447            0 :     TextStream& ts = *m_stream;
     448            0 :     ts << "<file name=\"" << fileName << "\">\n";
     449            0 :     ts << "<![CDATA[";
     450            0 :     ts << data;
     451            0 :     ts << "]]>\n";
     452            0 :     ts << "</file>\n";
     453              : 
     454            0 :     return true;
     455            0 : }
        

Generated by: LCOV version 2.0-1