From 74dedc4cc50bb5fbb7f3414001474ecd5388de3f Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 18 Dec 2023 00:33:32 +0200 Subject: [PATCH] Converted ZipArchive namespace into stateful class --- launcher/modManager/cmodmanager.cpp | 21 +++++-- lib/filesystem/CZipLoader.cpp | 94 ++++++++++++----------------- lib/filesystem/CZipLoader.h | 17 +++--- 3 files changed, 63 insertions(+), 69 deletions(-) diff --git a/launcher/modManager/cmodmanager.cpp b/launcher/modManager/cmodmanager.cpp index 9559630ff..068518553 100644 --- a/launcher/modManager/cmodmanager.cpp +++ b/launcher/modManager/cmodmanager.cpp @@ -24,7 +24,9 @@ namespace { QString detectModArchive(QString path, QString modName, std::vector & filesToExtract) { - filesToExtract = ZipArchive::listFiles(qstringToPath(path)); + ZipArchive archive(qstringToPath(path)); + + filesToExtract = archive.listFiles(); QString modDirName; @@ -285,14 +287,23 @@ bool CModManager::doInstallMod(QString modname, QString archivePath) if(!modDirName.size()) return addError(modname, "Mod archive is invalid or corrupted"); - auto futureExtract = std::async(std::launch::async, [&archivePath, &destDir, &filesToExtract]() + std::atomic filesCounter = 0; + + auto futureExtract = std::async(std::launch::async, [&archivePath, &destDir, &filesCounter, &filesToExtract]() { - return ZipArchive::extract(qstringToPath(archivePath), qstringToPath(destDir), filesToExtract); + ZipArchive archive(qstringToPath(archivePath)); + for (auto const & file : filesToExtract) + { + if (!archive.extract(qstringToPath(destDir), file)) + return false; + ++filesCounter; + } + return true; }); - while(futureExtract.wait_for(std::chrono::milliseconds(50)) != std::future_status::ready) + while(futureExtract.wait_for(std::chrono::milliseconds(10)) != std::future_status::ready) { - emit extractionProgress(0, 0); + emit extractionProgress(filesCounter, filesToExtract.size()); qApp->processEvents(); } diff --git a/lib/filesystem/CZipLoader.cpp b/lib/filesystem/CZipLoader.cpp index e6592f7bd..60e28972e 100644 --- a/lib/filesystem/CZipLoader.cpp +++ b/lib/filesystem/CZipLoader.cpp @@ -150,22 +150,11 @@ static bool extractCurrent(unzFile file, std::ostream & where) return false; } -std::vector ZipArchive::listFiles(const boost::filesystem::path & filename) +std::vector ZipArchive::listFiles() { std::vector ret; - CDefaultIOApi zipAPI; - auto zipStructure = zipAPI.getApiStructure(); - - unzFile file = unzOpen2_64(filename.c_str(), &zipStructure); - - if (file == nullptr) - { - logGlobal->error("Failed to open file '%s'! Unable to list files!", filename.string()); - return {}; - } - - int result = unzGoToFirstFile(file); + int result = unzGoToFirstFile(archive); if (result == UNZ_OK) { @@ -174,73 +163,66 @@ std::vector ZipArchive::listFiles(const boost::filesystem::path & f unz_file_info64 info; std::vector zipFilename; - unzGetCurrentFileInfo64 (file, &info, nullptr, 0, nullptr, 0, nullptr, 0); + unzGetCurrentFileInfo64 (archive, &info, nullptr, 0, nullptr, 0, nullptr, 0); zipFilename.resize(info.size_filename); // Get name of current file. Contrary to docs "info" parameter can't be null - unzGetCurrentFileInfo64(file, &info, zipFilename.data(), static_cast(zipFilename.size()), nullptr, 0, nullptr, 0); + unzGetCurrentFileInfo64(archive, &info, zipFilename.data(), static_cast(zipFilename.size()), nullptr, 0, nullptr, 0); ret.emplace_back(zipFilename.data(), zipFilename.size()); - result = unzGoToNextFile(file); + result = unzGoToNextFile(archive); } while (result == UNZ_OK); - - if (result != UNZ_OK && result != UNZ_END_OF_LIST_OF_FILE) - { - logGlobal->error("Failed to list file from '%s'! Error code %d", filename.string(), result); - } } - else - { - logGlobal->error("Failed to list files from '%s'! Error code %d", filename.string(), result); - } - - unzClose(file); - return ret; } -bool ZipArchive::extract(const boost::filesystem::path & from, const boost::filesystem::path & where) -{ - // Note: may not be fast enough for large archives (should NOT happen with mods) - // because locating each file by name may be slow. Unlikely slower than decompression though - return extract(from, where, listFiles(from)); -} - -bool ZipArchive::extract(const boost::filesystem::path & from, const boost::filesystem::path & where, const std::vector & what) +ZipArchive::ZipArchive(const boost::filesystem::path & from) { CDefaultIOApi zipAPI; auto zipStructure = zipAPI.getApiStructure(); - unzFile archive = unzOpen2_64(from.c_str(), &zipStructure); + archive = unzOpen2_64(from.c_str(), &zipStructure); - auto onExit = vstd::makeScopeGuard([&]() - { - unzClose(archive); - }); + if (archive == nullptr) + throw std::runtime_error("Failed to open file" + from.string() + "'%s'! Unable to list files!"); +} +ZipArchive::~ZipArchive() +{ + unzClose(archive); +} + +bool ZipArchive::extract(const boost::filesystem::path & where, const std::vector & what) +{ for (const std::string & file : what) - { - if (unzLocateFile(archive, file.c_str(), 1) != UNZ_OK) + if (!extract(where, file)) return false; - const boost::filesystem::path fullName = where / file; - const boost::filesystem::path fullPath = fullName.parent_path(); + return true; +} - boost::filesystem::create_directories(fullPath); - // directory. No file to extract - // TODO: better way to detect directory? Probably check return value of unzOpenCurrentFile? - if (boost::algorithm::ends_with(file, "/")) - continue; +bool ZipArchive::extract(const boost::filesystem::path & where, const std::string & file) +{ + if (unzLocateFile(archive, file.c_str(), 1) != UNZ_OK) + return false; - std::fstream destFile(fullName.c_str(), std::ios::out | std::ios::binary); - if (!destFile.good()) - return false; + const boost::filesystem::path fullName = where / file; + const boost::filesystem::path fullPath = fullName.parent_path(); - if (!extractCurrent(archive, destFile)) - return false; - } + boost::filesystem::create_directories(fullPath); + // directory. No file to extract + // TODO: better way to detect directory? Probably check return value of unzOpenCurrentFile? + if (boost::algorithm::ends_with(file, "/")) + return true; + + std::fstream destFile(fullName.c_str(), std::ios::out | std::ios::binary); + if (!destFile.good()) + return false; + + if (!extractCurrent(archive, destFile)) + return false; return true; } diff --git a/lib/filesystem/CZipLoader.h b/lib/filesystem/CZipLoader.h index 139e49188..be5428926 100644 --- a/lib/filesystem/CZipLoader.h +++ b/lib/filesystem/CZipLoader.h @@ -61,16 +61,17 @@ public: std::unordered_set getFilteredFiles(std::function filter) const override; }; -namespace ZipArchive +class DLL_LINKAGE ZipArchive : boost::noncopyable { - /// List all files present in archive - std::vector DLL_LINKAGE listFiles(const boost::filesystem::path & filename); + unzFile archive; - /// extracts all files from archive "from" into destination directory "where". Directory must exist - bool DLL_LINKAGE extract(const boost::filesystem::path & from, const boost::filesystem::path & where); +public: + ZipArchive(const boost::filesystem::path & from); + ~ZipArchive(); - ///same as above, but extracts only files mentioned in "what" list - bool DLL_LINKAGE extract(const boost::filesystem::path & from, const boost::filesystem::path & where, const std::vector & what); -} + std::vector listFiles(); + bool extract(const boost::filesystem::path & where, const std::vector & what); + bool extract(const boost::filesystem::path & where, const std::string & what); +}; VCMI_LIB_NAMESPACE_END