diff options
Diffstat (limited to 'src/libs/installer/extractarchiveoperation.cpp')
-rw-r--r-- | src/libs/installer/extractarchiveoperation.cpp | 175 |
1 files changed, 146 insertions, 29 deletions
diff --git a/src/libs/installer/extractarchiveoperation.cpp b/src/libs/installer/extractarchiveoperation.cpp index c2d541929..b00a67190 100644 --- a/src/libs/installer/extractarchiveoperation.cpp +++ b/src/libs/installer/extractarchiveoperation.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2021 The Qt Company Ltd. +** Copyright (C) 2023 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -30,6 +30,7 @@ #include "constants.h" #include "globals.h" +#include "fileguard.h" #include <QEventLoop> #include <QThreadPool> @@ -45,14 +46,14 @@ namespace QInstaller { */ /*! - \typedef QInstaller::Backup + \typedef QInstaller::ExtractArchiveOperation::Backup Synonym for QPair<QString, QString>. Contains a pair of an original and a generated backup filename for a file. */ /*! - \typedef QInstaller::BackupFiles + \typedef QInstaller::ExtractArchiveOperation::BackupFiles Synonym for QVector<Backup>. */ @@ -65,13 +66,64 @@ namespace QInstaller { ExtractArchiveOperation::ExtractArchiveOperation(PackageManagerCore *core) : UpdateOperation(core) + , m_totalEntries(0) { setName(QLatin1String("Extract")); + setGroup(OperationGroup::Unpack); } void ExtractArchiveOperation::backup() { - // we need to backup on the fly... + if (!checkArgumentCount(2)) + return; + + const QStringList args = arguments(); + const QString archivePath = args.at(0); + const QString targetDir = args.at(1); + + QScopedPointer<AbstractArchive> archive(ArchiveFactory::instance().create(archivePath)); + if (!archive) { + setError(UserDefinedError); + setErrorString(tr("Unsupported archive \"%1\": no handler registered for file suffix \"%2\".") + .arg(archivePath, QFileInfo(archivePath).suffix())); + return; + } + + if (!(archive->open(QIODevice::ReadOnly) && archive->isSupported())) { + setError(UserDefinedError); + setErrorString(tr("Cannot open archive \"%1\" for reading: %2") + .arg(archivePath, archive->errorString())); + return; + } + const QVector<ArchiveEntry> entries = archive->list(); + if (entries.isEmpty()) { + setError(UserDefinedError); + setErrorString(tr("Error while reading contents of archive \"%1\": %2") + .arg(archivePath, archive->errorString())); + return; + } + + const bool hasAdminRights = (packageManager() && packageManager()->hasAdminRights()); + const bool canCreateSymLinks = QInstaller::canCreateSymbolicLinks(); + bool needsAdminRights = false; + + for (auto &entry : entries) { + const QString completeFilePath = targetDir + QDir::separator() + entry.path; + if (!entry.isDirectory) { + // Ignore failed backups, existing files are overwritten when extracting. + // Should the backups be used on rollback too, this may not be the + // desired behavior anymore. + prepareForFile(completeFilePath); + } + if (!hasAdminRights && !canCreateSymLinks && entry.isSymbolicLink) + needsAdminRights = true; + } + m_totalEntries = entries.size(); + if (needsAdminRights) + setValue(QLatin1String("admin"), true); + + // Show something was done + emit progressChanged(scBackupProgressPart); } bool ExtractArchiveOperation::performOperation() @@ -88,27 +140,28 @@ bool ExtractArchiveOperation::performOperation() connect(&callback, &Callback::progressChanged, this, &ExtractArchiveOperation::progressChanged); - if (PackageManagerCore *core = packageManager()) { - connect(core, &PackageManagerCore::statusChanged, &callback, &Callback::statusChanged); - } - - Runnable *runnable = new Runnable(archivePath, targetDir, &callback); - connect(runnable, &Runnable::finished, &receiver, &Receiver::runnableFinished, + Worker *worker = new Worker(archivePath, targetDir, m_totalEntries, &callback); + connect(worker, &Worker::finished, &receiver, &Receiver::workerFinished, Qt::QueuedConnection); + if (PackageManagerCore *core = packageManager()) + connect(core, &PackageManagerCore::statusChanged, worker, &Worker::onStatusChanged); + QFileInfo fileInfo(archivePath); emit outputTextChanged(tr("Extracting \"%1\"").arg(fileInfo.fileName())); + { + QEventLoop loop; + QThread workerThread; + worker->moveToThread(&workerThread); - QEventLoop loop; - connect(&receiver, &Receiver::finished, &loop, &QEventLoop::quit); - if (QThreadPool::globalInstance()->tryStart(runnable)) { + connect(&workerThread, &QThread::started, worker, &Worker::run); + connect(&receiver, &Receiver::finished, &workerThread, &QThread::quit); + connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater); + connect(&workerThread, &QThread::finished, &loop, &QEventLoop::quit); + + workerThread.start(); loop.exec(); - } else { - // HACK: In case there is no availabe thread we should call it directly. - runnable->run(); - receiver.runnableFinished(true, QString()); } - // Write all file names which belongs to a package to a separate file and only the separate // filename to a .dat file. There can be enormous amount of files in a package, which makes // the dat file very slow to read and write. The .dat file is read into memory in startup, @@ -125,10 +178,15 @@ bool ExtractArchiveOperation::performOperation() QString installDir = targetDir; // If we have package manager in use (normal installer run) then use // TargetDir for saving filenames, otherwise those would be saved to - // extracted folder. - if (packageManager()) - installDir = packageManager()->value(QLatin1String("TargetDir")); + // extracted folder. Also initialize installerbasebinary which we use later + // to check if the extracted file in question is the maintenancetool itself. + QString installerBaseBinary; + if (PackageManagerCore *core = packageManager()) { + installDir = core->value(scTargetDir); + installerBaseBinary = core->toNativeSeparators(core->replaceVariables(core->installerBaseBinary())); + } const QString resourcesPath = installDir + QLatin1Char('/') + QLatin1String("installerResources"); + QString fileDirectory = resourcesPath + QLatin1Char('/') + archivePath.section(QLatin1Char('/'), 1, 1, QString::SectionSkipEmpty) + QLatin1Char('/'); QString archiveFileName = archivePath.section(QLatin1Char('/'), 2, 2, QString::SectionSkipEmpty); @@ -139,10 +197,7 @@ bool ExtractArchiveOperation::performOperation() QFileInfo targetDirectoryInfo(fileDirectory); - QDir dir(targetDirectoryInfo.absolutePath()); - if (!dir.exists()) { - dir.mkpath(targetDirectoryInfo.absolutePath()); - } + QInstaller::createDirectoryWithParents(targetDirectoryInfo.absolutePath()); setDefaultFilePermissions(resourcesPath, DefaultFilePermissions::Executable); setDefaultFilePermissions(targetDirectoryInfo.absolutePath(), DefaultFilePermissions::Executable); @@ -151,9 +206,17 @@ bool ExtractArchiveOperation::performOperation() setDefaultFilePermissions(file.fileName(), DefaultFilePermissions::NonExecutable); QDataStream out (&file); for (int i = 0; i < files.count(); ++i) { + if (!installerBaseBinary.isEmpty() && files[i].startsWith(installerBaseBinary)) { + // Do not write installerbase binary filename to extracted files. Installer binary + // is maintenance tool program, the binary is removed elsewhere + // when we do full uninstall. + files.clear(); + break; + } files[i] = replacePath(files.at(i), installDir, QLatin1String(scRelocatable)); } - out << files; + if (!files.isEmpty()) + out << files; setValue(QLatin1String("files"), file.fileName()); file.close(); } else { @@ -163,7 +226,7 @@ bool ExtractArchiveOperation::performOperation() // TODO: Use backups for rollback, too? Doesn't work for uninstallation though. // delete all backups we can delete right now, remember the rest - foreach (const QInstaller::Backup &i, callback.backupFiles()) + foreach (const Backup &i, m_backupFiles) deleteFileNowOrLater(i.second); if (!receiver.success()) { @@ -183,7 +246,7 @@ bool ExtractArchiveOperation::undoOperation() bool useStringListType(value(QLatin1String("files")).type() == QVariant::StringList); QString targetDir = arguments().at(1); if (packageManager()) - targetDir = packageManager()->value(QLatin1String("TargetDir")); + targetDir = packageManager()->value(scTargetDir); QStringList files; if (useStringListType) { files = value(QLatin1String("files")).toStringList(); @@ -191,10 +254,14 @@ bool ExtractArchiveOperation::undoOperation() if (!readDataFileContents(targetDir, &files)) return false; } - startUndoProcess(files); + if (!files.isEmpty()) + startUndoProcess(files); if (!useStringListType) deleteDataFile(m_relocatedDataFileName); + // Remove the installerResources directory if it is empty. + QDir(targetDir).rmdir(QLatin1String("installerResources")); + return true; } @@ -206,6 +273,9 @@ void ExtractArchiveOperation::startUndoProcess(const QStringList &files) connect(thread, &WorkerThread::progressChanged, this, &ExtractArchiveOperation::progressChanged); + const QFileInfo archive(arguments().at(0)); + emit outputTextChanged(tr("Removing files extracted from \"%1\"").arg(archive.fileName())); + QEventLoop loop; connect(thread, &QThread::finished, &loop, &QEventLoop::quit, Qt::QueuedConnection); thread->start(); @@ -230,11 +300,58 @@ void ExtractArchiveOperation::deleteDataFile(const QString &fileName) } } +QString ExtractArchiveOperation::generateBackupName(const QString &fn) +{ + const QString bfn = fn + QLatin1String(".tmpUpdate"); + QString res = bfn; + int i = 0; + while (QFile::exists(res)) + res = bfn + QString::fromLatin1(".%1").arg(i++); + return res; +} + +bool ExtractArchiveOperation::prepareForFile(const QString &filename) +{ + if (!QFile::exists(filename)) + return true; + + FileGuardLocker locker(filename, FileGuard::globalObject()); + + const QString backup = generateBackupName(filename); + QFile f(filename); + const bool renamed = f.rename(backup); + if (f.exists() && !renamed) { + qCritical("Cannot rename %s to %s: %s", qPrintable(filename), qPrintable(backup), + qPrintable(f.errorString())); + return false; + } + m_backupFiles.append(qMakePair(filename, backup)); + return true; +} + bool ExtractArchiveOperation::testOperation() { return true; } +quint64 ExtractArchiveOperation::sizeHint() +{ + if (!checkArgumentCount(2)) + return UpdateOperation::sizeHint(); + + if (hasValue(QLatin1String("sizeHint"))) + return value(QLatin1String("sizeHint")).toULongLong(); + + const QString archivePath = arguments().at(0); + const quint64 compressedSize = QFileInfo(archivePath).size(); + + setValue(QLatin1String("sizeHint"), QString::number(compressedSize)); + + // A rough estimate of how much time it takes to extract this archive. Other + // affecting parameters are the archive format, compression filter and -level. + return compressedSize; +} + bool ExtractArchiveOperation::readDataFileContents(QString &targetDir, QStringList *resultList) { const QString filePath = value(QLatin1String("files")).toString(); |