Browse Source
[qt_common] asynchronous, cancellable import/export
[qt_common] asynchronous, cancellable import/export
Signed-off-by: crueter <crueter@eden-emu.dev>pull/2700/head
No known key found for this signature in database
GPG Key ID: 425ACD2D4830EBC6
4 changed files with 494 additions and 41 deletions
-
1src/qt_common/CMakeLists.txt
-
351src/qt_common/qt_compress.cpp
-
68src/qt_common/qt_compress.h
-
113src/qt_common/qt_content_util.cpp
@ -0,0 +1,351 @@ |
|||||
|
#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
|
||||
@ -0,0 +1,68 @@ |
|||||
|
#pragma once |
||||
|
|
||||
|
#include <QDir> |
||||
|
#include <QString> |
||||
|
#include <JlCompress.h> |
||||
|
#include "qt_common/qt_common.h" |
||||
|
|
||||
|
/** This is a modified version of JlCompress **/ |
||||
|
namespace QtCommon::Compress { |
||||
|
|
||||
|
/** |
||||
|
* @brief Compress an entire directory and report its progress. |
||||
|
* @param fileCompressed Destination file |
||||
|
* @param dir The directory to compress |
||||
|
* @param options Compression level, etc |
||||
|
* @param callback Callback that takes in two std::size_t (total, progress) and returns false if the current operation should be cancelled. |
||||
|
*/ |
||||
|
bool compressDir(QString fileCompressed, |
||||
|
QString dir, |
||||
|
const JlCompress::Options& options = JlCompress::Options(), |
||||
|
QtCommon::QtProgressCallback callback = {}); |
||||
|
|
||||
|
// Internal // |
||||
|
bool compressSubDir(QuaZip *zip, |
||||
|
QString dir, |
||||
|
QString origDir, |
||||
|
const JlCompress::Options &options, |
||||
|
std::size_t total, |
||||
|
std::size_t &progress, |
||||
|
QtCommon::QtProgressCallback callback); |
||||
|
|
||||
|
bool compressFile(QuaZip *zip, |
||||
|
QString fileName, |
||||
|
QString fileDest, |
||||
|
const JlCompress::Options &options, |
||||
|
std::size_t total, |
||||
|
std::size_t &progress, |
||||
|
QtCommon::QtProgressCallback callback); |
||||
|
|
||||
|
bool copyData(QIODevice &inFile, |
||||
|
QIODevice &outFile, |
||||
|
std::size_t total, |
||||
|
std::size_t &progress, |
||||
|
QtCommon::QtProgressCallback callback); |
||||
|
|
||||
|
// Extract // |
||||
|
|
||||
|
/** |
||||
|
* @brief Extract a zip file and report its progress. |
||||
|
* @param fileCompressed Compressed file |
||||
|
* @param dir The directory to push the results to |
||||
|
* @param callback Callback that takes in two std::size_t (total, progress) and returns false if the current operation should be cancelled. |
||||
|
*/ |
||||
|
QStringList extractDir(QString fileCompressed, QString dir, QtCommon::QtProgressCallback callback = {}); |
||||
|
|
||||
|
// Internal // |
||||
|
QStringList extractDir(QuaZip &zip, const QString &dir, QtCommon::QtProgressCallback callback); |
||||
|
|
||||
|
bool extractFile(QuaZip *zip, |
||||
|
QString fileName, |
||||
|
QString fileDest, |
||||
|
std::size_t total, |
||||
|
std::size_t &progress, |
||||
|
QtCommon::QtProgressCallback callback); |
||||
|
|
||||
|
bool removeFile(QStringList listFile); |
||||
|
|
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue