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 : }
|