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