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
-
115src/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