diff --git a/src/appshell/view/devtools/settingslistmodel.cpp b/src/appshell/view/devtools/settingslistmodel.cpp index ec982323e0..bb2ca7a848 100644 --- a/src/appshell/view/devtools/settingslistmodel.cpp +++ b/src/appshell/view/devtools/settingslistmodel.cpp @@ -98,6 +98,7 @@ QString SettingListModel::typeToString(Val::Type t) const case Val::Type::Undefined: return "Undefined"; case Val::Type::Bool: return "Bool"; case Val::Type::Int: return "Int"; + case Val::Type::Int64: return "Int"; case Val::Type::Double: return "Double"; case Val::Type::String: return "String"; case Val::Type::Color: return "Color"; diff --git a/src/appshell/view/preferences/advancedpreferencesmodel.cpp b/src/appshell/view/preferences/advancedpreferencesmodel.cpp index 73b2f755ad..1334ec558f 100644 --- a/src/appshell/view/preferences/advancedpreferencesmodel.cpp +++ b/src/appshell/view/preferences/advancedpreferencesmodel.cpp @@ -122,10 +122,12 @@ QString AdvancedPreferencesModel::typeToString(Val::Type type) const case Val::Type::Undefined: return "Undefined"; case Val::Type::Bool: return "Bool"; case Val::Type::Int: return "Int"; + case Val::Type::Int64: return "Int"; case Val::Type::Double: return "Double"; case Val::Type::String: return "String"; case Val::Type::Color: return "Color"; - default: return "Undefined"; + case Val::Type::List: return "List"; + case Val::Type::Map: return "Map"; } return "Undefined"; } diff --git a/src/engraving/infrastructure/io/mscreader.cpp b/src/engraving/infrastructure/io/mscreader.cpp index 111747b50d..e6c1551e18 100644 --- a/src/engraving/infrastructure/io/mscreader.cpp +++ b/src/engraving/infrastructure/io/mscreader.cpp @@ -291,8 +291,8 @@ StringList MscReader::ZipFileReader::fileList() const StringList files; std::vector fileInfoList = m_zip->fileInfoList(); - if (m_zip->status() != ZipReader::NoError) { - LOGD() << "failed read meta, status: " << m_zip->status(); + if (m_zip->hasError()) { + LOGD() << "failed read meta"; } for (const ZipReader::FileInfo& fi : fileInfoList) { @@ -311,8 +311,8 @@ ByteArray MscReader::ZipFileReader::fileData(const String& fileName) const } ByteArray data = m_zip->fileData(fileName.toStdString()); - if (m_zip->status() != ZipReader::NoError) { - LOGD() << "failed read data, status: " << m_zip->status(); + if (m_zip->hasError()) { + LOGD() << "failed read data"; return ByteArray(); } return data; diff --git a/src/engraving/infrastructure/io/mscwriter.cpp b/src/engraving/infrastructure/io/mscwriter.cpp index 773eb57acf..867a7fc767 100644 --- a/src/engraving/infrastructure/io/mscwriter.cpp +++ b/src/engraving/infrastructure/io/mscwriter.cpp @@ -296,8 +296,8 @@ bool MscWriter::ZipFileWriter::addFileData(const String& fileName, const ByteArr } m_zip->addFile(fileName.toStdString(), data); - if (m_zip->status() != ZipWriter::NoError) { - LOGE() << "failed write files to zip, status: " << m_zip->status(); + if (m_zip->hasError()) { + LOGE() << "failed write files to zip"; return false; } return true; diff --git a/src/framework/global/CMakeLists.txt b/src/framework/global/CMakeLists.txt index 8be0e3efe4..2d73680784 100644 --- a/src/framework/global/CMakeLists.txt +++ b/src/framework/global/CMakeLists.txt @@ -63,10 +63,6 @@ set(MODULE_SRC ${CMAKE_CURRENT_LIST_DIR}/progress.h ${CMAKE_CURRENT_LIST_DIR}/smuflranges.cpp ${CMAKE_CURRENT_LIST_DIR}/smuflranges.h - ${CMAKE_CURRENT_LIST_DIR}/xmlreader.cpp - ${CMAKE_CURRENT_LIST_DIR}/xmlreader.h - ${CMAKE_CURRENT_LIST_DIR}/xmlwriter.cpp - ${CMAKE_CURRENT_LIST_DIR}/xmlwriter.h ${CMAKE_CURRENT_LIST_DIR}/utils.cpp ${CMAKE_CURRENT_LIST_DIR}/utils.h ${CMAKE_CURRENT_LIST_DIR}/defer.h @@ -109,9 +105,9 @@ set(MODULE_SRC ${CMAKE_CURRENT_LIST_DIR}/serialization/zipreader.h ${CMAKE_CURRENT_LIST_DIR}/serialization/zipwriter.cpp ${CMAKE_CURRENT_LIST_DIR}/serialization/zipwriter.h - ${CMAKE_CURRENT_LIST_DIR}/serialization/internal/qzip.cpp - ${CMAKE_CURRENT_LIST_DIR}/serialization/internal/qzipreader_p.h - ${CMAKE_CURRENT_LIST_DIR}/serialization/internal/qzipwriter_p.h + + ${CMAKE_CURRENT_LIST_DIR}/serialization/internal/zipcontainer.cpp + ${CMAKE_CURRENT_LIST_DIR}/serialization/internal/zipcontainer.h ${CMAKE_CURRENT_LIST_DIR}/serialization/textstream.cpp ${CMAKE_CURRENT_LIST_DIR}/serialization/textstream.h ${CMAKE_CURRENT_LIST_DIR}/serialization/json.cpp @@ -124,6 +120,10 @@ if (NOT GLOBAL_NO_INTERNAL) ${CMAKE_CURRENT_LIST_DIR}/globalmodule.h ${CMAKE_CURRENT_LIST_DIR}/settings.cpp ${CMAKE_CURRENT_LIST_DIR}/settings.h + ${CMAKE_CURRENT_LIST_DIR}/xmlreader.cpp + ${CMAKE_CURRENT_LIST_DIR}/xmlreader.h + ${CMAKE_CURRENT_LIST_DIR}/xmlwriter.cpp + ${CMAKE_CURRENT_LIST_DIR}/xmlwriter.h ${CMAKE_CURRENT_LIST_DIR}/internal/application.cpp ${CMAKE_CURRENT_LIST_DIR}/internal/application.h ${CMAKE_CURRENT_LIST_DIR}/internal/globalconfiguration.cpp @@ -137,6 +137,10 @@ if (NOT GLOBAL_NO_INTERNAL) ${CMAKE_CURRENT_LIST_DIR}/io/internal/filesystem.cpp ${CMAKE_CURRENT_LIST_DIR}/io/internal/filesystem.h + + ${CMAKE_CURRENT_LIST_DIR}/serialization/internal/qzip.cpp + ${CMAKE_CURRENT_LIST_DIR}/serialization/internal/qzipreader_p.h + ${CMAKE_CURRENT_LIST_DIR}/serialization/internal/qzipwriter_p.h ) endif() diff --git a/src/framework/global/io/dir.cpp b/src/framework/global/io/dir.cpp index 12317451e5..92c073d60a 100644 --- a/src/framework/global/io/dir.cpp +++ b/src/framework/global/io/dir.cpp @@ -31,6 +31,11 @@ Dir::Dir(const path_t& path) { } +path_t Dir::path() const +{ + return m_path; +} + path_t Dir::absolutePath() const { return fileSystem()->absoluteFilePath(m_path); @@ -55,3 +60,30 @@ RetVal Dir::scanFiles(const io::path_t& rootDir, const std::vector< { return fileSystem()->scanFiles(rootDir, filters, mode); } + +path_t Dir::fromNativeSeparators(const path_t& pathName) +{ +#if defined(Q_OS_WIN) + String path = pathName.toString(); + size_t i = path.indexOf(u'\\'); + if (i != mu::nidx) { + String n(path); + if (n.startsWith(u"\\\\?\\")) { + n.remove(0, 4); + i = n.indexOf(u'\\'); + if (i == mu::nidx) { + return n; + } + } + + for (i = 0; i < n.size(); ++i) { + if (n.at(i) == u'\\') { + n[i] = u'/'; + } + } + + return n; + } +#endif + return pathName; +} diff --git a/src/framework/global/io/dir.h b/src/framework/global/io/dir.h index 4812def55d..6564f79e3b 100644 --- a/src/framework/global/io/dir.h +++ b/src/framework/global/io/dir.h @@ -40,6 +40,7 @@ public: Dir() = default; Dir(const path_t& path); + path_t path() const; path_t absolutePath() const; bool exists() const; @@ -50,6 +51,8 @@ public: static RetVal scanFiles(const io::path_t& rootDir, const std::vector& filters, ScanMode mode = ScanMode::FilesInCurrentDirAndSubdirs); + static path_t fromNativeSeparators(const path_t& pathName); + private: path_t m_path; }; diff --git a/src/framework/global/io/fileinfo.cpp b/src/framework/global/io/fileinfo.cpp index 639efadf61..821fd03cf5 100644 --- a/src/framework/global/io/fileinfo.cpp +++ b/src/framework/global/io/fileinfo.cpp @@ -127,6 +127,10 @@ String FileInfo::doSuffix(const String& filePath) } size_t lastSep = filePath.lastIndexOf(u'/'); + if (lastSep == mu::nidx) { + lastSep = 0; + } + if (lastDot < lastSep) { return String(); } @@ -173,3 +177,13 @@ bool FileInfo::exists(const path_t& filePath) { return fileSystem()->exists(filePath); } + +Dir FileInfo::dir() const +{ + size_t lastSep = m_filePath.lastIndexOf(u'/'); + if (lastSep == mu::nidx) { + return Dir("."); + } + String dirPath = m_filePath.mid(0, lastSep); + return Dir(io::path_t(dirPath)); +} diff --git a/src/framework/global/io/fileinfo.h b/src/framework/global/io/fileinfo.h index 3e3172aed0..0ac0fa0101 100644 --- a/src/framework/global/io/fileinfo.h +++ b/src/framework/global/io/fileinfo.h @@ -22,6 +22,9 @@ #ifndef MU_IO_FILEINFO_H #define MU_IO_FILEINFO_H +#include "types/string.h" +#include "dir.h" + #include "modularity/ioc.h" #include "ifilesystem.h" @@ -53,6 +56,8 @@ public: DateTime birthTime() const; DateTime lastModified() const; + Dir dir() const; + private: static String doSuffix(const String& filePath); diff --git a/src/framework/global/io/path.cpp b/src/framework/global/io/path.cpp index 402cdd19e1..3a627c5efb 100644 --- a/src/framework/global/io/path.cpp +++ b/src/framework/global/io/path.cpp @@ -21,9 +21,6 @@ */ #include "path.h" -#include -#include - #include "stringutils.h" #include "fileinfo.h" @@ -114,99 +111,98 @@ std::wstring path_t::toStdWString() const std::string mu::io::suffix(const mu::io::path_t& path) { - QFileInfo fi(path.toQString()); - return fi.suffix().toLower().toStdString(); + return FileInfo::suffix(path).toLower().toStdString(); } mu::io::path_t mu::io::filename(const mu::io::path_t& path, bool includingExtension) { - QFileInfo fi(path.toQString()); + FileInfo fi(path); return includingExtension ? fi.fileName() : fi.completeBaseName(); } mu::io::path_t mu::io::basename(const mu::io::path_t& path) { - QFileInfo fi(path.toQString()); + FileInfo fi(path); return fi.baseName(); } mu::io::path_t mu::io::completeBasename(const mu::io::path_t& path) { - QFileInfo fi(path.toQString()); + FileInfo fi(path); return fi.completeBaseName(); } mu::io::path_t mu::io::absolutePath(const path_t& path) { - return QFileInfo(path.toQString()).absolutePath(); -} - -mu::io::path_t mu::io::dirname(const mu::io::path_t& path) -{ - return QFileInfo(path.toQString()).dir().dirName(); + return FileInfo(path).absolutePath(); } mu::io::path_t mu::io::dirpath(const mu::io::path_t& path) { - return QFileInfo(path.toQString()).dir().path(); + return FileInfo(path).dir().path(); } mu::io::path_t mu::io::absoluteDirpath(const mu::io::path_t& path) { - return QFileInfo(path.toQString()).dir().absolutePath(); + return FileInfo(path).dir().absolutePath(); } bool mu::io::isAbsolute(const path_t& path) { - return QFileInfo(path.toQString()).isAbsolute(); + return FileInfo(path).isAbsolute(); } bool mu::io::isAllowedFileName(const path_t& fn_) { - QString fn = basename(fn_).toQString(); + std::string fn = basename(fn_).toStdString(); + if (fn.empty()) { + return false; + } // Windows filenames are not case sensitive. - fn = fn.toUpper(); + fn = String::fromStdString(fn).toUpper().toStdString(); - static const QString illegal="<>:\"|?*"; + static const std::string illegal="<>:\"|?*"; - for (const QChar& c : fn) { + for (const char& c : fn) { // Check for control characters - if (c.toLatin1() > 0 && c.toLatin1() < 32) { + if (c > 0 && c < 32) { return false; } // Check for illegal characters - if (illegal.contains(c)) { - return false; + for (const char& ilc : illegal) { + if (c == ilc) { + return false; + } } } // Check for device names in filenames - static const QStringList devices = { + static const std::vector devices = { "CON", "PRN", "AUX", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9" }; - foreach (const QString& s, devices) { + for (const std::string& s : devices) { if (fn == s) { return false; } } // Check for trailing periods or spaces - if (fn.right(1) == "." || fn.right(1) == " ") { + if (fn.back() == '.' || fn.back() == ' ') { return false; } // Check for pathnames that are too long - if (fn.length() > 96) { + if (fn.size() > 96) { return false; } // Since we are checking for a filename, it mustn't be a directory - if (fn.right(1) == "\\") { + if (fn.back() == '\\') { return false; } @@ -219,20 +215,30 @@ mu::io::path_t mu::io::escapeFileName(const mu::io::path_t& fn_) // special characters in filenames are a constant source // of trouble, this replaces some of them common in german: // - QString fn = fn_.toQString(); + String fn = fn_.toString(); fn = fn.simplified(); - fn = fn.replace(QChar(' '), "_"); - fn = fn.replace(QChar('\n'), "_"); - fn = fn.replace(QChar(0xe4), "ae"); // ä - fn = fn.replace(QChar(0xf6), "oe"); // ö - fn = fn.replace(QChar(0xfc), "ue"); // ü - fn = fn.replace(QChar(0xdf), "ss"); // ß - fn = fn.replace(QChar(0xc4), "Ae"); // Ä - fn = fn.replace(QChar(0xd6), "Oe"); // Ö - fn = fn.replace(QChar(0xdc), "Ue"); // Ü - fn = fn.replace(QChar(0x266d), "b"); // musical flat sign, happen in instrument names, so can happen in part (file) names - fn = fn.replace(QChar(0x266f), "#"); // musical sharp sign, can happen in titles, so can happen in score (file) names - fn = fn.replace(QRegularExpression("[" + QRegularExpression::escape("\\/:*?\"<>|") + "]"), "_"); //FAT/NTFS special chars + fn = fn.replace(' ', '_'); + fn = fn.replace('\n', '_'); + fn = fn.replace(Char(0xe4), u"ae"); // ä + fn = fn.replace(Char(0xf6), u"oe"); // ö + fn = fn.replace(Char(0xfc), u"ue"); // ü + fn = fn.replace(Char(0xdf), u"ss"); // ß + fn = fn.replace(Char(0xc4), u"Ae"); // Ä + fn = fn.replace(Char(0xd6), u"Oe"); // Ö + fn = fn.replace(Char(0xdc), u"Ue"); // Ü + fn = fn.replace(Char(0x266d), u"b"); // musical flat sign, happen in instrument names, so can happen in part (file) names + fn = fn.replace(Char(0x266f), u"#"); // musical sharp sign, can happen in titles, so can happen in score (file) names + //FAT/NTFS special chars + fn = fn.replace('<', '_') + .replace('>', '_') + .replace(':', '_') + .replace('"', '_') + .replace('/', '_') + .replace('\\', '_') + .replace('|', '_') + .replace('?', '_') + .replace('*', '_'); + return fn; } diff --git a/src/framework/global/io/path.h b/src/framework/global/io/path.h index 38c463c5a9..ddc52a5545 100644 --- a/src/framework/global/io/path.h +++ b/src/framework/global/io/path.h @@ -100,7 +100,7 @@ path_t filename(const path_t& path, bool includingExtension = true); path_t basename(const path_t& path); path_t completeBasename(const path_t& path); path_t absolutePath(const path_t& path); -path_t dirname(const path_t& path); +//path_t dirname(const path_t& path); path_t dirpath(const path_t& path); path_t absoluteDirpath(const path_t& path); diff --git a/src/framework/global/serialization/internal/zipcontainer.cpp b/src/framework/global/serialization/internal/zipcontainer.cpp new file mode 100644 index 0000000000..c962c6fde6 --- /dev/null +++ b/src/framework/global/serialization/internal/zipcontainer.cpp @@ -0,0 +1,828 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2021 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "zipcontainer.h" + +#include +#include +#include + +#include "io/dir.h" + +#include "log.h" + +// Zip standard version for archives handled by this API +// (actually, the only basic support of this version is implemented but it is enough for now) +#define ZIP_VERSION 20 + +#if 0 +#define ZDEBUG LOGD +#else +#define ZDEBUG if (0) LOGD +#endif + +using namespace mu::io; + +namespace mu { +static inline uint readUInt(const uint8_t* data) +{ + return (data[0]) + (data[1] << 8) + (data[2] << 16) + (data[3] << 24); +} + +static inline ushort readUShort(const uint8_t* data) +{ + return (data[0]) + (data[1] << 8); +} + +static inline void writeUInt(uint8_t* data, uint i) +{ + data[0] = i & 0xff; + data[1] = (i >> 8) & 0xff; + data[2] = (i >> 16) & 0xff; + data[3] = (i >> 24) & 0xff; +} + +static inline void writeUShort(uint8_t* data, ushort i) +{ + data[0] = i & 0xff; + data[1] = (i >> 8) & 0xff; +} + +static inline void copyUInt(uint8_t* dest, const uint8_t* src) +{ + dest[0] = src[0]; + dest[1] = src[1]; + dest[2] = src[2]; + dest[3] = src[3]; +} + +static inline void copyUShort(uint8_t* dest, const uint8_t* src) +{ + dest[0] = src[0]; + dest[1] = src[1]; +} + +static void writeMSDosDate(uint8_t* dest, const std::tm& dt) +{ + if (dt.tm_year > 0) { + uint16_t time + =(dt.tm_hour << 11) // 5 bit hour + | (dt.tm_min << 5) // 6 bit minute + | (dt.tm_sec >> 1); // 5 bit double seconds + + dest[0] = time & 0xff; + dest[1] = time >> 8; + + uint16_t date + =((dt.tm_year + 1900 - 1980) << 9) // 7 bit year 1980-based + | ((dt.tm_mon + 1) << 5) // 4 bit month + | (dt.tm_mday); // 5 bit day + + dest[2] = char(date); + dest[3] = char(date >> 8); + } else { + dest[0] = 0; + dest[1] = 0; + dest[2] = 0; + dest[3] = 0; + } +} + +static int inflate(Bytef* dest, ulong* destLen, const Bytef* source, ulong sourceLen) +{ + z_stream stream; + int err; + + stream.next_in = const_cast(source); + stream.avail_in = (uInt)sourceLen; + if ((uLong)stream.avail_in != sourceLen) { + return Z_BUF_ERROR; + } + + stream.next_out = dest; + stream.avail_out = (uInt) * destLen; + if ((uLong)stream.avail_out != *destLen) { + return Z_BUF_ERROR; + } + + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + + err = inflateInit2(&stream, -MAX_WBITS); + if (err != Z_OK) { + return err; + } + + err = inflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) { + inflateEnd(&stream); + if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0)) { + return Z_DATA_ERROR; + } + return err; + } + *destLen = stream.total_out; + + err = inflateEnd(&stream); + return err; +} + +static int deflate(Bytef* dest, ulong* destLen, const Bytef* source, ulong sourceLen) +{ + z_stream stream; + int err; + + stream.next_in = const_cast(source); + stream.avail_in = (uInt)sourceLen; + stream.next_out = dest; + stream.avail_out = (uInt) * destLen; + if ((uLong)stream.avail_out != *destLen) { + return Z_BUF_ERROR; + } + + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + stream.opaque = (voidpf)0; + + err = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY); + if (err != Z_OK) { + return err; + } + + err = deflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) { + deflateEnd(&stream); + return err == Z_OK ? Z_BUF_ERROR : err; + } + *destLen = stream.total_out; + + err = deflateEnd(&stream); + return err; +} + +namespace WindowsFileAttributes { +enum { + Dir = 0x10, // FILE_ATTRIBUTE_DIRECTORY + File = 0x80, // FILE_ATTRIBUTE_NORMAL + TypeMask = 0x90, + + ReadOnly = 0x01, // FILE_ATTRIBUTE_READONLY + PermMask = 0x01 +}; +} + +namespace UnixFileAttributes { +enum { + Dir = 0040000, // __S_IFDIR + File = 0100000, // __S_IFREG + SymLink = 0120000, // __S_IFLNK + TypeMask = 0170000, // __S_IFMT + + ReadUser = 0400, // __S_IRUSR + WriteUser = 0200, // __S_IWUSR + ExeUser = 0100, // __S_IXUSR + ReadGroup = 0040, // __S_IRGRP + WriteGroup = 0020, // __S_IWGRP + ExeGroup = 0010, // __S_IXGRP + ReadOther = 0004, // __S_IROTH + WriteOther = 0002, // __S_IWOTH + ExeOther = 0001, // __S_IXOTH + PermMask = 0777 +}; +} + +static std::tm readMSDosDate(const uint8_t* src) +{ + std::tm tm; + uint dosDate = readUInt(src); + uint64_t uDate; + uDate = (uint64_t)(dosDate >> 16); + tm.tm_mday = (uDate & 0x1f); + tm.tm_mon = ((uDate & 0x1E0) >> 5) - 1; + tm.tm_year = (((uDate & 0x0FE00) >> 9) + 1980 - 1900); + tm.tm_hour = ((dosDate & 0xF800) >> 11); + tm.tm_min = ((dosDate & 0x7E0) >> 5); + tm.tm_sec = ((dosDate & 0x1f) << 1); + + return tm; +} + +// for details, see http://www.pkware.com/documents/casestudies/APPNOTE.TXT + +enum HostOS { + HostFAT = 0, + HostAMIGA = 1, + HostVMS = 2, // VAX/VMS + HostUnix = 3, + HostVM_CMS = 4, + HostAtari = 5, // what if it's a minix filesystem? [cjh] + HostHPFS = 6, // filesystem used by OS/2 (and NT 3.x) + HostMac = 7, + HostZ_System = 8, + HostCPM = 9, + HostTOPS20 = 10, // pkzip 2.50 NTFS + HostNTFS = 11, // filesystem used by Windows NT + HostQDOS = 12, // SMS/QDOS + HostAcorn = 13, // Archimedes Acorn RISC OS + HostVFAT = 14, // filesystem used by Windows 95, NT + HostMVS = 15, + HostBeOS = 16, // hybrid POSIX/database filesystem + HostTandem = 17, + HostOS400 = 18, + HostOSX = 19 +}; + +enum GeneralPurposeFlag { + Encrypted = 0x01, + AlgTune1 = 0x02, + AlgTune2 = 0x04, + HasDataDescriptor = 0x08, + PatchedData = 0x20, + StrongEncrypted = 0x40, + Utf8Names = 0x0800, + CentralDirectoryEncrypted = 0x2000 +}; + +enum CompressionMethod { + CompressionMethodStored = 0, + CompressionMethodShrunk = 1, + CompressionMethodReduced1 = 2, + CompressionMethodReduced2 = 3, + CompressionMethodReduced3 = 4, + CompressionMethodReduced4 = 5, + CompressionMethodImploded = 6, + CompressionMethodReservedTokenizing = 7, // reserved for tokenizing + CompressionMethodDeflated = 8, + CompressionMethodDeflated64 = 9, + CompressionMethodPKImploding = 10, + + CompressionMethodBZip2 = 12, + + CompressionMethodLZMA = 14, + + CompressionMethodTerse = 18, + CompressionMethodLz77 = 19, + + CompressionMethodJpeg = 96, + CompressionMethodWavPack = 97, + CompressionMethodPPMd = 98, + CompressionMethodWzAES = 99 +}; + +struct LocalFileHeader +{ + uint8_t signature[4]; // 0x04034b50 + uint8_t version_needed[2]; + uint8_t general_purpose_bits[2]; + uint8_t compression_method[2]; + uint8_t last_mod_file[4]; + uint8_t crc_32[4]; + uint8_t compressed_size[4]; + uint8_t uncompressed_size[4]; + uint8_t file_name_length[2]; + uint8_t extra_field_length[2]; +}; + +struct DataDescriptor +{ + uint8_t crc_32[4]; + uint8_t compressed_size[4]; + uint8_t uncompressed_size[4]; +}; + +struct CentralFileHeader +{ + uint8_t signature[4]; // 0x02014b50 + uint8_t version_made[2]; + uint8_t version_needed[2]; + uint8_t general_purpose_bits[2]; + uint8_t compression_method[2]; + uint8_t last_mod_file[4]; + uint8_t crc_32[4]; + uint8_t compressed_size[4]; + uint8_t uncompressed_size[4]; + uint8_t file_name_length[2]; + uint8_t extra_field_length[2]; + uint8_t file_comment_length[2]; + uint8_t disk_start[2]; + uint8_t internal_file_attributes[2]; + uint8_t external_file_attributes[4]; + uint8_t offset_local_header[4]; + LocalFileHeader toLocalHeader() const; +}; + +struct EndOfDirectory +{ + uint8_t signature[4]; // 0x06054b50 + uint8_t this_disk[2]; + uint8_t start_of_directory_disk[2]; + uint8_t num_dir_entries_this_disk[2]; + uint8_t num_dir_entries[2]; + uint8_t directory_size[4]; + uint8_t dir_start_offset[4]; + uint8_t comment_length[2]; +}; + +struct FileHeader +{ + CentralFileHeader h; + ByteArray file_name; + ByteArray extra_field; + ByteArray file_comment; +}; + +LocalFileHeader CentralFileHeader::toLocalHeader() const +{ + LocalFileHeader h; + writeUInt(h.signature, 0x04034b50); + copyUShort(h.version_needed, version_needed); + copyUShort(h.general_purpose_bits, general_purpose_bits); + copyUShort(h.compression_method, compression_method); + copyUInt(h.last_mod_file, last_mod_file); + copyUInt(h.crc_32, crc_32); + copyUInt(h.compressed_size, compressed_size); + copyUInt(h.uncompressed_size, uncompressed_size); + copyUShort(h.file_name_length, file_name_length); + copyUShort(h.extra_field_length, extra_field_length); + return h; +} + +struct ZipContainer::Impl { + IODevice* device = nullptr; + + bool dirtyFileTree = true; + std::vector fileHeaders; + ByteArray comment; + uint start_of_directory = 0; + ZipContainer::Status status = ZipContainer::NoError; + + ZipContainer::CompressionPolicy compressionPolicy; + + enum EntryType { + Directory, File, Symlink + }; + + void addEntry(EntryType type, const std::string& fileName, const ByteArray& contents); + + Impl(IODevice* d) + : device(d) {} + + void scanFiles(); + ZipContainer::FileInfo fillFileInfo(int index) const; +}; + +void ZipContainer::Impl::scanFiles() +{ + if (!dirtyFileTree) { + return; + } + + if (!(device->isOpen() || device->open(IODevice::ReadOnly))) { + status = ZipContainer::FileOpenError; + return; + } + + if ((device->openMode() & IODevice::ReadOnly) == 0) { // only read the index from readable files. + status = ZipContainer::FileReadError; + return; + } + + dirtyFileTree = false; + uint8_t tmp[4]; + device->read(tmp, 4); + if (readUInt(tmp) != 0x04034b50) { + LOGW("Zip: not a zip file!"); + return; + } + + // find EndOfDirectory header + int i = 0; + int start_of_directory_local = -1; + int num_dir_entries = 0; + EndOfDirectory eod; + while (start_of_directory_local == -1) { + const int pos = device->size() - int(sizeof(EndOfDirectory)) - i; + if (pos < 0 || i > 65535) { + LOGW("Zip: EndOfDirectory not found"); + return; + } + + device->seek(pos); + device->read((uint8_t*)&eod, sizeof(EndOfDirectory)); + if (readUInt(eod.signature) == 0x06054b50) { + break; + } + ++i; + } + + // have the eod + start_of_directory_local = readUInt(eod.dir_start_offset); + num_dir_entries = readUShort(eod.num_dir_entries); + ZDEBUG("start_of_directory at %d, num_dir_entries=%d", start_of_directory_local, num_dir_entries); + int comment_length = readUShort(eod.comment_length); + if (comment_length != i) { + LOGW("Zip: failed to parse zip file."); + } + comment = device->read(std::min(comment_length, i)); + + device->seek(start_of_directory_local); + for (i = 0; i < num_dir_entries; ++i) { + FileHeader header; + int read = device->read((uint8_t*)&header.h, sizeof(CentralFileHeader)); + if (read < (int)sizeof(CentralFileHeader)) { + LOGW("Zip: Failed to read complete header, index may be incomplete"); + break; + } + if (readUInt(header.h.signature) != 0x02014b50) { + LOGW("Zip: invalid header signature, index may be incomplete"); + break; + } + + size_t l = readUShort(header.h.file_name_length); + header.file_name = device->read(l); + if (header.file_name.size() != l) { + LOGW("Zip: Failed to read filename from zip index, index may be incomplete"); + break; + } + l = readUShort(header.h.extra_field_length); + header.extra_field = device->read(l); + if (header.extra_field.size() != l) { + LOGW("Zip: Failed to read extra field in zip file, skipping file, index may be incomplete"); + break; + } + l = readUShort(header.h.file_comment_length); + header.file_comment = device->read(l); + if (header.file_comment.size() != l) { + LOGW("Zip: Failed to read read file comment, index may be incomplete"); + break; + } + + ZDEBUG("found file '%s'", header.file_name.data()); + fileHeaders.push_back(header); + } +} + +ZipContainer::FileInfo ZipContainer::Impl::fillFileInfo(int index) const +{ + ZipContainer::FileInfo fileInfo; + FileHeader header = fileHeaders.at(index); + uint32_t mode = readUInt(header.h.external_file_attributes); + const HostOS hostOS = HostOS(readUShort(header.h.version_made) >> 8); + switch (hostOS) { + case HostUnix: + mode = (mode >> 16) & 0xffff; + switch (mode & UnixFileAttributes::TypeMask) { + case UnixFileAttributes::SymLink: + fileInfo.isSymLink = true; + break; + case UnixFileAttributes::Dir: + fileInfo.isDir = true; + break; + case UnixFileAttributes::File: + default: // ### just for the case; should we warn? + fileInfo.isFile = true; + break; + } + break; + case HostFAT: + case HostNTFS: + case HostHPFS: + case HostVFAT: + switch (mode & WindowsFileAttributes::TypeMask) { + case WindowsFileAttributes::Dir: + fileInfo.isDir = true; + break; + case WindowsFileAttributes::File: + default: + fileInfo.isFile = true; + break; + } + + break; + default: + LOGW("Zip: Zip entry format at %d is not supported.", index); + return fileInfo; // we don't support anything else + } + + // ushort general_purpose_bits = readUShort(header.h.general_purpose_bits); + // if bit 11 is set, the filename and comment fields must be encoded using UTF-8 + // const bool inUtf8 = (general_purpose_bits & Utf8Names) != 0; + fileInfo.filePath = header.file_name.constChar(); + fileInfo.crc = readUInt(header.h.crc_32); + fileInfo.size = readUInt(header.h.uncompressed_size); + fileInfo.lastModified = readMSDosDate(header.h.last_mod_file); + + // fix the file path, if broken (convert separators, eat leading and trailing ones) + fileInfo.filePath = Dir::fromNativeSeparators(fileInfo.filePath).toStdString(); + while (!fileInfo.filePath.empty() && (fileInfo.filePath.front() == '.' || fileInfo.filePath.front() == '/')) { + fileInfo.filePath = fileInfo.filePath.substr(1); + } + while (!fileInfo.filePath.empty() && fileInfo.filePath.back() == '/') { + fileInfo.filePath = fileInfo.filePath.substr(fileInfo.filePath.size() - 1); + } + return fileInfo; +} + +void ZipContainer::Impl::addEntry(EntryType type, const std::string& fileName, const ByteArray& contents) +{ + if (!(device->isOpen() || device->open(IODevice::WriteOnly))) { + status = ZipContainer::FileOpenError; + return; + } + device->seek(start_of_directory); + + // don't compress small files + ZipContainer::CompressionPolicy compression = compressionPolicy; + if (compressionPolicy == ZipContainer::AutoCompress) { + if (contents.size() < 64) { + compression = ZipContainer::NeverCompress; + } else { + compression = ZipContainer::AlwaysCompress; + } + } + + FileHeader header; + std::memset(&header.h, 0, sizeof(CentralFileHeader)); + writeUInt(header.h.signature, 0x02014b50); + + writeUShort(header.h.version_needed, ZIP_VERSION); + writeUInt(header.h.uncompressed_size, contents.size()); + + std::time_t t = std::time(0); // get time now + std::tm* now = std::localtime(&t); + writeMSDosDate(header.h.last_mod_file, *now); + ByteArray data = contents; + if (compression == ZipContainer::AlwaysCompress) { + writeUShort(header.h.compression_method, CompressionMethodDeflated); + + ulong len = contents.size(); + // shamelessly copied form zlib + len += (len >> 12) + (len >> 14) + 11; + int res; + do { + data.resize(len); + res = deflate((uint8_t*)data.data(), &len, (const uint8_t*)contents.constData(), contents.size()); + + switch (res) { + case Z_OK: + data.resize(len); + break; + case Z_MEM_ERROR: + LOGW("Zip: Z_MEM_ERROR: Not enough memory to compress file, skipping"); + data.resize(0); + break; + case Z_BUF_ERROR: + len *= 2; + break; + } + } while (res == Z_BUF_ERROR); + } +// TODO add a check if data.size() > contents.size(). Then try to store the original and revert the compression method to be uncompressed + writeUInt(header.h.compressed_size, data.size()); + uint crc_32 = ::crc32(0, 0, 0); + crc_32 = ::crc32(crc_32, (const uint8_t*)contents.constData(), contents.size()); + writeUInt(header.h.crc_32, crc_32); + + // if bit 11 is set, the filename and comment fields must be encoded using UTF-8 + ushort general_purpose_bits = Utf8Names; // always use utf-8 + writeUShort(header.h.general_purpose_bits, general_purpose_bits); + + //const bool inUtf8 = (general_purpose_bits & Utf8Names) != 0; + header.file_name = ByteArray(fileName.c_str(), fileName.size()); + if (header.file_name.size() > 0xffff) { + LOGW("Zip: Filename is too long, chopping it to 65535 bytes"); + header.file_name = header.file_name.left(0xffff); // ### don't break the utf-8 sequence, if any + } + if (header.file_comment.size() + header.file_name.size() > 0xffff) { + LOGW("Zip: File comment is too long, chopping it to 65535 bytes"); + header.file_comment.truncate(0xffff - header.file_name.size()); // ### don't break the utf-8 sequence, if any + } + writeUShort(header.h.file_name_length, header.file_name.size()); + //h.extra_field_length[2]; + + writeUShort(header.h.version_made, HostUnix << 8); + //uint8_t internal_file_attributes[2]; + //uint8_t external_file_attributes[4]; + uint32_t mode = 0; + switch (type) { + case Symlink: + mode |= UnixFileAttributes::SymLink; + break; + case Directory: + mode |= UnixFileAttributes::Dir; + break; + case File: + mode |= UnixFileAttributes::File; + break; + default: + UNREACHABLE; + break; + } + writeUInt(header.h.external_file_attributes, mode << 16); + writeUInt(header.h.offset_local_header, start_of_directory); + + fileHeaders.push_back(header); + + LocalFileHeader h = header.h.toLocalHeader(); + device->write((const uint8_t*)&h, sizeof(LocalFileHeader)); + device->write(header.file_name); + device->write(data); + start_of_directory = device->pos(); + dirtyFileTree = true; +} + +ZipContainer::ZipContainer(IODevice* device) + : p(new Impl(device)) +{ + assert(device); +} + +ZipContainer::~ZipContainer() +{ + close(); + delete p; +} + +std::vector ZipContainer::fileInfoList() const +{ + p->scanFiles(); + std::vector files; + const int numFileHeaders = p->fileHeaders.size(); + files.reserve(numFileHeaders); + for (int i = 0; i < numFileHeaders; ++i) { + files.push_back(p->fillFileInfo(i)); + } + return files; +} + +int ZipContainer::count() const +{ + p->scanFiles(); + return p->fileHeaders.size(); +} + +ByteArray ZipContainer::fileData(const std::string& fileName) const +{ + p->scanFiles(); + + size_t i; + for (i = 0; i < p->fileHeaders.size(); ++i) { + if (p->fileHeaders.at(i).file_name == ByteArray::fromRawData(fileName.c_str(), fileName.size())) { + break; + } + } + + if (i == p->fileHeaders.size()) { + return ByteArray(); + } + + FileHeader header = p->fileHeaders.at(i); + + ushort version_needed = readUShort(header.h.version_needed); + if (version_needed > ZIP_VERSION) { + LOGW("Zip: .ZIP specification version %d implementationis needed to extract the data.", version_needed); + return ByteArray(); + } + + ushort general_purpose_bits = readUShort(header.h.general_purpose_bits); + int compressed_size = readUInt(header.h.compressed_size); + int uncompressed_size = readUInt(header.h.uncompressed_size); + int start = readUInt(header.h.offset_local_header); + + p->device->seek(start); + LocalFileHeader lh; + p->device->read((uint8_t*)&lh, sizeof(LocalFileHeader)); + uint skip = readUShort(lh.file_name_length) + readUShort(lh.extra_field_length); + p->device->seek(p->device->pos() + skip); + + int compression_method = readUShort(lh.compression_method); + + if ((general_purpose_bits & Encrypted) != 0) { + LOGW("Zip: Unsupported encryption method is needed to extract the data."); + return ByteArray(); + } + + ByteArray compressed = p->device->read(compressed_size); + if (compression_method == CompressionMethodStored) { + // no compression + compressed.truncate(uncompressed_size); + return compressed; + } else if (compression_method == CompressionMethodDeflated) { + // Deflate + //qDebug("compressed=%d", compressed.size()); + compressed.truncate(compressed_size); + ByteArray baunzip; + ulong len = std::max(uncompressed_size, 1); + int res; + do { + baunzip.resize(len); + res = inflate((uint8_t*)baunzip.data(), &len, + (const uint8_t*)compressed.constData(), compressed_size); + + switch (res) { + case Z_OK: + if ((size_t)len != baunzip.size()) { + baunzip.resize(len); + } + break; + case Z_MEM_ERROR: + LOGW("Zip: Z_MEM_ERROR: Not enough memory"); + break; + case Z_BUF_ERROR: + len *= 2; + break; + case Z_DATA_ERROR: + LOGW("Zip: Z_DATA_ERROR: Input data is corrupted"); + break; + } + } while (res == Z_BUF_ERROR); + return baunzip; + } + + LOGW("Zip: Unsupported compression method %d is needed to extract the data.", compression_method); + return ByteArray(); +} + +ZipContainer::Status ZipContainer::status() const +{ + return p->status; +} + +void ZipContainer::setCompressionPolicy(CompressionPolicy policy) +{ + p->compressionPolicy = policy; +} + +ZipContainer::CompressionPolicy ZipContainer::compressionPolicy() const +{ + return p->compressionPolicy; +} + +void ZipContainer::addFile(const std::string& fileName, const ByteArray& data) +{ + p->addEntry(Impl::File, Dir::fromNativeSeparators(fileName).toStdString(), data); +} + +void ZipContainer::addDirectory(const std::string& dirName) +{ + std::string name(Dir::fromNativeSeparators(dirName).toStdString()); + // separator is mandatory + if (name.back() != '/') { + name.push_back('/'); + } + p->addEntry(Impl::Directory, name, ByteArray()); +} + +void ZipContainer::close() +{ + if (!(p->device->openMode() & IODevice::WriteOnly)) { + p->device->close(); + return; + } + + //qDebug("Zip::close writing directory, %d entries", p->fileHeaders.size()); + p->device->seek(p->start_of_directory); + // write new directory + for (size_t i = 0; i < p->fileHeaders.size(); ++i) { + const FileHeader& header = p->fileHeaders.at(i); + p->device->write((const uint8_t*)&header.h, sizeof(CentralFileHeader)); + p->device->write(header.file_name); + p->device->write(header.extra_field); + p->device->write(header.file_comment); + } + int dir_size = p->device->pos() - p->start_of_directory; + // write end of directory + EndOfDirectory eod; + memset(&eod, 0, sizeof(EndOfDirectory)); + writeUInt(eod.signature, 0x06054b50); + //uint8_t this_disk[2]; + //uint8_t start_of_directory_disk[2]; + writeUShort(eod.num_dir_entries_this_disk, p->fileHeaders.size()); + writeUShort(eod.num_dir_entries, p->fileHeaders.size()); + writeUInt(eod.directory_size, dir_size); + writeUInt(eod.dir_start_offset, p->start_of_directory); + writeUShort(eod.comment_length, p->comment.size()); + + p->device->write((const uint8_t*)&eod, sizeof(EndOfDirectory)); + p->device->write(p->comment); + p->device->close(); +} +} diff --git a/src/framework/global/serialization/internal/zipcontainer.h b/src/framework/global/serialization/internal/zipcontainer.h new file mode 100644 index 0000000000..f2a4033191 --- /dev/null +++ b/src/framework/global/serialization/internal/zipcontainer.h @@ -0,0 +1,87 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-CLA-applies + * + * MuseScore + * Music Composition & Notation + * + * Copyright (C) 2021 MuseScore BVBA and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef MU_GLOBAL_ZIPCONTAINER_H +#define MU_GLOBAL_ZIPCONTAINER_H + +#include +#include +#include "io/iodevice.h" + +namespace mu { +class ZipContainer +{ +public: + explicit ZipContainer(io::IODevice* device); + ~ZipContainer(); + + enum Status { + NoError, + FileOpenError, + FileReadError, + FileWriteError, + FileError + }; + + struct FileInfo + { + std::string filePath; + bool isDir = false; + bool isFile = false; + bool isSymLink = false; + uint crc = 0; + int64_t size = 0; + std::tm lastModified; + + bool isValid() const { return isDir || isFile || isSymLink; } + }; + + Status status() const; + + void close(); + + // Read + std::vector fileInfoList() const; + int count() const; + + ByteArray fileData(const std::string& fileName) const; + + // Write + enum CompressionPolicy { + AlwaysCompress, + NeverCompress, + AutoCompress + }; + + void setCompressionPolicy(CompressionPolicy policy); + CompressionPolicy compressionPolicy() const; + + void addFile(const std::string& fileName, const ByteArray& data); + void addDirectory(const std::string& dirName); + +private: + + struct Impl; + Impl* p = nullptr; +}; +} + +#endif // MU_GLOBAL_ZIPCONTAINER_H diff --git a/src/framework/global/serialization/zipreader.cpp b/src/framework/global/serialization/zipreader.cpp index 0addd2d13e..d453694ccf 100644 --- a/src/framework/global/serialization/zipreader.cpp +++ b/src/framework/global/serialization/zipreader.cpp @@ -21,9 +21,7 @@ */ #include "zipreader.h" -#include - -#include "internal/qzipreader_p.h" +#include "internal/zipcontainer.h" #include "io/file.h" using namespace mu; @@ -31,40 +29,36 @@ using namespace mu::io; struct ZipReader::Impl { - MQZipReader* zip = nullptr; - ByteArray data; - QByteArray ba; - QBuffer buf; + ZipContainer* zip = nullptr; + IODevice* device = nullptr; + bool isSelfDevice = false; }; ZipReader::ZipReader(const io::path_t& filePath) : m_filePath(filePath) { m_impl = new Impl(); - File f(filePath); - if (f.open(IODevice::ReadOnly)) { - m_impl->data = f.readAll(); - m_impl->ba = m_impl->data.toQByteArrayNoCopy(); + m_impl->device = new File(filePath); + m_impl->isSelfDevice = true; + if (m_impl->device->open(IODevice::ReadOnly)) { } - m_impl->buf.setBuffer(&m_impl->ba); - m_impl->buf.open(QIODevice::ReadOnly); - m_impl->zip = new MQZipReader(&m_impl->buf); + m_impl->zip = new ZipContainer(m_impl->device); } ZipReader::ZipReader(IODevice* device) { m_impl = new Impl(); - m_impl->data = device->readAll(); - m_impl->ba = m_impl->data.toQByteArrayNoCopy(); - m_impl->buf.setBuffer(&m_impl->ba); - m_impl->buf.open(QIODevice::ReadOnly); - m_impl->zip = new MQZipReader(&m_impl->buf); + m_impl->device = device; + m_impl->zip = new ZipContainer(m_impl->device); } ZipReader::~ZipReader() { close(); delete m_impl->zip; + if (m_impl->isSelfDevice) { + delete m_impl->device; + } delete m_impl; } @@ -78,17 +72,17 @@ void ZipReader::close() m_impl->zip->close(); } -ZipReader::Status ZipReader::status() const +bool ZipReader::hasError() const { - return static_cast(m_impl->zip->status()); + return m_impl->zip->status() != ZipContainer::NoError; } std::vector ZipReader::fileInfoList() const { std::vector ret; - QVector qfis = m_impl->zip->fileInfoList(); - ret.reserve(qfis.size()); - for (const MQZipReader::FileInfo& qfi : qfis) { + std::vector fis = m_impl->zip->fileInfoList(); + ret.reserve(fis.size()); + for (const ZipContainer::FileInfo& qfi : fis) { FileInfo fi; fi.filePath = qfi.filePath; fi.isDir = qfi.isDir; @@ -104,6 +98,5 @@ std::vector ZipReader::fileInfoList() const ByteArray ZipReader::fileData(const std::string& fileName) const { - QByteArray ba = m_impl->zip->fileData(QString::fromStdString(fileName)); - return ByteArray(reinterpret_cast(ba.constData()), ba.size()); + return m_impl->zip->fileData(fileName); } diff --git a/src/framework/global/serialization/zipreader.h b/src/framework/global/serialization/zipreader.h index 724d291038..b78cec6cce 100644 --- a/src/framework/global/serialization/zipreader.h +++ b/src/framework/global/serialization/zipreader.h @@ -32,14 +32,6 @@ class ZipReader { public: - enum Status { - NoError, - FileReadError, - FileOpenError, - FilePermissionsError, - FileError - }; - struct FileInfo { io::path_t filePath; @@ -57,7 +49,7 @@ public: bool exists() const; void close(); - Status status() const; + bool hasError() const; std::vector fileInfoList() const; ByteArray fileData(const std::string& fileName) const; diff --git a/src/framework/global/serialization/zipwriter.cpp b/src/framework/global/serialization/zipwriter.cpp index a0c213eddd..fb3c952bf8 100644 --- a/src/framework/global/serialization/zipwriter.cpp +++ b/src/framework/global/serialization/zipwriter.cpp @@ -21,10 +21,8 @@ */ #include "zipwriter.h" -#include - +#include "internal/zipcontainer.h" #include "io/file.h" -#include "internal/qzipwriter_p.h" #include "log.h" @@ -32,9 +30,7 @@ using namespace mu; struct ZipWriter::Impl { - MQZipWriter* zip = nullptr; - QByteArray data; - QBuffer buf; + ZipContainer* zip = nullptr; bool isClosed = false; }; @@ -47,18 +43,14 @@ ZipWriter::ZipWriter(const io::path_t& filePath) } m_impl = new Impl(); - m_impl->buf.setBuffer(&m_impl->data); - m_impl->buf.open(QIODevice::WriteOnly); - m_impl->zip = new MQZipWriter(&m_impl->buf); + m_impl->zip = new ZipContainer(m_device); } ZipWriter::ZipWriter(io::IODevice* device) { m_device = device; m_impl = new Impl(); - m_impl->buf.setBuffer(&m_impl->data); - m_impl->buf.open(QIODevice::WriteOnly); - m_impl->zip = new MQZipWriter(&m_impl->buf); + m_impl->zip = new ZipContainer(m_device); } ZipWriter::~ZipWriter() @@ -75,10 +67,6 @@ ZipWriter::~ZipWriter() void ZipWriter::flush() { - if (m_device) { - m_device->seek(0); - m_device->write(m_impl->data); - } } void ZipWriter::close() @@ -96,13 +84,13 @@ void ZipWriter::close() m_impl->isClosed = true; } -ZipWriter::Status ZipWriter::status() const +bool ZipWriter::hasError() const { - return static_cast(m_impl->zip->status()); + return m_impl->zip->status() != ZipContainer::NoError; } void ZipWriter::addFile(const std::string& fileName, const ByteArray& data) { - m_impl->zip->addFile(QString::fromStdString(fileName), data.toQByteArrayNoCopy()); + m_impl->zip->addFile(fileName, data); flush(); } diff --git a/src/framework/global/serialization/zipwriter.h b/src/framework/global/serialization/zipwriter.h index 5ef9045ee8..eeebc750d9 100644 --- a/src/framework/global/serialization/zipwriter.h +++ b/src/framework/global/serialization/zipwriter.h @@ -29,20 +29,13 @@ namespace mu { class ZipWriter { public: - enum Status { - NoError, - FileWriteError, - FileOpenError, - FilePermissionsError, - FileError - }; explicit ZipWriter(const io::path_t& filePath); explicit ZipWriter(io::IODevice* device); ~ZipWriter(); void close(); - Status status() const; + bool hasError() const; void addFile(const std::string& fileName, const ByteArray& data); diff --git a/src/framework/global/tests/fileinfo_tests.cpp b/src/framework/global/tests/fileinfo_tests.cpp index 529da8537c..2061a83405 100644 --- a/src/framework/global/tests/fileinfo_tests.cpp +++ b/src/framework/global/tests/fileinfo_tests.cpp @@ -25,6 +25,9 @@ #include "io/fileinfo.h" +#include +#include + using namespace mu::io; class Global_IO_FileInfoTests : public ::testing::Test @@ -370,3 +373,15 @@ TEST_F(Global_IO_FileInfoTests, IsAbsolute) EXPECT_TRUE(ret); } } + +TEST_F(Global_IO_FileInfoTests, DirPath) +{ + { + //! GIVE Some file path + path_t filePath = "/path/to/filename.ext1"; + //! DO + path_t dirPath = FileInfo(filePath).dir().path(); + //! CHECK + EXPECT_EQ(dirPath, "/path/to"); + } +} diff --git a/src/framework/global/types/string.cpp b/src/framework/global/types/string.cpp index 7430ca8733..deb7d66e99 100644 --- a/src/framework/global/types/string.cpp +++ b/src/framework/global/types/string.cpp @@ -116,13 +116,21 @@ char Char::toAscii(char16_t c, bool* ok) bool Char::isLetter(char16_t c) { //! TODO +#ifndef GLOBAL_NO_QT_SUPPORT return QChar::isLetter(c); +#else + return std::isalpha(static_cast(c)); +#endif } bool Char::isSpace(char16_t c) { //! TODO +#ifndef GLOBAL_NO_QT_SUPPORT return QChar::isSpace(c); +#else + return std::isspace(static_cast(c)); +#endif } bool Char::isDigit(char16_t c) @@ -138,21 +146,33 @@ int Char::digitValue() const return m_ch - '0'; } -bool Char::isPunct(char16_t c) +bool Char::isPunct(char16_t ch) { - return QChar::isPunct(c); +#ifndef GLOBAL_NO_QT_SUPPORT + return QChar::isPunct(ch); +#else + return std::ispunct(static_cast(ch)); +#endif } char16_t Char::toLower(char16_t ch) { //! TODO +#ifndef GLOBAL_NO_QT_SUPPORT return QChar::toLower(ch); +#else + return std::tolower(static_cast(ch)); +#endif } char16_t Char::toUpper(char16_t ch) { //! TODO +#ifndef GLOBAL_NO_QT_SUPPORT return QChar::toUpper(ch); +#else + return std::toupper(static_cast(ch)); +#endif } // ============================ @@ -419,6 +439,7 @@ std::u32string String::toStdU32String() const return s32; } +#ifndef GLOBAL_NO_QT_SUPPORT String String::fromQString(const QString& str) { const QChar* qu = str.unicode(); @@ -437,6 +458,8 @@ QString String::toQString() const return QString(reinterpret_cast(u), static_cast(size())); } +#endif + size_t String::size() const { return constStr().size(); @@ -868,7 +891,11 @@ String String::trimmed() const String String::simplified() const { //! TODO +#ifndef GLOBAL_NO_QT_SUPPORT return String::fromQString(toQString().simplified()); +#else + return *this; +#endif } String String::toXmlEscaped(char16_t c) @@ -910,15 +937,29 @@ String String::toXmlEscaped() const String String::toLower() const { //! TODO +#ifndef GLOBAL_NO_QT_SUPPORT QString qs = toQString(); return String::fromQString(qs.toLower()); +#else + String s = *this; + std::u16string& us = s.mutStr(); + std::transform(us.begin(), us.end(), us.begin(), [](char16_t c){ return Char::toLower(c); }); + return s; +#endif } String String::toUpper() const { //! TODO +#ifndef GLOBAL_NO_QT_SUPPORT QString qs = toQString(); return String::fromQString(qs.toUpper()); +#else + String s = *this; + std::u16string& us = s.mutStr(); + std::transform(us.begin(), us.end(), us.begin(), [](char16_t c){ return Char::toUpper(c); }); + return s; +#endif } int String::toInt(bool* ok, int base) const @@ -989,15 +1030,6 @@ double String::toDouble(bool* ok) const // ============================ // StringList // ============================ - -StringList::StringList(const QStringList& l) -{ - reserve(l.size()); - for (const QString& s : l) { - push_back(String::fromQString(s)); - } -} - StringList& StringList::append(const StringList& l) { for (const String& s : l) { @@ -1051,6 +1083,15 @@ void StringList::removeAt(size_t i) erase(begin() + i); } +#ifndef GLOBAL_NO_QT_SUPPORT +StringList::StringList(const QStringList& l) +{ + reserve(l.size()); + for (const QString& s : l) { + push_back(String::fromQString(s)); + } +} + QStringList StringList::toQStringList() const { QStringList l; @@ -1061,6 +1102,7 @@ QStringList StringList::toQStringList() const return l; } +#endif // ============================ // AsciiChar // ============================ diff --git a/src/importexport/guitarpro/internal/gtp/gpconverter.cpp b/src/importexport/guitarpro/internal/gtp/gpconverter.cpp index a161b5b4a8..9e10441a89 100644 --- a/src/importexport/guitarpro/internal/gtp/gpconverter.cpp +++ b/src/importexport/guitarpro/internal/gtp/gpconverter.cpp @@ -275,7 +275,6 @@ void GPConverter::fixPercussion() void GPConverter::setupTabDisplayStyle() { GPDomModel::GPProperties properties = _gpDom->properties(); - using parts_import_t = GPDomModel::TabImportOption; std::vector& partsImportOpts = properties.partsImportOptions; bool importLinkedStaffForce = properties.createLinkedTabForce; diff --git a/src/instrumentsscene/view/staffsettingsmodel.cpp b/src/instrumentsscene/view/staffsettingsmodel.cpp index c1de82cb58..e742302cb6 100644 --- a/src/instrumentsscene/view/staffsettingsmodel.cpp +++ b/src/instrumentsscene/view/staffsettingsmodel.cpp @@ -45,8 +45,8 @@ void StaffSettingsModel::load(const QString& staffId) m_type = staff->staffType()->type(); m_voicesVisibility.clear(); - for (const QVariant& voice: staff->visibilityVoices()) { - m_voicesVisibility << voice.toBool(); + for (const bool& voice : staff->visibilityVoices()) { + m_voicesVisibility << voice; } emit cutawayEnabledChanged(); diff --git a/src/palette/internal/palette.cpp b/src/palette/internal/palette.cpp index d8e4a16bdb..62ded0cefd 100644 --- a/src/palette/internal/palette.cpp +++ b/src/palette/internal/palette.cpp @@ -445,7 +445,7 @@ bool Palette::writeToFile(const QString& p) const } ZipWriter f(path); - if (f.status() != ZipWriter::NoError) { + if (f.hasError()) { showWritingPaletteError(path); return false; } @@ -486,7 +486,7 @@ bool Palette::writeToFile(const QString& p) const f.addFile("palette.xml", cbuf1.data()); } f.close(); - if (f.status() != ZipWriter::NoError) { + if (f.hasError()) { showWritingPaletteError(path); return false; } diff --git a/src/palette/view/widgets/masterpalette.cpp b/src/palette/view/widgets/masterpalette.cpp index f31f5d95c6..cd1face642 100644 --- a/src/palette/view/widgets/masterpalette.cpp +++ b/src/palette/view/widgets/masterpalette.cpp @@ -152,14 +152,14 @@ MasterPalette::MasterPalette(QWidget* parent) m_symbolItem->addChild(child); std::vector symbols = mu::keys(mu::smuflRanges()); - for (int i = 0; i < symbols.size(); i++) { + for (size_t i = 0; i < symbols.size(); i++) { QString symbol = symbols[i].toQString(); if (symbol == mu::SMUFL_ALL_SYMBOLS) { continue; } QTreeWidgetItem* chld = new QTreeWidgetItem(QStringList(symbol)); - chld->setData(0, Qt::UserRole, m_idxAllSymbols + i + 1); + chld->setData(0, Qt::UserRole, m_idxAllSymbols + static_cast(i) + 1); m_symbolItem->addChild(chld); }