fix(InstanceImportTask): stream archive reads (#4459)

This commit is contained in:
Seth Flynn
2025-12-12 02:41:31 -05:00
committed by GitHub
7 changed files with 91 additions and 91 deletions

View File

@@ -108,35 +108,14 @@ void InstanceImportTask::downloadFromUrl()
filesNetJob->start();
}
QString InstanceImportTask::getRootFromZip(QStringList files)
QString cleanPath(QString path)
{
if (!isRunning()) {
return {};
}
auto cleanPath = [](QString path) {
if (path == ".")
return QString();
QString result = path;
if (result.startsWith("./"))
result = result.mid(2);
return result;
};
for (auto&& fileName : files) {
setDetails(fileName);
QFileInfo fileInfo(fileName);
if (fileInfo.fileName() == "instance.cfg") {
qDebug() << "MultiMC:" << true;
m_modpackType = ModpackType::MultiMC;
return cleanPath(fileInfo.path());
}
if (fileInfo.fileName() == "manifest.json") {
qDebug() << "Flame:" << true;
m_modpackType = ModpackType::Flame;
return cleanPath(fileInfo.path());
}
QCoreApplication::processEvents();
}
return {};
if (path == ".")
return QString();
QString result = path;
if (result.startsWith("./"))
result = result.mid(2);
return result;
}
void InstanceImportTask::processZipPack()
@@ -147,31 +126,53 @@ void InstanceImportTask::processZipPack()
// open the zip and find relevant files in it
MMCZip::ArchiveReader packZip(m_archivePath);
if (!packZip.collectFiles()) {
emitFailed(tr("Unable to open supplied modpack zip file."));
return;
}
qDebug() << "Attempting to determine instance type";
QString root;
// NOTE: Prioritize modpack platforms that aren't searched for recursively.
// Especially Flame has a very common filename for its manifest, which may appear inside overrides for example
// https://docs.modrinth.com/docs/modpacks/format_definition/#storage
if (packZip.exists("/modrinth.index.json")) {
// process as Modrinth pack
qDebug() << "Modrinth:" << true;
m_modpackType = ModpackType::Modrinth;
} else if (packZip.exists("/bin/modpack.jar") || packZip.exists("/bin/version.json")) {
// process as Technic pack
qDebug() << "Technic:" << true;
extractDir.mkpath("minecraft");
extractDir.cd("minecraft");
m_modpackType = ModpackType::Technic;
} else {
root = getRootFromZip(packZip.getFiles());
setDetails("");
auto detectInstance = [this, &extractDir, &root](MMCZip::ArchiveReader::File* f, bool& stop) {
if (!isRunning()) {
stop = true;
return true;
}
auto fileName = f->filename();
if (fileName == "/modrinth.index.json") {
// process as Modrinth pack
qDebug() << "Modrinth:" << true;
m_modpackType = ModpackType::Modrinth;
stop = true;
} else if (fileName == "/bin/modpack.jar" || fileName == "/bin/version.json") {
// process as Technic pack
qDebug() << "Technic:" << true;
extractDir.mkpath("minecraft");
extractDir.cd("minecraft");
m_modpackType = ModpackType::Technic;
stop = true;
} else {
QFileInfo fileInfo(fileName);
if (fileInfo.fileName() == "instance.cfg") {
qDebug() << "MultiMC:" << true;
m_modpackType = ModpackType::MultiMC;
root = cleanPath(fileInfo.path());
stop = true;
return true;
}
if (fileInfo.fileName() == "manifest.json") {
qDebug() << "Flame:" << true;
m_modpackType = ModpackType::Flame;
root = cleanPath(fileInfo.path());
stop = true;
return true;
}
}
QCoreApplication::processEvents();
return true;
};
if (!packZip.parse(detectInstance)) {
emitFailed(tr("Unable to open supplied modpack zip file."));
return;
}
if (m_modpackType == ModpackType::Unknown) {
emitFailed(tr("Archive does not contain a recognized modpack type."));

View File

@@ -56,7 +56,6 @@ class InstanceImportTask : public InstanceTask {
void processTechnic();
void processFlame();
void processModrinth();
QString getRootFromZip(QStringList files);
private slots:
void processZipPack();

View File

@@ -165,7 +165,7 @@ bool ArchiveReader::parse(std::function<bool(File*, bool&)> doStuff)
bool breakControl = false;
while (f->readNextHeader() == ARCHIVE_OK) {
if (!doStuff(f.get(), breakControl)) {
if (f && !doStuff(f.get(), breakControl)) {
qCritical() << "Failed to parse file:" << f->filename() << "-" << f->error();
return false;
}

View File

@@ -26,9 +26,9 @@ void ExportToZipTask::executeTask()
{
setStatus("Adding files...");
setProgress(0, m_files.length());
m_build_zip_future = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { return exportZip(); });
connect(&m_build_zip_watcher, &QFutureWatcher<ZipResult>::finished, this, &ExportToZipTask::finish);
m_build_zip_watcher.setFuture(m_build_zip_future);
m_buildZipFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { return exportZip(); });
connect(&m_buildZipWatcher, &QFutureWatcher<ZipResult>::finished, this, &ExportToZipTask::finish);
m_buildZipWatcher.setFuture(m_buildZipFuture);
}
auto ExportToZipTask::exportZip() -> ZipResult
@@ -40,30 +40,30 @@ auto ExportToZipTask::exportZip() -> ZipResult
return ZipResult(tr("Could not create file"));
}
for (auto fileName : m_extra_files.keys()) {
if (m_build_zip_future.isCanceled())
for (auto fileName : m_extraFiles.keys()) {
if (m_buildZipFuture.isCanceled())
return ZipResult();
if (!m_output.addFile(fileName, m_extra_files[fileName])) {
if (!m_output.addFile(fileName, m_extraFiles[fileName])) {
return ZipResult(tr("Could not add:") + fileName);
}
}
for (const QFileInfo& file : m_files) {
if (m_build_zip_future.isCanceled())
if (m_buildZipFuture.isCanceled())
return ZipResult();
auto absolute = file.absoluteFilePath();
auto relative = m_dir.relativeFilePath(absolute);
setStatus("Compressing: " + relative);
setProgress(m_progress + 1, m_progressTotal);
if (m_follow_symlinks) {
if (m_followSymlinks) {
if (file.isSymLink())
absolute = file.symLinkTarget();
else
absolute = file.canonicalFilePath();
}
if (!m_exclude_files.contains(relative) && !m_output.addFile(absolute, m_destination_prefix + relative)) {
if (!m_excludeFiles.contains(relative) && !m_output.addFile(absolute, m_destinationPrefix + relative)) {
return ZipResult(tr("Could not read and compress %1").arg(relative));
}
}
@@ -76,11 +76,11 @@ auto ExportToZipTask::exportZip() -> ZipResult
void ExportToZipTask::finish()
{
if (m_build_zip_future.isCanceled()) {
FS::deletePath(m_output_path);
if (m_buildZipFuture.isCanceled()) {
FS::deletePath(m_outputPath);
emitAborted();
} else if (auto result = m_build_zip_future.result(); result.has_value()) {
FS::deletePath(m_output_path);
} else if (auto result = m_buildZipFuture.result(); result.has_value()) {
FS::deletePath(m_outputPath);
emitFailed(result.value());
} else {
emitSucceeded();
@@ -89,8 +89,8 @@ void ExportToZipTask::finish()
bool ExportToZipTask::abort()
{
if (m_build_zip_future.isRunning()) {
m_build_zip_future.cancel();
if (m_buildZipFuture.isRunning()) {
m_buildZipFuture.cancel();
// NOTE: Here we don't do `emitAborted()` because it will be done when `m_build_zip_future` actually cancels, which may not occur
// immediately.
return true;

View File

@@ -30,12 +30,12 @@ class ExportToZipTask : public Task {
Q_OBJECT
public:
ExportToZipTask(QString outputPath, QDir dir, QFileInfoList files, QString destinationPrefix = "", bool followSymlinks = false)
: m_output_path(outputPath)
: m_outputPath(outputPath)
, m_output(outputPath)
, m_dir(dir)
, m_files(files)
, m_destination_prefix(destinationPrefix)
, m_follow_symlinks(followSymlinks)
, m_destinationPrefix(destinationPrefix)
, m_followSymlinks(followSymlinks)
{
setAbortable(true);
};
@@ -44,8 +44,8 @@ class ExportToZipTask : public Task {
virtual ~ExportToZipTask() = default;
void setExcludeFiles(QStringList excludeFiles) { m_exclude_files = excludeFiles; }
void addExtraFile(QString fileName, QByteArray data) { m_extra_files.insert(fileName, data); }
void setExcludeFiles(QStringList excludeFiles) { m_excludeFiles = excludeFiles; }
void addExtraFile(QString fileName, QByteArray data) { m_extraFiles.insert(fileName, data); }
using ZipResult = std::optional<QString>;
@@ -57,16 +57,16 @@ class ExportToZipTask : public Task {
void finish();
private:
QString m_output_path;
QString m_outputPath;
ArchiveWriter m_output;
QDir m_dir;
QFileInfoList m_files;
QString m_destination_prefix;
bool m_follow_symlinks;
QStringList m_exclude_files;
QHash<QString, QByteArray> m_extra_files;
QString m_destinationPrefix;
bool m_followSymlinks;
QStringList m_excludeFiles;
QHash<QString, QByteArray> m_extraFiles;
QFuture<ZipResult> m_build_zip_future;
QFutureWatcher<ZipResult> m_build_zip_watcher;
QFuture<ZipResult> m_buildZipFuture;
QFutureWatcher<ZipResult> m_buildZipWatcher;
};
} // namespace MMCZip

View File

@@ -25,14 +25,14 @@ namespace MMCZip {
void ExtractZipTask::executeTask()
{
m_zip_future = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { return extractZip(); });
connect(&m_zip_watcher, &QFutureWatcher<ZipResult>::finished, this, &ExtractZipTask::finish);
m_zip_watcher.setFuture(m_zip_future);
m_zipFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { return extractZip(); });
connect(&m_zipWatcher, &QFutureWatcher<ZipResult>::finished, this, &ExtractZipTask::finish);
m_zipWatcher.setFuture(m_zipFuture);
}
auto ExtractZipTask::extractZip() -> ZipResult
{
auto target = m_output_dir.absolutePath();
auto target = m_outputDir.absolutePath();
auto target_top_dir = QUrl::fromLocalFile(target);
QStringList extracted;
@@ -52,8 +52,9 @@ auto ExtractZipTask::extractZip() -> ZipResult
setStatus("Extracting files...");
setProgress(0, m_input.getFiles().count());
ZipResult result;
auto fileName = m_input.getZipName();
if (!m_input.parse([this, &result, &target, &target_top_dir, ext, &extracted](ArchiveReader::File* f) {
if (m_zip_future.isCanceled())
if (m_zipFuture.isCanceled())
return false;
setProgress(m_progress + 1, m_progressTotal);
QString file_name = f->filename();
@@ -104,17 +105,16 @@ auto ExtractZipTask::extractZip() -> ZipResult
return true;
})) {
FS::removeFiles(extracted);
return result.has_value() || m_zip_future.isCanceled() ? result
: ZipResult(tr("Failed to parse file %1").arg(m_input.getZipName()));
return result.has_value() ? result : ZipResult(tr("Failed to parse file %1").arg(fileName));
}
return ZipResult();
}
void ExtractZipTask::finish()
{
if (m_zip_future.isCanceled()) {
if (m_zipFuture.isCanceled()) {
emitAborted();
} else if (auto result = m_zip_future.result(); result.has_value()) {
} else if (auto result = m_zipFuture.result(); result.has_value()) {
emitFailed(result.value());
} else {
emitSucceeded();
@@ -123,8 +123,8 @@ void ExtractZipTask::finish()
bool ExtractZipTask::abort()
{
if (m_zip_future.isRunning()) {
m_zip_future.cancel();
if (m_zipFuture.isRunning()) {
m_zipFuture.cancel();
// NOTE: Here we don't do `emitAborted()` because it will be done when `m_build_zip_future` actually cancels, which may not occur
// immediately.
return true;

View File

@@ -29,7 +29,7 @@ class ExtractZipTask : public Task {
Q_OBJECT
public:
ExtractZipTask(QString input, QDir outputDir, QString subdirectory = "")
: m_input(input), m_output_dir(outputDir), m_subdirectory(subdirectory)
: m_input(input), m_outputDir(outputDir), m_subdirectory(subdirectory)
{}
virtual ~ExtractZipTask() = default;
@@ -44,10 +44,10 @@ class ExtractZipTask : public Task {
private:
ArchiveReader m_input;
QDir m_output_dir;
QDir m_outputDir;
QString m_subdirectory;
QFuture<ZipResult> m_zip_future;
QFutureWatcher<ZipResult> m_zip_watcher;
QFuture<ZipResult> m_zipFuture;
QFutureWatcher<ZipResult> m_zipWatcher;
};
} // namespace MMCZip