mirror of
https://github.com/PrismLauncher/PrismLauncher.git
synced 2025-08-03 11:27:33 -04:00
move ExportToZipTask
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
parent
cf091e9cc6
commit
b2aee9e846
@ -362,6 +362,15 @@ if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
||||
|
||||
# Find qrcodegencpp-cmake
|
||||
find_package(qrcodegencpp QUIET)
|
||||
|
||||
find_package(LibArchive 3.7.8 QUIET)
|
||||
# Fallback to pkg-config (if available) if CMake files aren't found
|
||||
if(NOT LibArchive_FOUND)
|
||||
find_package(PkgConfig)
|
||||
if(PkgConfig_FOUND)
|
||||
pkg_check_modules(LibArchive IMPORTED_TARGET LibArchive>=3.7.8)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
include(ECMQtDeclareLoggingCategory)
|
||||
|
@ -26,6 +26,10 @@ set(CORE_SOURCES
|
||||
NullInstance.h
|
||||
MMCZip.h
|
||||
MMCZip.cpp
|
||||
archive/ArchiveWriter.cpp
|
||||
archive/ArchiveWriter.h
|
||||
archive/ExportToZipTask.cpp
|
||||
archive/ExportToZipTask.h
|
||||
Untar.h
|
||||
Untar.cpp
|
||||
StringUtils.h
|
||||
@ -1329,6 +1333,12 @@ else()
|
||||
target_link_libraries(Launcher_logic tomlplusplus::tomlplusplus)
|
||||
endif()
|
||||
|
||||
if(TARGET PkgConfig::LibArchive)
|
||||
target_link_libraries(Launcher_logic PkgConfig::LibArchive)
|
||||
else()
|
||||
target_link_libraries(Launcher_logic LibArchive::LibArchive)
|
||||
endif()
|
||||
|
||||
if (UNIX AND NOT CYGWIN AND NOT APPLE)
|
||||
target_link_libraries(Launcher_logic
|
||||
gamemode
|
||||
|
@ -35,6 +35,7 @@
|
||||
*/
|
||||
|
||||
#include "MMCZip.h"
|
||||
#include <archive.h>
|
||||
#include <quazip/quazip.h>
|
||||
#include <quazip/quazipdir.h>
|
||||
#include <quazip/quazipfile.h>
|
||||
@ -47,19 +48,32 @@
|
||||
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
#include <QtConcurrentRun>
|
||||
#include "archive/ArchiveWriter.h"
|
||||
#endif
|
||||
|
||||
namespace MMCZip {
|
||||
// ours
|
||||
bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet<QString>& contained, const FilterFunction& filter)
|
||||
using FilterFunction = std::function<bool(const QString&)>;
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
bool mergeZipFiles(ArchiveWriter& into, QFileInfo from, QSet<QString>& contained, const FilterFunction& filter = nullptr)
|
||||
{
|
||||
QuaZip modZip(from.filePath());
|
||||
modZip.open(QuaZip::mdUnzip);
|
||||
std::unique_ptr<struct archive, int (*)(struct archive*)> modZip(archive_read_new(), archive_read_free);
|
||||
if (!modZip) {
|
||||
qCritical() << "Failed to create archive";
|
||||
}
|
||||
archive_read_support_format_all(modZip.get());
|
||||
|
||||
QuaZipFile fileInsideMod(&modZip);
|
||||
QuaZipFile zipOutFile(into);
|
||||
for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile()) {
|
||||
QString filename = modZip.getCurrentFileName();
|
||||
auto fromUtf8 = from.absoluteFilePath().toUtf8();
|
||||
if (archive_read_open_filename(modZip.get(), fromUtf8.constData(), 10240) != ARCHIVE_OK) {
|
||||
qCritical() << "Failed to open file:" << from << ": " << archive_error_string(modZip.get());
|
||||
return false;
|
||||
}
|
||||
|
||||
archive_entry* entry;
|
||||
|
||||
while (archive_read_next_header(modZip.get(), &entry) == ARCHIVE_OK) {
|
||||
auto name = archive_entry_pathname(entry);
|
||||
auto filename = QString::fromStdString(name);
|
||||
if (filter && !filter(filename)) {
|
||||
qDebug() << "Skipping file " << filename << " from " << from.fileName() << " - filtered";
|
||||
continue;
|
||||
@ -69,32 +83,16 @@ bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet<QString>& contained, const
|
||||
continue;
|
||||
}
|
||||
contained.insert(filename);
|
||||
|
||||
if (!fileInsideMod.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << "Failed to open " << filename << " from " << from.fileName();
|
||||
return false;
|
||||
}
|
||||
|
||||
QuaZipNewInfo info_out(fileInsideMod.getActualFileName());
|
||||
|
||||
if (!zipOutFile.open(QIODevice::WriteOnly, info_out)) {
|
||||
qCritical() << "Failed to open " << filename << " in the jar";
|
||||
fileInsideMod.close();
|
||||
return false;
|
||||
}
|
||||
if (!JlCompress::copyData(fileInsideMod, zipOutFile)) {
|
||||
zipOutFile.close();
|
||||
fileInsideMod.close();
|
||||
if (!into.addFile(modZip.get(), entry)) {
|
||||
qCritical() << "Failed to copy data of " << filename << " into the jar";
|
||||
return false;
|
||||
}
|
||||
zipOutFile.close();
|
||||
fileInsideMod.close();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool compressDirFiles(QuaZip* zip, QString dir, QFileInfoList files, bool followSymlinks)
|
||||
bool compressDirFiles(ArchiveWriter& zip, QString dir, QFileInfoList files)
|
||||
{
|
||||
QDir directory(dir);
|
||||
if (!directory.exists())
|
||||
@ -103,48 +101,18 @@ bool compressDirFiles(QuaZip* zip, QString dir, QFileInfoList files, bool follow
|
||||
for (auto e : files) {
|
||||
auto filePath = directory.relativeFilePath(e.absoluteFilePath());
|
||||
auto srcPath = e.absoluteFilePath();
|
||||
if (followSymlinks) {
|
||||
if (e.isSymLink()) {
|
||||
srcPath = e.symLinkTarget();
|
||||
} else {
|
||||
srcPath = e.canonicalFilePath();
|
||||
}
|
||||
}
|
||||
if (!JlCompress::compressFile(zip, srcPath, filePath))
|
||||
if (!zip.addFile(srcPath, filePath))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks)
|
||||
{
|
||||
QuaZip zip(fileCompressed);
|
||||
zip.setUtf8Enabled(true);
|
||||
QDir().mkpath(QFileInfo(fileCompressed).absolutePath());
|
||||
if (!zip.open(QuaZip::mdCreate)) {
|
||||
FS::deletePath(fileCompressed);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto result = compressDirFiles(&zip, dir, files, followSymlinks);
|
||||
|
||||
zip.close();
|
||||
if (zip.getZipError() != 0) {
|
||||
FS::deletePath(fileCompressed);
|
||||
return false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
// ours
|
||||
bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods)
|
||||
{
|
||||
QuaZip zipOut(targetJarPath);
|
||||
zipOut.setUtf8Enabled(true);
|
||||
if (!zipOut.open(QuaZip::mdCreate)) {
|
||||
ArchiveWriter zipOut(targetJarPath);
|
||||
if (!zipOut.open()) {
|
||||
FS::deletePath(targetJarPath);
|
||||
qCritical() << "Failed to open the minecraft.jar for modding";
|
||||
return false;
|
||||
@ -161,7 +129,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<M
|
||||
if (!mod->enabled())
|
||||
continue;
|
||||
if (mod->type() == ResourceType::ZIPFILE) {
|
||||
if (!mergeZipFiles(&zipOut, mod->fileinfo(), addedFiles)) {
|
||||
if (!mergeZipFiles(zipOut, mod->fileinfo(), addedFiles)) {
|
||||
zipOut.close();
|
||||
FS::deletePath(targetJarPath);
|
||||
qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar.";
|
||||
@ -170,7 +138,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<M
|
||||
} else if (mod->type() == ResourceType::SINGLEFILE) {
|
||||
// FIXME: buggy - does not work with addedFiles
|
||||
auto filename = mod->fileinfo();
|
||||
if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName())) {
|
||||
if (!zipOut.addFile(filename.absoluteFilePath(), filename.fileName())) {
|
||||
zipOut.close();
|
||||
FS::deletePath(targetJarPath);
|
||||
qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar.";
|
||||
@ -193,7 +161,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<M
|
||||
files.removeAll(e);
|
||||
}
|
||||
|
||||
if (!compressDirFiles(&zipOut, parent_dir, files)) {
|
||||
if (!compressDirFiles(zipOut, parent_dir, files)) {
|
||||
zipOut.close();
|
||||
FS::deletePath(targetJarPath);
|
||||
qCritical() << "Failed to add" << mod->fileinfo().fileName() << "to the jar.";
|
||||
@ -209,7 +177,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<M
|
||||
}
|
||||
}
|
||||
|
||||
if (!mergeZipFiles(&zipOut, QFileInfo(sourceJarPath), addedFiles, [](const QString key) { return !key.contains("META-INF"); })) {
|
||||
if (!mergeZipFiles(zipOut, QFileInfo(sourceJarPath), addedFiles, [](const QString key) { return !key.contains("META-INF"); })) {
|
||||
zipOut.close();
|
||||
FS::deletePath(targetJarPath);
|
||||
qCritical() << "Failed to insert minecraft.jar contents.";
|
||||
@ -217,8 +185,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<M
|
||||
}
|
||||
|
||||
// Recompress the jar
|
||||
zipOut.close();
|
||||
if (zipOut.getZipError() != 0) {
|
||||
if (!zipOut.close()) {
|
||||
FS::deletePath(targetJarPath);
|
||||
qCritical() << "Failed to finalize minecraft.jar!";
|
||||
return false;
|
||||
@ -251,22 +218,6 @@ QString findFolderOfFileInZip(QuaZip* zip, const QString& what, const QStringLis
|
||||
return {};
|
||||
}
|
||||
|
||||
// ours
|
||||
bool findFilesInZip(QuaZip* zip, const QString& what, QStringList& result, const QString& root)
|
||||
{
|
||||
QuaZipDir rootDir(zip, root);
|
||||
for (auto fileName : rootDir.entryList(QDir::Files)) {
|
||||
if (fileName == what) {
|
||||
result.append(root);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (auto fileName : rootDir.entryList(QDir::Dirs)) {
|
||||
findFilesInZip(zip, what, result, root + fileName);
|
||||
}
|
||||
return !result.isEmpty();
|
||||
}
|
||||
|
||||
// ours
|
||||
std::optional<QStringList> extractSubDir(QuaZip* zip, const QString& subdir, const QString& target)
|
||||
{
|
||||
@ -455,84 +406,6 @@ bool collectFileListRecursively(const QString& rootDir, const QString& subDir, Q
|
||||
}
|
||||
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
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);
|
||||
}
|
||||
|
||||
auto ExportToZipTask::exportZip() -> ZipResult
|
||||
{
|
||||
if (!m_dir.exists()) {
|
||||
return ZipResult(tr("Folder doesn't exist"));
|
||||
}
|
||||
if (!m_output.isOpen() && !m_output.open(QuaZip::mdCreate)) {
|
||||
return ZipResult(tr("Could not create file"));
|
||||
}
|
||||
|
||||
for (auto fileName : m_extra_files.keys()) {
|
||||
if (m_build_zip_future.isCanceled())
|
||||
return ZipResult();
|
||||
QuaZipFile indexFile(&m_output);
|
||||
if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo(fileName))) {
|
||||
return ZipResult(tr("Could not create:") + fileName);
|
||||
}
|
||||
indexFile.write(m_extra_files[fileName]);
|
||||
}
|
||||
|
||||
for (const QFileInfo& file : m_files) {
|
||||
if (m_build_zip_future.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 (file.isSymLink())
|
||||
absolute = file.symLinkTarget();
|
||||
else
|
||||
absolute = file.canonicalFilePath();
|
||||
}
|
||||
|
||||
if (!m_exclude_files.contains(relative) && !JlCompress::compressFile(&m_output, absolute, m_destination_prefix + relative)) {
|
||||
return ZipResult(tr("Could not read and compress %1").arg(relative));
|
||||
}
|
||||
}
|
||||
|
||||
m_output.close();
|
||||
if (m_output.getZipError() != 0) {
|
||||
return ZipResult(tr("A zip error occurred"));
|
||||
}
|
||||
return ZipResult();
|
||||
}
|
||||
|
||||
void ExportToZipTask::finish()
|
||||
{
|
||||
if (m_build_zip_future.isCanceled()) {
|
||||
FS::deletePath(m_output_path);
|
||||
emitAborted();
|
||||
} else if (auto result = m_build_zip_future.result(); result.has_value()) {
|
||||
FS::deletePath(m_output_path);
|
||||
emitFailed(result.value());
|
||||
} else {
|
||||
emitSucceeded();
|
||||
}
|
||||
}
|
||||
|
||||
bool ExportToZipTask::abort()
|
||||
{
|
||||
if (m_build_zip_future.isRunning()) {
|
||||
m_build_zip_future.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;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ExtractZipTask::executeTask()
|
||||
{
|
||||
|
@ -55,34 +55,8 @@
|
||||
#include "tasks/Task.h"
|
||||
|
||||
namespace MMCZip {
|
||||
using FilterFunction = std::function<bool(const QString&)>;
|
||||
using FilterFileFunction = std::function<bool(const QFileInfo&)>;
|
||||
|
||||
/**
|
||||
* Merge two zip files, using a filter function
|
||||
*/
|
||||
bool mergeZipFiles(QuaZip* into, QFileInfo from, QSet<QString>& contained, const FilterFunction& filter = nullptr);
|
||||
|
||||
/**
|
||||
* Compress directory, by providing a list of files to compress
|
||||
* \param zip target archive
|
||||
* \param dir directory that will be compressed (to compress with relative paths)
|
||||
* \param files list of files to compress
|
||||
* \param followSymlinks should follow symlinks when compressing file data
|
||||
* \return true for success or false for failure
|
||||
*/
|
||||
bool compressDirFiles(QuaZip* zip, QString dir, QFileInfoList files, bool followSymlinks = false);
|
||||
|
||||
/**
|
||||
* Compress directory, by providing a list of files to compress
|
||||
* \param fileCompressed target archive file
|
||||
* \param dir directory that will be compressed (to compress with relative paths)
|
||||
* \param files list of files to compress
|
||||
* \param followSymlinks should follow symlinks when compressing file data
|
||||
* \return true for success or false for failure
|
||||
*/
|
||||
bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks = false);
|
||||
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
/**
|
||||
* take a source jar, add mods to it, resulting in target jar
|
||||
@ -98,14 +72,6 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<M
|
||||
*/
|
||||
QString findFolderOfFileInZip(QuaZip* zip, const QString& what, const QStringList& ignore_paths = {}, const QString& root = QString(""));
|
||||
|
||||
/**
|
||||
* Find a multiple files of the same name in archive by file name
|
||||
* If a file is found in a path, no deeper paths are searched
|
||||
*
|
||||
* \return true if anything was found
|
||||
*/
|
||||
bool findFilesInZip(QuaZip* zip, const QString& what, QStringList& result, const QString& root = QString());
|
||||
|
||||
/**
|
||||
* Extract a subdirectory from an archive
|
||||
*/
|
||||
@ -153,60 +119,6 @@ bool extractFile(QString fileCompressed, QString file, QString dir);
|
||||
bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFileFunction excludeFilter);
|
||||
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
class ExportToZipTask : public Task {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ExportToZipTask(QString outputPath,
|
||||
QDir dir,
|
||||
QFileInfoList files,
|
||||
QString destinationPrefix = "",
|
||||
bool followSymlinks = false,
|
||||
bool utf8Enabled = false)
|
||||
: m_output_path(outputPath)
|
||||
, m_output(outputPath)
|
||||
, m_dir(dir)
|
||||
, m_files(files)
|
||||
, m_destination_prefix(destinationPrefix)
|
||||
, m_follow_symlinks(followSymlinks)
|
||||
{
|
||||
setAbortable(true);
|
||||
m_output.setUtf8Enabled(utf8Enabled);
|
||||
};
|
||||
ExportToZipTask(QString outputPath,
|
||||
QString dir,
|
||||
QFileInfoList files,
|
||||
QString destinationPrefix = "",
|
||||
bool followSymlinks = false,
|
||||
bool utf8Enabled = false)
|
||||
: ExportToZipTask(outputPath, QDir(dir), files, destinationPrefix, followSymlinks, utf8Enabled) {};
|
||||
|
||||
virtual ~ExportToZipTask() = default;
|
||||
|
||||
void setExcludeFiles(QStringList excludeFiles) { m_exclude_files = excludeFiles; }
|
||||
void addExtraFile(QString fileName, QByteArray data) { m_extra_files.insert(fileName, data); }
|
||||
|
||||
using ZipResult = std::optional<QString>;
|
||||
|
||||
protected:
|
||||
virtual void executeTask() override;
|
||||
bool abort() override;
|
||||
|
||||
ZipResult exportZip();
|
||||
void finish();
|
||||
|
||||
private:
|
||||
QString m_output_path;
|
||||
QuaZip 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;
|
||||
|
||||
QFuture<ZipResult> m_build_zip_future;
|
||||
QFutureWatcher<ZipResult> m_build_zip_watcher;
|
||||
};
|
||||
|
||||
class ExtractZipTask : public Task {
|
||||
Q_OBJECT
|
||||
|
211
launcher/archive/ArchiveWriter.cpp
Normal file
211
launcher/archive/ArchiveWriter.cpp
Normal file
@ -0,0 +1,211 @@
|
||||
|
||||
#include "ArchiveWriter.h"
|
||||
#include <archive.h>
|
||||
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace MMCZip {
|
||||
|
||||
ArchiveWriter::ArchiveWriter(const QString& archiveName) : m_filename(archiveName) {}
|
||||
|
||||
ArchiveWriter::~ArchiveWriter()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
bool ArchiveWriter::open()
|
||||
{
|
||||
if (m_filename.isEmpty()) {
|
||||
qCritical() << "Archive m_filename not set.";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_archive = archive_write_new();
|
||||
if (!m_archive) {
|
||||
qCritical() << "Archive not initialized.";
|
||||
return false;
|
||||
}
|
||||
|
||||
QString lowerName = m_filename.toLower();
|
||||
if (lowerName.endsWith(".tar.gz") || lowerName.endsWith(".tgz")) {
|
||||
archive_write_set_format_pax_restricted(m_archive);
|
||||
archive_write_add_filter_gzip(m_archive);
|
||||
} else if (lowerName.endsWith(".tar.bz2") || lowerName.endsWith(".tbz")) {
|
||||
archive_write_set_format_pax_restricted(m_archive);
|
||||
archive_write_add_filter_bzip2(m_archive);
|
||||
} else if (lowerName.endsWith(".tar.xz") || lowerName.endsWith(".txz")) {
|
||||
archive_write_set_format_pax_restricted(m_archive);
|
||||
archive_write_add_filter_xz(m_archive);
|
||||
} else if (lowerName.endsWith(".zip") || lowerName.endsWith(".jar")) {
|
||||
archive_write_set_format_zip(m_archive);
|
||||
} else if (lowerName.endsWith(".tar")) {
|
||||
archive_write_set_format_pax_restricted(m_archive);
|
||||
} else {
|
||||
qCritical() << "Unknown archive format:" << m_filename;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto archiveNameUtf8 = m_filename.toUtf8();
|
||||
if (archive_write_open_filename(m_archive, archiveNameUtf8.constData()) != ARCHIVE_OK) {
|
||||
qCritical() << "Failed to open archive file:" << m_filename << "-" << archive_error_string(m_archive);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ArchiveWriter::close()
|
||||
{
|
||||
bool success = true;
|
||||
if (m_archive) {
|
||||
if (archive_write_close(m_archive) != ARCHIVE_OK) {
|
||||
qCritical() << "Failed to close archive" << m_filename << "-" << archive_error_string(m_archive);
|
||||
success = false;
|
||||
}
|
||||
if (archive_write_free(m_archive) != ARCHIVE_OK) {
|
||||
qCritical() << "Failed to free archive" << m_filename << "-" << archive_error_string(m_archive);
|
||||
success = false;
|
||||
}
|
||||
m_archive = nullptr;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ArchiveWriter::addFile(const QString& fileName, const QString& fileDest)
|
||||
{
|
||||
QFileInfo fileInfo(fileName);
|
||||
if (!fileInfo.exists()) {
|
||||
qCritical() << "File does not exists:" << fileInfo.filePath();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<archive_entry, void (*)(archive_entry*)> entry_ptr(archive_entry_new(), archive_entry_free);
|
||||
auto entry = entry_ptr.get();
|
||||
if (!entry) {
|
||||
qCritical() << "Failed to create archive entry";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto fileDestUtf8 = fileDest.toUtf8();
|
||||
archive_entry_set_pathname(entry, fileDestUtf8.constData());
|
||||
archive_entry_set_perm(entry, fileInfo.permissions() & 0777);
|
||||
|
||||
if (fileInfo.isSymLink()) {
|
||||
auto target = fileInfo.symLinkTarget().toUtf8();
|
||||
archive_entry_set_filetype(entry, AE_IFLNK);
|
||||
archive_entry_set_symlink(entry, target.constData());
|
||||
archive_entry_set_size(entry, 0);
|
||||
|
||||
if (archive_write_header(m_archive, entry) != ARCHIVE_OK) {
|
||||
qCritical() << "Failed to write symlink header for:" << fileDest << "-" << archive_error_string(m_archive);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!fileInfo.isFile()) {
|
||||
qCritical() << "Unsupported file type:" << fileInfo.filePath();
|
||||
return false;
|
||||
}
|
||||
|
||||
QFile file(fileInfo.absoluteFilePath());
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << "Failed to open file: " << fileInfo.filePath();
|
||||
return false;
|
||||
}
|
||||
|
||||
archive_entry_set_filetype(entry, AE_IFREG);
|
||||
archive_entry_set_size(entry, file.size());
|
||||
|
||||
if (archive_write_header(m_archive, entry) != ARCHIVE_OK) {
|
||||
qCritical() << "Failed to write header for: " << fileDest;
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr qint64 chunkSize = 8192;
|
||||
QByteArray buffer;
|
||||
buffer.resize(chunkSize);
|
||||
|
||||
while (!file.atEnd()) {
|
||||
auto bytesRead = file.read(buffer.data(), chunkSize);
|
||||
if (bytesRead < 0) {
|
||||
qCritical() << "Read error in file: " << fileInfo.filePath();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (archive_write_data(m_archive, buffer.constData(), bytesRead) < 0) {
|
||||
qCritical() << "Write error in archive for: " << fileDest;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ArchiveWriter::addFile(const QString& fileDest, const QByteArray& data)
|
||||
{
|
||||
std::unique_ptr<archive_entry, void (*)(archive_entry*)> entry_ptr(archive_entry_new(), archive_entry_free);
|
||||
auto entry = entry_ptr.get();
|
||||
if (!entry) {
|
||||
qCritical() << "Failed to create archive entry";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto fileDestUtf8 = fileDest.toUtf8();
|
||||
archive_entry_set_pathname(entry, fileDestUtf8.constData());
|
||||
archive_entry_set_perm(entry, 0644);
|
||||
|
||||
archive_entry_set_filetype(entry, AE_IFREG);
|
||||
archive_entry_set_size(entry, data.size());
|
||||
|
||||
if (archive_write_header(m_archive, entry) != ARCHIVE_OK) {
|
||||
qCritical() << "Failed to write header for: " << fileDest;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (archive_write_data(m_archive, data.constData(), data.size()) < 0) {
|
||||
qCritical() << "Write error in archive for: " << fileDest;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ArchiveWriter::addFile(archive* src, archive_entry* entry)
|
||||
{
|
||||
if (!src) {
|
||||
qCritical() << "Invalid source archive";
|
||||
return false;
|
||||
}
|
||||
if (!entry) { // if is empty read next header
|
||||
if (auto r = archive_read_next_header(src, &entry); r == ARCHIVE_EOF) {
|
||||
return false;
|
||||
} else if (r != ARCHIVE_OK) {
|
||||
qCritical() << "Failed to read entry from source archive:" << archive_error_string(src);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (archive_write_header(m_archive, entry) != ARCHIVE_OK) {
|
||||
qCritical() << "Failed to write header to entry:" << archive_entry_pathname(entry) << "-" << archive_error_string(m_archive);
|
||||
return false;
|
||||
}
|
||||
const void* buff;
|
||||
size_t size;
|
||||
la_int64_t offset;
|
||||
|
||||
int status;
|
||||
while ((status = archive_read_data_block(src, &buff, &size, &offset)) == ARCHIVE_OK) {
|
||||
if (archive_write_data(m_archive, buff, size) < 0) {
|
||||
qCritical() << "Failed writing data block:" << archive_error_string(m_archive);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (status != ARCHIVE_EOF) {
|
||||
qCritical() << "Failed reading data block:" << archive_error_string(m_archive);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace MMCZip
|
27
launcher/archive/ArchiveWriter.h
Normal file
27
launcher/archive/ArchiveWriter.h
Normal file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <archive.h>
|
||||
#include <archive_entry.h>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QFileDevice>
|
||||
|
||||
namespace MMCZip {
|
||||
|
||||
class ArchiveWriter {
|
||||
public:
|
||||
ArchiveWriter(const QString& archiveName);
|
||||
~ArchiveWriter();
|
||||
|
||||
bool open();
|
||||
bool close();
|
||||
|
||||
bool addFile(const QString& fileName, const QString& fileDest);
|
||||
bool addFile(const QString& fileDest, const QByteArray& data);
|
||||
bool addFile(archive* src, archive_entry* entry = nullptr);
|
||||
|
||||
private:
|
||||
struct archive* m_archive = nullptr;
|
||||
QString m_filename;
|
||||
};
|
||||
} // namespace MMCZip
|
84
launcher/archive/ExportToZipTask.cpp
Normal file
84
launcher/archive/ExportToZipTask.cpp
Normal file
@ -0,0 +1,84 @@
|
||||
|
||||
#include "ExportToZipTask.h"
|
||||
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include "FileSystem.h"
|
||||
|
||||
namespace MMCZip {
|
||||
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);
|
||||
}
|
||||
|
||||
auto ExportToZipTask::exportZip() -> ZipResult
|
||||
{
|
||||
if (!m_dir.exists()) {
|
||||
return ZipResult(tr("Folder doesn't exist"));
|
||||
}
|
||||
if (!m_output.open()) {
|
||||
return ZipResult(tr("Could not create file"));
|
||||
}
|
||||
|
||||
for (auto fileName : m_extra_files.keys()) {
|
||||
if (m_build_zip_future.isCanceled())
|
||||
return ZipResult();
|
||||
if (!m_output.addFile(fileName, m_extra_files[fileName])) {
|
||||
return ZipResult(tr("Could not add:") + fileName);
|
||||
}
|
||||
}
|
||||
|
||||
for (const QFileInfo& file : m_files) {
|
||||
if (m_build_zip_future.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 (file.isSymLink())
|
||||
absolute = file.symLinkTarget();
|
||||
else
|
||||
absolute = file.canonicalFilePath();
|
||||
}
|
||||
|
||||
if (!m_exclude_files.contains(relative) && !m_output.addFile(absolute, m_destination_prefix + relative)) {
|
||||
return ZipResult(tr("Could not read and compress %1").arg(relative));
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_output.close()) {
|
||||
return ZipResult(tr("A zip error occurred"));
|
||||
}
|
||||
return ZipResult();
|
||||
}
|
||||
|
||||
void ExportToZipTask::finish()
|
||||
{
|
||||
if (m_build_zip_future.isCanceled()) {
|
||||
FS::deletePath(m_output_path);
|
||||
emitAborted();
|
||||
} else if (auto result = m_build_zip_future.result(); result.has_value()) {
|
||||
FS::deletePath(m_output_path);
|
||||
emitFailed(result.value());
|
||||
} else {
|
||||
emitSucceeded();
|
||||
}
|
||||
}
|
||||
|
||||
bool ExportToZipTask::abort()
|
||||
{
|
||||
if (m_build_zip_future.isRunning()) {
|
||||
m_build_zip_future.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;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} // namespace MMCZip
|
59
launcher/archive/ExportToZipTask.h
Normal file
59
launcher/archive/ExportToZipTask.h
Normal file
@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfoList>
|
||||
#include <QFuture>
|
||||
#include <QFutureWatcher>
|
||||
|
||||
#include "ArchiveWriter.h"
|
||||
#include "tasks/Task.h"
|
||||
|
||||
namespace MMCZip {
|
||||
class ExportToZipTask : public Task {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ExportToZipTask(QString outputPath, QDir dir, QFileInfoList files, QString destinationPrefix = "", bool followSymlinks = false)
|
||||
: m_output_path(outputPath)
|
||||
, m_output(outputPath)
|
||||
, m_dir(dir)
|
||||
, m_files(files)
|
||||
, m_destination_prefix(destinationPrefix)
|
||||
, m_follow_symlinks(followSymlinks)
|
||||
{
|
||||
setAbortable(true);
|
||||
// m_output.setUtf8Enabled(utf8Enabled); // ignore for now
|
||||
// need to test:
|
||||
// - https://github.com/PrismLauncher/PrismLauncher/pull/2225
|
||||
// - https://github.com/PrismLauncher/PrismLauncher/pull/2353
|
||||
};
|
||||
ExportToZipTask(QString outputPath, QString dir, QFileInfoList files, QString destinationPrefix = "", bool followSymlinks = false)
|
||||
: ExportToZipTask(outputPath, QDir(dir), files, destinationPrefix, followSymlinks) {};
|
||||
|
||||
virtual ~ExportToZipTask() = default;
|
||||
|
||||
void setExcludeFiles(QStringList excludeFiles) { m_exclude_files = excludeFiles; }
|
||||
void addExtraFile(QString fileName, QByteArray data) { m_extra_files.insert(fileName, data); }
|
||||
|
||||
using ZipResult = std::optional<QString>;
|
||||
|
||||
protected:
|
||||
virtual void executeTask() override;
|
||||
bool abort() override;
|
||||
|
||||
ZipResult exportZip();
|
||||
void finish();
|
||||
|
||||
private:
|
||||
QString m_output_path;
|
||||
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;
|
||||
|
||||
QFuture<ZipResult> m_build_zip_future;
|
||||
QFutureWatcher<ZipResult> m_build_zip_watcher;
|
||||
};
|
||||
} // namespace MMCZip
|
@ -31,6 +31,7 @@
|
||||
#include "Application.h"
|
||||
#include "Json.h"
|
||||
#include "MMCZip.h"
|
||||
#include "archive/ExportToZipTask.h"
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "minecraft/mod/ModFolderModel.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
@ -318,7 +319,7 @@ void FlamePackExportTask::buildZip()
|
||||
setStatus(tr("Adding files..."));
|
||||
setProgress(4, 5);
|
||||
|
||||
auto zipTask = makeShared<MMCZip::ExportToZipTask>(m_options.output, m_gameRoot, files, "overrides/", true, false);
|
||||
auto zipTask = makeShared<MMCZip::ExportToZipTask>(m_options.output, m_gameRoot, files, "overrides/", true);
|
||||
zipTask->addExtraFile("manifest.json", generateIndex());
|
||||
zipTask->addExtraFile("modlist.html", generateHTML());
|
||||
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include <QtConcurrentRun>
|
||||
#include "Json.h"
|
||||
#include "MMCZip.h"
|
||||
#include "archive/ExportToZipTask.h"
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "minecraft/mod/MetadataHandler.h"
|
||||
#include "minecraft/mod/ModFolderModel.h"
|
||||
@ -200,7 +201,7 @@ void ModrinthPackExportTask::buildZip()
|
||||
{
|
||||
setStatus(tr("Adding files..."));
|
||||
|
||||
auto zipTask = makeShared<MMCZip::ExportToZipTask>(output, gameRoot, files, "overrides/", true, true);
|
||||
auto zipTask = makeShared<MMCZip::ExportToZipTask>(output, gameRoot, files, "overrides/", true);
|
||||
zipTask->addExtraFile("modrinth.index.json", generateIndex());
|
||||
|
||||
zipTask->setExcludeFiles(resolvedFiles.keys());
|
||||
|
@ -43,6 +43,7 @@
|
||||
#include <QMessageBox>
|
||||
#include "FileIgnoreProxy.h"
|
||||
#include "QObjectPtr.h"
|
||||
#include "archive/ExportToZipTask.h"
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
#include "ui/dialogs/ProgressDialog.h"
|
||||
#include "ui_ExportInstanceDialog.h"
|
||||
@ -150,7 +151,7 @@ void ExportInstanceDialog::doExport()
|
||||
return;
|
||||
}
|
||||
|
||||
auto task = makeShared<MMCZip::ExportToZipTask>(output, m_instance->instanceRoot(), files, "", true, true);
|
||||
auto task = makeShared<MMCZip::ExportToZipTask>(output, m_instance->instanceRoot(), files, "", true);
|
||||
|
||||
connect(task.get(), &Task::failed, this,
|
||||
[this, output](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
|
||||
|
13
vcpkg.json
13
vcpkg.json
@ -2,9 +2,16 @@
|
||||
"dependencies": [
|
||||
"bzip2",
|
||||
"cmark",
|
||||
{ "name": "ecm", "host": true },
|
||||
{ "name": "pkgconf", "host": true },
|
||||
{
|
||||
"name": "ecm",
|
||||
"host": true
|
||||
},
|
||||
{
|
||||
"name": "pkgconf",
|
||||
"host": true
|
||||
},
|
||||
"tomlplusplus",
|
||||
"zlib"
|
||||
"zlib",
|
||||
"libarchive"
|
||||
]
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user