cleanups
This commit is contained in:
+3
-38
@@ -12,10 +12,6 @@
|
||||
#include <QtCore/private/qzipwriter_p.h>
|
||||
|
||||
namespace {
|
||||
|
||||
// Имена внутри архива приходят из файла и превращаются в пути на диске —
|
||||
// «../..» увёл бы распаковку за пределы .minecraft. Правило то же, что у
|
||||
// установщика версий.
|
||||
bool isSafeRelativePath(const QString &path)
|
||||
{
|
||||
if (path.isEmpty() || path.startsWith(u'/') || path.contains(u'\\') || path.contains(u':'))
|
||||
@@ -27,30 +23,20 @@ bool isSafeRelativePath(const QString &path)
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Последняя проверка перед записью: куда бы ни указывало имя из архива после
|
||||
// всех преобразований, файл обязан оказаться внутри папки игры. Правило имён
|
||||
// выше это уже гарантирует, но проверка по готовому пути не зависит от того,
|
||||
// как именно QZipReader разбирает записи.
|
||||
bool isInside(const QDir &root, const QString &absolutePath)
|
||||
{
|
||||
const QString base = QDir::cleanPath(root.absolutePath()) + u'/';
|
||||
return QDir::cleanPath(absolutePath).startsWith(base);
|
||||
}
|
||||
|
||||
// Верхний сегмент относительного пути: по нему проверяется, не лезет ли пак в
|
||||
// общие каталоги лаунчера.
|
||||
QString topSegment(const QString &path)
|
||||
{
|
||||
const int slash = path.indexOf(u'/');
|
||||
return slash < 0 ? path : path.left(slash);
|
||||
}
|
||||
|
||||
// После удаления файлов остаются пустые папки: пустой mods/ вместо удалённого
|
||||
// набора модов выглядит как незавершённая установка.
|
||||
void pruneEmptyDirs(const QDir &root, QStringList dirs)
|
||||
{
|
||||
// Сначала самые глубокие: удаление вложенной может опустошить родителя.
|
||||
std::sort(dirs.begin(), dirs.end(),
|
||||
[](const QString &a, const QString &b) { return a.size() > b.size(); });
|
||||
for (const QString &relative : std::as_const(dirs)) {
|
||||
@@ -62,7 +48,6 @@ void pruneEmptyDirs(const QDir &root, QStringList dirs)
|
||||
}
|
||||
}
|
||||
|
||||
// Файлы, которые попадут в архив, — относительными путями от gameDir.
|
||||
QStringList collectFiles(const QString &gameDir, const QStringList &excludeTop)
|
||||
{
|
||||
QStringList result;
|
||||
@@ -73,7 +58,7 @@ QStringList collectFiles(const QString &gameDir, const QStringList &excludeTop)
|
||||
if (excludeTop.contains(entry.fileName()))
|
||||
continue;
|
||||
if (entry.isSymLink())
|
||||
continue; // ссылку в архив не положить, а идти по ней опасно
|
||||
continue;
|
||||
if (entry.isFile()) {
|
||||
result << entry.fileName();
|
||||
continue;
|
||||
@@ -90,7 +75,7 @@ QStringList collectFiles(const QString &gameDir, const QStringList &excludeTop)
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
}
|
||||
|
||||
BuildArchiveWorker::BuildArchiveWorker(QObject *parent)
|
||||
: QObject(parent)
|
||||
@@ -118,8 +103,6 @@ void BuildArchiveWorker::archive(const QString &gameDir,
|
||||
.arg(QDir::toNativeSeparators(tempZipPath)));
|
||||
return;
|
||||
}
|
||||
// Миры и конфиги сжимаются хорошо, уже сжатые jar-моды — нет: пусть
|
||||
// решает эвристика, а не мы.
|
||||
writer.setCompressionPolicy(QZipWriter::AutoCompress);
|
||||
|
||||
int done = 0;
|
||||
@@ -133,9 +116,6 @@ void BuildArchiveWorker::archive(const QString &gameDir,
|
||||
|
||||
QFile file(root.absoluteFilePath(relative));
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
// Один нечитаемый файл не повод потерять всю сборку, но и молча
|
||||
// выкидывать его нельзя — иначе пользователь узнает о пропаже
|
||||
// уже после очистки .minecraft.
|
||||
writer.close();
|
||||
QFile::remove(tempZipPath);
|
||||
emit finished(false,
|
||||
@@ -166,7 +146,7 @@ void BuildArchiveWorker::clear(const QString &gameDir, const QStringList &keepTo
|
||||
{
|
||||
QDir root(gameDir);
|
||||
if (!root.exists()) {
|
||||
emit finished(true, {}); // чистить нечего
|
||||
emit finished(true, {});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -181,8 +161,6 @@ void BuildArchiveWorker::clear(const QString &gameDir, const QStringList &keepTo
|
||||
emit progress(0, victims.size(), {});
|
||||
int done = 0;
|
||||
for (const QFileInfo &entry : victims) {
|
||||
// Отмена посреди очистки оставила бы полупустую папку, которую нечем
|
||||
// склеить обратно, — на этом шаге её не слушаем.
|
||||
const bool ok = entry.isDir() && !entry.isSymLink()
|
||||
? QDir(entry.absoluteFilePath()).removeRecursively()
|
||||
: QFile::remove(entry.absoluteFilePath());
|
||||
@@ -267,9 +245,6 @@ void BuildArchiveWorker::applyPack(const QString &zipPath,
|
||||
Loc::text("switch.error.archiveOpenFailed").arg(QDir::toNativeSeparators(zipPath)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Весь архив проверяется до первой записи на диск: наполовину раскатанный
|
||||
// пак, отвергнутый на середине, оставил бы сборку в неизвестном состоянии.
|
||||
const QList<QZipReader::FileInfo> entries = reader.fileInfoList();
|
||||
QStringList applied;
|
||||
applied.reserve(entries.size());
|
||||
@@ -299,22 +274,16 @@ void BuildArchiveWorker::applyPack(const QString &zipPath,
|
||||
const int total = removeRelative.size() + applied.size();
|
||||
emit progress(0, total, {});
|
||||
int done = 0;
|
||||
|
||||
// ── Убираем то, чего в новой ревизии больше нет ──────────────────────
|
||||
QStringList touchedDirs;
|
||||
for (const QString &relative : removeRelative) {
|
||||
if (canceled()) {
|
||||
emit finished(false, Loc::text("switch.status.installCancelled"));
|
||||
return;
|
||||
}
|
||||
// Список пришёл из нашего же файла состояния, но он лежит на диске
|
||||
// рядом с игрой и мог быть отредактирован — правила те же, что у архива.
|
||||
if (!isSafeRelativePath(relative) || forbiddenTop.contains(topSegment(relative))) {
|
||||
emit progress(++done, total, relative);
|
||||
continue;
|
||||
}
|
||||
// Файл мог быть удалён игроком или прийти заново в новой ревизии —
|
||||
// и то и другое нормально.
|
||||
const QString target = root.absoluteFilePath(relative);
|
||||
if (!isInside(root, target)) {
|
||||
emit progress(++done, total, relative);
|
||||
@@ -331,8 +300,6 @@ void BuildArchiveWorker::applyPack(const QString &zipPath,
|
||||
emit progress(++done, total, relative);
|
||||
}
|
||||
pruneEmptyDirs(root, touchedDirs);
|
||||
|
||||
// ── Раскладываем новую ревизию поверх ────────────────────────────────
|
||||
for (const QString &relative : std::as_const(applied)) {
|
||||
if (canceled()) {
|
||||
emit finished(false, Loc::text("switch.status.installCancelled"));
|
||||
@@ -355,8 +322,6 @@ void BuildArchiveWorker::applyPack(const QString &zipPath,
|
||||
|
||||
QFile file(target);
|
||||
const QByteArray data = reader.fileData(relative);
|
||||
// QIODevice::Truncate обязателен: поверх более длинного старого файла
|
||||
// иначе остался бы его хвост.
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)
|
||||
|| file.write(data) != data.size()) {
|
||||
emit finished(false,
|
||||
|
||||
Reference in New Issue
Block a user