summaryrefslogtreecommitdiffstats
path: root/src/libs/installer/extractarchiveoperation.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/installer/extractarchiveoperation.cpp')
-rw-r--r--src/libs/installer/extractarchiveoperation.cpp175
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();