diff options
| author | crueter <crueter@eden-emu.dev> | 2025-10-15 04:54:41 +0200 |
|---|---|---|
| committer | crueter <crueter@eden-emu.dev> | 2025-10-15 04:54:41 +0200 |
| commit | 5f9dba40a0fe71ccd7014ae37a2fbfd8f225396e (patch) | |
| tree | 4a0258d989bb795dd68fc2067fb01a7d7703868b /src/qt_common/qt_compress.cpp | |
| parent | 0a54ac63f00782605b74044e1661569d931354fe (diff) | |
[desktop] Data Manager, data import/export (#2700)
This adds a "Data Manager" dialog to the Tools menu. The Data Manager allows for the following operations:
- Open w/ system file manager
- Clear
- Export
- Import
On any of the following directories:
- Save (w/ profile selector)
- UserNAND
- SysNAND
- Mods
- Shaders
TODO for the future:
- "Cleanup" for each directory
- TitleID -> Game name--let users clean data for a specific game if applicable
Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2700
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Diffstat (limited to 'src/qt_common/qt_compress.cpp')
| -rw-r--r-- | src/qt_common/qt_compress.cpp | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/src/qt_common/qt_compress.cpp b/src/qt_common/qt_compress.cpp new file mode 100644 index 0000000000..1fc5c4bb2c --- /dev/null +++ b/src/qt_common/qt_compress.cpp @@ -0,0 +1,354 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "qt_compress.h" +#include "quazipfileinfo.h" + +#include <QDirIterator> + +/** This is a modified version of JlCompress **/ +namespace QtCommon::Compress { + +bool compressDir(QString fileCompressed, + QString dir, + const JlCompress::Options &options, + QtCommon::QtProgressCallback callback) +{ + // Create zip + QuaZip zip(fileCompressed); + QDir().mkpath(QFileInfo(fileCompressed).absolutePath()); + if (!zip.open(QuaZip::mdCreate)) { + QFile::remove(fileCompressed); + return false; + } + + // See how big the overall fs structure is + // good approx. of total progress + // TODO(crueter): QDirListing impl + QDirIterator iter(dir, + QDir::NoDotAndDotDot | QDir::Hidden | QDir::Files, + QDirIterator::Subdirectories); + + std::size_t total = 0; + while (iter.hasNext()) { + total += iter.nextFileInfo().size(); + } + + std::size_t progress = 0; + callback(total, progress); + + // Add the files and subdirectories + if (!compressSubDir(&zip, dir, dir, options, total, progress, callback)) { + QFile::remove(fileCompressed); + return false; + } + + // Close zip + zip.close(); + if (zip.getZipError() != 0) { + QFile::remove(fileCompressed); + return false; + } + + return true; +} + +bool compressSubDir(QuaZip *zip, + QString dir, + QString origDir, + const JlCompress::Options &options, + std::size_t total, + std::size_t &progress, + QtProgressCallback callback) +{ + // zip: object where to add the file + // dir: current real directory + // origDir: original real directory + // (path(dir)-path(origDir)) = path inside the zip object + + if (!zip) + return false; + if (zip->getMode() != QuaZip::mdCreate && zip->getMode() != QuaZip::mdAppend + && zip->getMode() != QuaZip::mdAdd) + return false; + + QDir directory(dir); + if (!directory.exists()) + return false; + + + QDir origDirectory(origDir); + if (dir != origDir) { + QuaZipFile dirZipFile(zip); + std::unique_ptr<QuaZipNewInfo> qzni; + if (options.getDateTime().isNull()) { + qzni = std::make_unique<QuaZipNewInfo>(origDirectory.relativeFilePath(dir) + + QLatin1String("/"), + dir); + } else { + qzni = std::make_unique<QuaZipNewInfo>(origDirectory.relativeFilePath(dir) + + QLatin1String("/"), + dir, + options.getDateTime()); + } + if (!dirZipFile.open(QIODevice::WriteOnly, *qzni, nullptr, 0, 0)) { + return false; + } + dirZipFile.close(); + } + + // For each subfolder + QFileInfoList subfiles = directory.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot + | QDir::Hidden | QDir::Dirs); + for (const auto &file : std::as_const(subfiles)) { + if (!compressSubDir( + zip, file.absoluteFilePath(), origDir, options, total, progress, callback)) { + return false; + } + } + + // For each file in directory + QFileInfoList files = directory.entryInfoList(QDir::Hidden | QDir::Files); + for (const auto &file : std::as_const(files)) { + // If it's not a file or it's the compressed file being created + if (!file.isFile() || file.absoluteFilePath() == zip->getZipName()) + continue; + + // Create relative name for the compressed file + QString filename = origDirectory.relativeFilePath(file.absoluteFilePath()); + + // Compress the file + if (!compressFile(zip, file.absoluteFilePath(), filename, options, total, progress, callback)) { + return false; + } + } + + return true; +} + +bool compressFile(QuaZip *zip, + QString fileName, + QString fileDest, + const JlCompress::Options &options, + std::size_t total, + std::size_t &progress, + QtCommon::QtProgressCallback callback) +{ + // zip: object where to add the file + // fileName: real file name + // fileDest: file name inside the zip object + + if (!zip) + return false; + if (zip->getMode() != QuaZip::mdCreate && zip->getMode() != QuaZip::mdAppend + && zip->getMode() != QuaZip::mdAdd) + return false; + + QuaZipFile outFile(zip); + if (options.getDateTime().isNull()) { + if (!outFile.open(QIODevice::WriteOnly, + QuaZipNewInfo(fileDest, fileName), + nullptr, + 0, + options.getCompressionMethod(), + options.getCompressionLevel())) + return false; + } else { + if (!outFile.open(QIODevice::WriteOnly, + QuaZipNewInfo(fileDest, fileName, options.getDateTime()), + nullptr, + 0, + options.getCompressionMethod(), + options.getCompressionLevel())) + return false; + } + + QFileInfo input(fileName); + if (quazip_is_symlink(input)) { + // Not sure if we should use any specialized codecs here. + // After all, a symlink IS just a byte array. And + // this is mostly for Linux, where UTF-8 is ubiquitous these days. + QString path = quazip_symlink_target(input); + QString relativePath = input.dir().relativeFilePath(path); + outFile.write(QFile::encodeName(relativePath)); + } else { + QFile inFile; + inFile.setFileName(fileName); + if (!inFile.open(QIODevice::ReadOnly)) { + return false; + } + if (!copyData(inFile, outFile, total, progress, callback) || outFile.getZipError() != UNZ_OK) { + return false; + } + inFile.close(); + } + + outFile.close(); + return outFile.getZipError() == UNZ_OK; +} + +bool copyData(QIODevice &inFile, + QIODevice &outFile, + std::size_t total, + std::size_t &progress, + QtProgressCallback callback) +{ + while (!inFile.atEnd()) { + char buf[4096]; + qint64 readLen = inFile.read(buf, 4096); + if (readLen <= 0) + return false; + if (outFile.write(buf, readLen) != readLen) + return false; + + progress += readLen; + if (!callback(total, progress)) { + return false; + } + } + return true; +} + +QStringList extractDir(QString fileCompressed, QString dir, QtCommon::QtProgressCallback callback) +{ + // Open zip + QuaZip zip(fileCompressed); + return extractDir(zip, dir, callback); +} + +QStringList extractDir(QuaZip &zip, const QString &dir, QtCommon::QtProgressCallback callback) +{ + if (!zip.open(QuaZip::mdUnzip)) { + return QStringList(); + } + QString cleanDir = QDir::cleanPath(dir); + QDir directory(cleanDir); + QString absCleanDir = directory.absolutePath(); + if (!absCleanDir.endsWith(QLatin1Char('/'))) // It only ends with / if it's the FS root. + absCleanDir += QLatin1Char('/'); + QStringList extracted; + if (!zip.goToFirstFile()) { + return QStringList(); + } + + std::size_t total = 0; + for (const QuaZipFileInfo64 &info : zip.getFileInfoList64()) { + total += info.uncompressedSize; + } + + std::size_t progress = 0; + callback(total, progress); + + do { + QString name = zip.getCurrentFileName(); + QString absFilePath = directory.absoluteFilePath(name); + QString absCleanPath = QDir::cleanPath(absFilePath); + if (!absCleanPath.startsWith(absCleanDir)) + continue; + if (!extractFile(&zip, QLatin1String(""), absFilePath, total, progress, callback)) { + removeFile(extracted); + return QStringList(); + } + extracted.append(absFilePath); + } while (zip.goToNextFile()); + + // Close zip + zip.close(); + if (zip.getZipError() != 0) { + removeFile(extracted); + return QStringList(); + } + + return extracted; +} + +bool extractFile(QuaZip *zip, + QString fileName, + QString fileDest, + std::size_t total, + std::size_t &progress, + QtCommon::QtProgressCallback callback) +{ + // zip: object where to add the file + // filename: real file name + // fileincompress: file name of the compressed file + + if (!zip) + return false; + if (zip->getMode() != QuaZip::mdUnzip) + return false; + + if (!fileName.isEmpty()) + zip->setCurrentFile(fileName); + QuaZipFile inFile(zip); + if (!inFile.open(QIODevice::ReadOnly) || inFile.getZipError() != UNZ_OK) + return false; + + // Check existence of resulting file + QDir curDir; + if (fileDest.endsWith(QLatin1String("/"))) { + if (!curDir.mkpath(fileDest)) { + return false; + } + } else { + if (!curDir.mkpath(QFileInfo(fileDest).absolutePath())) { + return false; + } + } + + QuaZipFileInfo64 info; + if (!zip->getCurrentFileInfo(&info)) + return false; + + QFile::Permissions srcPerm = info.getPermissions(); + if (fileDest.endsWith(QLatin1String("/")) && QFileInfo(fileDest).isDir()) { + if (srcPerm != 0) { + QFile(fileDest).setPermissions(srcPerm); + } + return true; + } + + if (info.isSymbolicLink()) { + QString target = QFile::decodeName(inFile.readAll()); + return QFile::link(target, fileDest); + } + + // Open resulting file + QFile outFile; + outFile.setFileName(fileDest); + if (!outFile.open(QIODevice::WriteOnly)) + return false; + + // Copy data + if (!copyData(inFile, outFile, total, progress, callback) || inFile.getZipError() != UNZ_OK) { + outFile.close(); + removeFile(QStringList(fileDest)); + return false; + } + outFile.close(); + + // Close file + inFile.close(); + if (inFile.getZipError() != UNZ_OK) { + removeFile(QStringList(fileDest)); + return false; + } + + if (srcPerm != 0) { + outFile.setPermissions(srcPerm); + } + return true; +} + +bool removeFile(QStringList listFile) +{ + bool ret = true; + // For each file + for (int i = 0; i < listFile.count(); i++) { + // Remove + ret = ret && QFile::remove(listFile.at(i)); + } + return ret; +} + +} // namespace QtCommon::Compress |
