1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-02-03 13:01:33 +02:00

Merge pull request #3341 from IvanSavenko/fix_extraction_progress

Fix extraction progress display for mod installation
This commit is contained in:
Ivan Savenko 2023-12-18 13:34:37 +02:00 committed by GitHub
commit 3f089cce78
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 73 additions and 70 deletions

View File

@ -591,7 +591,7 @@ void CModListView::downloadFile(QString file, QString url, QString description,
this, SLOT(downloadFinished(QStringList,QStringList,QStringList))); this, SLOT(downloadFinished(QStringList,QStringList,QStringList)));
connect(manager.get(), SIGNAL(extractionProgress(qint64,qint64)), connect(manager.get(), SIGNAL(extractionProgress(qint64,qint64)),
this, SLOT(downloadProgress(qint64,qint64))); this, SLOT(extractionProgress(qint64,qint64)));
connect(modModel, &CModListModel::dataChanged, filterModel, &QAbstractItemModel::dataChanged); connect(modModel, &CModListModel::dataChanged, filterModel, &QAbstractItemModel::dataChanged);
@ -613,6 +613,14 @@ void CModListView::downloadProgress(qint64 current, qint64 max)
ui->progressBar->setValue(current / (1024 * 1024)); ui->progressBar->setValue(current / (1024 * 1024));
} }
void CModListView::extractionProgress(qint64 current, qint64 max)
{
// display progress, in extracted files
ui->progressBar->setVisible(true);
ui->progressBar->setMaximum(max);
ui->progressBar->setValue(current);
}
void CModListView::downloadFinished(QStringList savedFiles, QStringList failedFiles, QStringList errors) void CModListView::downloadFinished(QStringList savedFiles, QStringList failedFiles, QStringList errors)
{ {
QString title = tr("Download failed"); QString title = tr("Download failed");

View File

@ -98,6 +98,7 @@ private slots:
void dataChanged(const QModelIndex & topleft, const QModelIndex & bottomRight); void dataChanged(const QModelIndex & topleft, const QModelIndex & bottomRight);
void modSelected(const QModelIndex & current, const QModelIndex & previous); void modSelected(const QModelIndex & current, const QModelIndex & previous);
void downloadProgress(qint64 current, qint64 max); void downloadProgress(qint64 current, qint64 max);
void extractionProgress(qint64 current, qint64 max);
void downloadFinished(QStringList savedFiles, QStringList failedFiles, QStringList errors); void downloadFinished(QStringList savedFiles, QStringList failedFiles, QStringList errors);
void modelReset(); void modelReset();
void hideProgressBar(); void hideProgressBar();

View File

@ -24,7 +24,9 @@ namespace
{ {
QString detectModArchive(QString path, QString modName, std::vector<std::string> & filesToExtract) QString detectModArchive(QString path, QString modName, std::vector<std::string> & filesToExtract)
{ {
filesToExtract = ZipArchive::listFiles(qstringToPath(path)); ZipArchive archive(qstringToPath(path));
filesToExtract = archive.listFiles();
QString modDirName; QString modDirName;
@ -285,14 +287,23 @@ bool CModManager::doInstallMod(QString modname, QString archivePath)
if(!modDirName.size()) if(!modDirName.size())
return addError(modname, "Mod archive is invalid or corrupted"); return addError(modname, "Mod archive is invalid or corrupted");
auto futureExtract = std::async(std::launch::async, [&archivePath, &destDir, &filesToExtract]() std::atomic<int> 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(); qApp->processEvents();
} }

View File

@ -150,22 +150,11 @@ static bool extractCurrent(unzFile file, std::ostream & where)
return false; return false;
} }
std::vector<std::string> ZipArchive::listFiles(const boost::filesystem::path & filename) std::vector<std::string> ZipArchive::listFiles()
{ {
std::vector<std::string> ret; std::vector<std::string> ret;
CDefaultIOApi zipAPI; int result = unzGoToFirstFile(archive);
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);
if (result == UNZ_OK) if (result == UNZ_OK)
{ {
@ -174,73 +163,66 @@ std::vector<std::string> ZipArchive::listFiles(const boost::filesystem::path & f
unz_file_info64 info; unz_file_info64 info;
std::vector<char> zipFilename; std::vector<char> zipFilename;
unzGetCurrentFileInfo64 (file, &info, nullptr, 0, nullptr, 0, nullptr, 0); unzGetCurrentFileInfo64 (archive, &info, nullptr, 0, nullptr, 0, nullptr, 0);
zipFilename.resize(info.size_filename); zipFilename.resize(info.size_filename);
// Get name of current file. Contrary to docs "info" parameter can't be null // Get name of current file. Contrary to docs "info" parameter can't be null
unzGetCurrentFileInfo64(file, &info, zipFilename.data(), static_cast<uLong>(zipFilename.size()), nullptr, 0, nullptr, 0); unzGetCurrentFileInfo64(archive, &info, zipFilename.data(), static_cast<uLong>(zipFilename.size()), nullptr, 0, nullptr, 0);
ret.emplace_back(zipFilename.data(), zipFilename.size()); ret.emplace_back(zipFilename.data(), zipFilename.size());
result = unzGoToNextFile(file); result = unzGoToNextFile(archive);
} }
while (result == UNZ_OK); 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; return ret;
} }
bool ZipArchive::extract(const boost::filesystem::path & from, const boost::filesystem::path & where) ZipArchive::ZipArchive(const boost::filesystem::path & from)
{
// 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<std::string> & what)
{ {
CDefaultIOApi zipAPI; CDefaultIOApi zipAPI;
auto zipStructure = zipAPI.getApiStructure(); auto zipStructure = zipAPI.getApiStructure();
unzFile archive = unzOpen2_64(from.c_str(), &zipStructure); archive = unzOpen2_64(from.c_str(), &zipStructure);
auto onExit = vstd::makeScopeGuard([&]() if (archive == nullptr)
{ throw std::runtime_error("Failed to open file" + from.string() + "'%s'! Unable to list files!");
unzClose(archive); }
});
ZipArchive::~ZipArchive()
{
unzClose(archive);
}
bool ZipArchive::extract(const boost::filesystem::path & where, const std::vector<std::string> & what)
{
for (const std::string & file : what) for (const std::string & file : what)
{ if (!extract(where, file))
if (unzLocateFile(archive, file.c_str(), 1) != UNZ_OK)
return false; return false;
const boost::filesystem::path fullName = where / file; return true;
const boost::filesystem::path fullPath = fullName.parent_path(); }
boost::filesystem::create_directories(fullPath); bool ZipArchive::extract(const boost::filesystem::path & where, const std::string & file)
// directory. No file to extract {
// TODO: better way to detect directory? Probably check return value of unzOpenCurrentFile? if (unzLocateFile(archive, file.c_str(), 1) != UNZ_OK)
if (boost::algorithm::ends_with(file, "/")) return false;
continue;
std::fstream destFile(fullName.c_str(), std::ios::out | std::ios::binary); const boost::filesystem::path fullName = where / file;
if (!destFile.good()) const boost::filesystem::path fullPath = fullName.parent_path();
return false;
if (!extractCurrent(archive, destFile)) boost::filesystem::create_directories(fullPath);
return false; // 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; return true;
} }

View File

@ -61,16 +61,17 @@ public:
std::unordered_set<ResourcePath> getFilteredFiles(std::function<bool(const ResourcePath &)> filter) const override; std::unordered_set<ResourcePath> getFilteredFiles(std::function<bool(const ResourcePath &)> filter) const override;
}; };
namespace ZipArchive class DLL_LINKAGE ZipArchive : boost::noncopyable
{ {
/// List all files present in archive unzFile archive;
std::vector<std::string> DLL_LINKAGE listFiles(const boost::filesystem::path & filename);
/// extracts all files from archive "from" into destination directory "where". Directory must exist public:
bool DLL_LINKAGE extract(const boost::filesystem::path & from, const boost::filesystem::path & where); ZipArchive(const boost::filesystem::path & from);
~ZipArchive();
///same as above, but extracts only files mentioned in "what" list std::vector<std::string> listFiles();
bool DLL_LINKAGE extract(const boost::filesystem::path & from, const boost::filesystem::path & where, const std::vector<std::string> & what); bool extract(const boost::filesystem::path & where, const std::vector<std::string> & what);
} bool extract(const boost::filesystem::path & where, const std::string & what);
};
VCMI_LIB_NAMESPACE_END VCMI_LIB_NAMESPACE_END