summaryrefslogtreecommitdiffstats
path: root/src/libs/kdtools/kdupdaterupdateoperations.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/kdtools/kdupdaterupdateoperations.cpp')
-rw-r--r--src/libs/kdtools/kdupdaterupdateoperations.cpp1058
1 files changed, 1058 insertions, 0 deletions
diff --git a/src/libs/kdtools/kdupdaterupdateoperations.cpp b/src/libs/kdtools/kdupdaterupdateoperations.cpp
new file mode 100644
index 000000000..681631542
--- /dev/null
+++ b/src/libs/kdtools/kdupdaterupdateoperations.cpp
@@ -0,0 +1,1058 @@
+/****************************************************************************
+** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved.
+**
+** This file is part of the KD Tools library.
+**
+** Licensees holding valid commercial KD Tools licenses may use this file in
+** accordance with the KD Tools Commercial License Agreement provided with
+** the Software.
+**
+**
+** This file may be distributed and/or modified under the terms of the
+** GNU Lesser General Public License version 2 and version 3 as published by the
+** Free Software Foundation and appearing in the file LICENSE.LGPL included.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+** Contact info@kdab.com if any conditions of this licensing are not
+** clear to you.
+**
+**********************************************************************/
+
+#include "kdupdaterupdateoperations.h"
+#include "kdupdaterapplication.h"
+#include "kdupdaterpackagesinfo.h"
+#include "environment.h"
+
+#include <QFile>
+#include <QDir>
+#include <QDirIterator>
+#include <QProcess>
+#include <QTextStream>
+#include <QDebug>
+#include <QTemporaryFile>
+
+
+#include <cerrno>
+
+#define SUPPORT_DETACHED_PROCESS_EXECUTION
+
+#ifdef SUPPORT_DETACHED_PROCESS_EXECUTION
+#ifdef Q_WS_WIN
+#include <windows.h>
+#endif
+#endif
+
+using namespace KDUpdater;
+
+static bool removeDirectory(const QString &path, QString *errorString)
+{
+ Q_ASSERT(errorString);
+ const QFileInfoList entries = QDir(path).entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries | QDir::Hidden);
+ for (QFileInfoList::const_iterator it = entries.constBegin(); it != entries.constEnd(); ++it) {
+ if (it->isDir() && !it->isSymLink()) {
+ removeDirectory(it->filePath(), errorString);
+ } else {
+ QFile f(it->filePath());
+ if (!f.remove())
+ return false;
+ }
+ }
+
+ errno = 0;
+ const bool success = QDir().rmdir(path);
+ if (errno)
+ *errorString = QLatin1String(strerror(errno));
+ return success;
+}
+/*
+ * \internal
+ * Returns a filename for a temporary file based on \a templateName
+ */
+static QString backupFileName(const QString &templateName)
+{
+ QTemporaryFile file(templateName);
+ file.open();
+ const QString name = file.fileName();
+ file.close();
+ file.remove();
+ return name;
+}
+
+////////////////////////////////////////////////////////////////////////////
+// KDUpdater::CopyOperation
+////////////////////////////////////////////////////////////////////////////
+
+CopyOperation::CopyOperation()
+{
+ setName(QLatin1String("Copy"));
+}
+
+CopyOperation::~CopyOperation()
+{
+ deleteFileNowOrLater(value(QLatin1String("backupOfExistingDestination")).toString());
+}
+
+void CopyOperation::backup()
+{
+ const QString dest = arguments().last();
+ if (!QFile::exists(dest)) {
+ clearValue(QLatin1String("backupOfExistingDestination"));
+ return;
+ }
+
+ setValue(QLatin1String("backupOfExistingDestination"), backupFileName(dest));
+
+ // race condition: The backup file could get created
+ // by another process right now. But this is the same
+ // in QFile::copy...
+ const bool success = QFile::rename(dest, value(QLatin1String("backupOfExistingDestination")).toString());
+ if (!success)
+ setError(UserDefinedError, tr("Could not backup file %1.").arg(dest));
+}
+
+bool CopyOperation::performOperation()
+{
+ // We need two args to complete the copy operation.
+ // First arg provides the complete file name of source
+ // Second arg provides the complete file name of dest
+ QStringList args = this->arguments();
+ if (args.count() != 2) {
+ setError(InvalidArguments);
+ setErrorString(tr("Invalid arguments: %1 arguments given, 2 expected.").arg(args.count()));
+ return false;
+ }
+ QString source = args.first();
+ QString dest = args.last();
+
+ // If destination file exists, then we cannot use QFile::copy()
+ // because it does not overwrite an existing file. So we remove
+ // the destination file.
+ if (QFile::exists(dest)) {
+ QFile file(dest);
+ if (!file.remove()) {
+ setError(UserDefinedError);
+ setErrorString(tr("Could not remove destination file %1: %2").arg(dest, file.errorString()));
+ return false;
+ }
+ }
+
+ QFile file(source);
+ const bool copied = file.copy(dest);
+ if (!copied) {
+ setError(UserDefinedError);
+ setErrorString(tr("Could not copy %1 to %2: %3").arg(source, dest, file.errorString()));
+ }
+ return copied;
+}
+
+bool CopyOperation::undoOperation()
+{
+ const QString dest = arguments().last();
+
+ QFile destF(dest);
+ // first remove the dest
+ if (!destF.remove()) {
+ setError(UserDefinedError, tr("Could not delete file %1: %2").arg(dest, destF.errorString()));
+ return false;
+ }
+
+ // no backup was done:
+ // the copy destination file wasn't existing yet - that's no error
+ if (!hasValue(QLatin1String("backupOfExistingDestination")))
+ return true;
+
+ QFile backupF(value(QLatin1String("backupOfExistingDestination")).toString());
+ // otherwise we have to copy the backup back:
+ const bool success = backupF.rename(dest);
+ if (!success)
+ setError(UserDefinedError, tr("Could not restore backup file into %1: %2").arg(dest, backupF.errorString()));
+ return success;
+}
+
+/*!
+ \reimp
+ */
+QDomDocument CopyOperation::toXml() const
+{
+ // we don't want to save the backupOfExistingDestination
+ if (!hasValue(QLatin1String("backupOfExistingDestination")))
+ return UpdateOperation::toXml();
+
+ CopyOperation *const me = const_cast<CopyOperation *>(this);
+
+ const QVariant v = value(QLatin1String("backupOfExistingDestination"));
+ me->clearValue(QLatin1String("backupOfExistingDestination"));
+ const QDomDocument xml = UpdateOperation::toXml();
+ me->setValue(QLatin1String("backupOfExistingDestination"), v);
+ return xml;
+}
+
+bool CopyOperation::testOperation()
+{
+ // TODO
+ return true;
+}
+
+CopyOperation *CopyOperation::clone() const
+{
+ return new CopyOperation();
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+// KDUpdater::MoveOperation
+////////////////////////////////////////////////////////////////////////////
+
+MoveOperation::MoveOperation()
+{
+ setName(QLatin1String("Move"));
+}
+
+MoveOperation::~MoveOperation()
+{
+ deleteFileNowOrLater(value(QLatin1String("backupOfExistingDestination")).toString());
+}
+
+void MoveOperation::backup()
+{
+ const QString dest = arguments().last();
+ if (!QFile::exists(dest)) {
+ clearValue(QLatin1String("backupOfExistingDestination"));
+ return;
+ }
+
+ setValue(QLatin1String("backupOfExistingDestination"), backupFileName(dest));
+
+ // race condition: The backup file could get created
+ // by another process right now. But this is the same
+ // in QFile::copy...
+ const bool success = QFile::rename(dest, value(QLatin1String("backupOfExistingDestination")).toString());
+ if (!success)
+ setError(UserDefinedError, tr("Could not backup file %1.").arg(dest));
+}
+
+bool MoveOperation::performOperation()
+{
+ // We need two args to complete the copy operation.
+ // First arg provides the complete file name of source
+ // Second arg provides the complete file name of dest
+ QStringList args = this->arguments();
+ if (args.count() != 2) {
+ setError(InvalidArguments);
+ setErrorString(tr("Invalid arguments: %1 arguments given, 2 expected.").arg(args.count()));
+ return false;
+ }
+
+ QString source = args.first();
+ QString dest = args.last();
+
+ // If destination file exists, then we cannot use QFile::copy()
+ // because it does not overwrite an existing file. So we remove
+ // the destination file.
+ if (QFile::exists(dest)) {
+ QFile file(dest);
+ if (!file.remove(dest)) {
+ setError(UserDefinedError);
+ setErrorString(tr("Could not remove destination file %1: %2").arg(dest, file.errorString()));
+ return false;
+ }
+ }
+
+ // Copy source to destination.
+ QFile file(source);
+ const bool copied = file.copy(source, dest);
+ if (!copied) {
+ setError(UserDefinedError);
+ setErrorString(tr("Could not copy %1 to %2: %3").arg(source, dest, file.errorString()));
+ return false;
+ }
+
+ return deleteFileNowOrLater(source);
+}
+
+bool MoveOperation::undoOperation()
+{
+ const QStringList args = arguments();
+ const QString& source = args.first();
+ const QString& dest = args.last();
+
+ // first: copy back the destination to source
+ QFile destF(dest);
+ if (!destF.copy(source)) {
+ setError(UserDefinedError, tr("Cannot copy %1 to %2: %3").arg(dest, source, destF.errorString()));
+ return false;
+ }
+
+ // second: delete the move destination
+ if (!deleteFileNowOrLater(dest)) {
+ setError(UserDefinedError, tr("Cannot remove file %1."));
+ return false;
+ }
+
+ // no backup was done:
+ // the move destination file wasn't existing yet - that's no error
+ if (!hasValue(QLatin1String("backupOfExistingDestination")))
+ return true;
+
+ // otherwise we have to copy the backup back:
+ QFile backupF(value(QLatin1String("backupOfExistingDestination")).toString());
+ const bool success = backupF.rename(dest);
+ if (!success)
+ setError(UserDefinedError, tr("Cannot restore backup file for %1: %2").arg(dest, backupF.errorString()));
+
+ return success;
+}
+
+bool MoveOperation::testOperation()
+{
+ // TODO
+ return true;
+}
+
+MoveOperation *MoveOperation::clone() const
+{
+ return new MoveOperation;
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+// KDUpdater::DeleteOperation
+////////////////////////////////////////////////////////////////////////////
+
+DeleteOperation::DeleteOperation()
+{
+ setName(QLatin1String("Delete"));
+}
+
+DeleteOperation::~DeleteOperation()
+{
+ deleteFileNowOrLater(value(QLatin1String("backupOfExistingFile")).toString());
+}
+
+void DeleteOperation::backup()
+{
+ const QString fileName = arguments().first();
+ setValue(QLatin1String("backupOfExistingFile"), backupFileName(fileName));
+ QFile file(fileName);
+ const bool success = file.copy(value(QLatin1String("backupOfExistingFile")).toString());
+ if (!success)
+ setError(UserDefinedError, tr("Cannot create backup of %1: %2").arg(fileName, file.errorString()));
+}
+
+bool DeleteOperation::performOperation()
+{
+ // Requires only one parameter. That is the name of
+ // the file to remove.
+ QStringList args = this->arguments();
+ if (args.count() != 1) {
+ setError(InvalidArguments);
+ setErrorString(tr("Invalid arguments: %1 arguments given, 1 expected.").arg(args.count()));
+ return false;
+ }
+
+ const QString fName = args.first();
+ return deleteFileNowOrLater(fName);
+}
+
+bool DeleteOperation::undoOperation()
+{
+ if (!hasValue(QLatin1String("backupOfExistingFile")))
+ return true;
+
+ const QString fileName = arguments().first();
+ QFile backupF(value(QLatin1String("backupOfExistingFile")).toString());
+ const bool success = backupF.copy(fileName) && deleteFileNowOrLater(backupF.fileName());
+ if (!success)
+ setError(UserDefinedError, tr("Cannot restore backup file for %1: %2").arg(fileName, backupF.errorString()));
+
+ return success;
+}
+
+bool DeleteOperation::testOperation()
+{
+ // TODO
+ return true;
+}
+
+DeleteOperation *DeleteOperation::clone() const
+{
+ return new DeleteOperation;
+}
+
+/*!
+ \reimp
+ */
+QDomDocument DeleteOperation::toXml() const
+{
+ // we don't want to save the backupOfExistingFile
+ if (!hasValue(QLatin1String("backupOfExistingFile")))
+ return UpdateOperation::toXml();
+
+ DeleteOperation *const me = const_cast<DeleteOperation *>(this);
+
+ const QVariant v = value(QLatin1String("backupOfExistingFile"));
+ me->clearValue(QLatin1String("backupOfExistingFile"));
+ const QDomDocument xml = UpdateOperation::toXml();
+ me->setValue(QLatin1String("backupOfExistingFile"), v);
+ return xml;
+}
+
+////////////////////////////////////////////////////////////////////////////
+// KDUpdater::MkdirOperation
+////////////////////////////////////////////////////////////////////////////
+
+MkdirOperation::MkdirOperation()
+{
+ setName(QLatin1String("Mkdir"));
+}
+
+void MkdirOperation::backup()
+{
+ static const QRegExp re(QLatin1String("\\\\|/"));
+ static const QLatin1String sep("/");
+
+ QString path = arguments().first();
+ path.replace(re, sep);
+
+ QDir createdDir = QDir::root();
+
+ // find out, which part of the path is the first one we actually need to create
+ int end = 0;
+ while (true) {
+ QString p = path.section(sep, 0, ++end);
+ createdDir = QDir(p);
+ if (!createdDir.exists())
+ break;
+ if (p == path) {
+ // everything did already exist -> nothing to do for us (nothing to revert then, either)
+ createdDir = QDir::root();
+ break;
+ }
+ }
+
+ setValue(QLatin1String("createddir"), createdDir.absolutePath());
+}
+
+bool MkdirOperation::performOperation()
+{
+ // Requires only one parameter. That is the name of
+ // the file to remove.
+ QStringList args = this->arguments();
+ if (args.count() != 1) {
+ setError(InvalidArguments);
+ setErrorString(tr("Invalid arguments: %1 arguments given, 1 expected.").arg(args.count()));
+ return false;
+ }
+ QString dirName = args.first();
+ const bool created = QDir::root().mkpath(dirName);
+ if (!created) {
+ setError(UserDefinedError);
+ setErrorString(tr("Could not create folder %1: Unknown error.").arg(dirName));
+ }
+ return created;
+}
+
+bool MkdirOperation::undoOperation()
+{
+ Q_ASSERT(arguments().count() == 1);
+
+ QDir createdDir = QDir(value(QLatin1String("createddir")).toString());
+ const bool forceremoval = QVariant(value(QLatin1String("forceremoval"))).toBool();
+
+ // Since refactoring we know the mkdir operation which is creating the target path. If we do a full
+ // uninstall prevent removing the full path including target, instead remove the target only. (QTIFW-46)
+ if (hasValue(QLatin1String("uninstall-only")) && value(QLatin1String("uninstall-only")).toBool())
+ createdDir = QDir(arguments().first());
+
+ if (createdDir == QDir::root())
+ return true;
+
+ if (!createdDir.exists())
+ return true;
+
+ QString errorString;
+ if (forceremoval)
+ return removeDirectory(createdDir.path(), &errorString);
+
+ // even remove some hidden, OS-created files in there
+#if defined Q_WS_MAC
+ QFile::remove(createdDir.path() + QLatin1String("/.DS_Store"));
+#elif defined Q_WS_WIN
+ QFile::remove(createdDir.path() + QLatin1String("/Thumbs.db"));
+#endif
+
+ errno = 0;
+ const bool result = QDir::root().rmdir(createdDir.path());
+ if (!result) {
+ if (errorString.isEmpty())
+ setError(UserDefinedError, tr("Cannot remove directory %1: %2").arg(createdDir.path(), errorString));
+ else
+ setError(UserDefinedError, tr("Cannot remove directory %1: %2").arg(createdDir.path(), QLatin1String(strerror(errno))));
+ }
+ return result;
+}
+
+bool KDUpdater::MkdirOperation::testOperation()
+{
+ // TODO
+ return true;
+}
+
+MkdirOperation *MkdirOperation::clone() const
+{
+ return new MkdirOperation;
+}
+
+////////////////////////////////////////////////////////////////////////////
+// KDUpdater::RmdirOperation
+////////////////////////////////////////////////////////////////////////////
+
+RmdirOperation::RmdirOperation()
+{
+ setValue(QLatin1String("removed"), false);
+ setName(QLatin1String("Rmdir"));
+}
+
+void RmdirOperation::backup()
+{
+ // nothing to backup - rollback will just create the directory
+}
+
+bool RmdirOperation::performOperation()
+{
+ // Requires only one parameter. That is the name of
+ // the file to remove.
+ QStringList args = this->arguments();
+ if (args.count() != 1) {
+ setError(InvalidArguments);
+ setErrorString(tr("Invalid arguments: %1 arguments given, 1 expected.").arg(args.count()));
+ return false;
+ }
+
+ QString dirName = args.first();
+ QDir dir(dirName);
+ if (!dir.exists()) {
+ setError(UserDefinedError);
+ setErrorString(tr("Could not remove folder %1: The folder does not exist.").arg(dirName));
+ return false;
+ }
+
+ errno = 0;
+ const bool removed = dir.rmdir(dirName);
+ setValue(QLatin1String("removed"), removed);
+ if (!removed) {
+ setError(UserDefinedError);
+ setErrorString(tr("Could not remove folder %1: %2").arg(dirName, QLatin1String(strerror(errno))));
+ }
+ return removed;
+}
+
+bool RmdirOperation::undoOperation()
+{
+ if (!value(QLatin1String("removed")).toBool())
+ return true;
+
+ const QFileInfo fi(arguments().first());
+ errno = 0;
+ const bool success = fi.dir().mkdir(fi.fileName());
+ if( !success)
+ setError(UserDefinedError, tr("Cannot recreate directory %1: %2").arg(fi.fileName(), QLatin1String(strerror(errno))));
+
+ return success;
+}
+
+bool RmdirOperation::testOperation()
+{
+ // TODO
+ return true;
+}
+
+RmdirOperation *RmdirOperation::clone() const
+{
+ return new RmdirOperation;
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+// KDUpdater::AppendFileOperation
+////////////////////////////////////////////////////////////////////////////
+
+AppendFileOperation::AppendFileOperation()
+{
+ setName(QLatin1String("AppendFile"));
+}
+
+void AppendFileOperation::backup()
+{
+ const QString filename = arguments().first();
+
+ QFile file(filename);
+ if (!file.exists())
+ return; // nothing to backup
+
+ setValue(QLatin1String("backupOfFile"), backupFileName(filename));
+ if (!file.copy(value(QLatin1String("backupOfFile")).toString())) {
+ setError(UserDefinedError, tr("Cannot backup file %1: %2").arg(filename, file.errorString()));
+ clearValue(QLatin1String("backupOfFile"));
+ }
+}
+
+bool AppendFileOperation::performOperation()
+{
+ // This operation takes two arguments. First argument is the name
+ // of the file into which a text has to be appended. Second argument
+ // is the text to append.
+ QStringList args = this->arguments();
+ if (args.count() != 2) {
+ setError(InvalidArguments);
+ setErrorString(tr("Invalid arguments: %1 arguments given, 2 expected.").arg(args.count()));
+ return false;
+ }
+
+ QString fName = args.first();
+ QString text = args.last();
+
+ QFile file(fName);
+ if (!file.open(QFile::Append)) {
+ // first we rename the file, then we copy it to the real target and open the copy - the renamed original is then marked for deletion
+ const QString newName = backupFileName(fName);
+ if (!QFile::rename(fName, newName) && QFile::copy(newName, fName) && file.open(QFile::Append)) {
+ QFile::rename(newName, fName);
+ setError(UserDefinedError);
+ setErrorString(tr("Could not open file %1 for writing: %2").arg(file.fileName(), file.errorString()));
+ return false;
+ }
+ deleteFileNowOrLater(newName);
+ }
+
+ QTextStream ts(&file);
+ ts << text;
+ file.close();
+
+ return true;
+}
+
+bool AppendFileOperation::undoOperation()
+{
+ // backupOfFile being empty -> file didn't exist before -> no error
+ const QString filename = arguments().first();
+ const QString backupOfFile = value(QLatin1String("backupOfFile")).toString();
+ if (!backupOfFile.isEmpty() && !QFile::exists(backupOfFile)) {
+ setError(UserDefinedError, tr("Cannot find backup file for %1.").arg(filename));
+ return false;
+ }
+
+ const bool removed = deleteFileNowOrLater(filename);
+ if (!removed) {
+ setError(UserDefinedError, tr("Could not restore backup file for %1.").arg(filename));
+ return false;
+ }
+
+ // got deleted? We might be done, if it didn't exist before
+ if (backupOfFile.isEmpty())
+ return true;
+
+ QFile backupFile(backupOfFile);
+ const bool success = backupFile.rename(filename);
+ if (!success)
+ setError(UserDefinedError, tr("Could not restore backup file for %1: %2").arg(filename, backupFile.errorString()));
+ return success;
+}
+
+bool AppendFileOperation::testOperation()
+{
+ // TODO
+ return true;
+}
+
+AppendFileOperation *AppendFileOperation::clone() const
+{
+ return new AppendFileOperation;
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+// KDUpdater::PrependFileOperation
+////////////////////////////////////////////////////////////////////////////
+
+PrependFileOperation::PrependFileOperation()
+{
+ setName(QLatin1String("PrependFile"));
+}
+
+void PrependFileOperation::backup()
+{
+ const QString filename = arguments().first();
+
+ QFile file(filename);
+ if (!file.exists())
+ return; // nothing to backup
+
+ setValue(QLatin1String("backupOfFile"), backupFileName(filename));
+ if (!file.copy(value(QLatin1String("backupOfFile")).toString())) {
+ setError(UserDefinedError, tr("Cannot backup file %1: %2").arg(filename, file.errorString()));
+ clearValue(QLatin1String("backupOfFile"));
+ }
+}
+
+bool PrependFileOperation::performOperation()
+{
+ // This operation takes two arguments. First argument is the name
+ // of the file into which a text has to be appended. Second argument
+ // is the text to append.
+ QStringList args = this->arguments();
+ if (args.count() != 2) {
+ setError(InvalidArguments);
+ setErrorString(tr("Invalid arguments: %1 arguments given, 2 expected.").arg(args.count()));
+ return false;
+ }
+
+ QString fName = args.first();
+ QString text = args.last();
+
+ // Load the file first.
+ QFile file(fName);
+ if (!file.open(QFile::ReadOnly)) {
+ setError(UserDefinedError);
+ setErrorString(tr("Could not open file %1 for reading: %2").arg(file.fileName(), file.errorString()));
+ return false;
+ }
+ QString fContents(QLatin1String(file.readAll()));
+ file.close();
+
+ // Prepend text to the file text
+ fContents = text + fContents;
+
+ // Now re-open the file in write only mode.
+ if (!file.open(QFile::WriteOnly)) {
+ // first we rename the file, then we copy it to the real target and open the copy - the renamed original is then marked for deletion
+ const QString newName = backupFileName(fName);
+ if (!QFile::rename(fName, newName) && QFile::copy(newName, fName) && file.open(QFile::WriteOnly)) {
+ QFile::rename(newName, fName);
+ setError(UserDefinedError);
+ setErrorString(tr("Could not open file %1 for writing: %2").arg(file.fileName(), file.errorString()));
+ return false;
+ }
+ deleteFileNowOrLater(newName);
+ }
+ QTextStream ts(&file);
+ ts << fContents;
+ file.close();
+
+ return true;
+}
+
+bool PrependFileOperation::undoOperation()
+{
+ // bockupOfFile being empty -> file didn't exist before -> no error
+ const QString filename = arguments().first();
+ const QString backupOfFile = value(QLatin1String("backupOfFile")).toString();
+ if (!backupOfFile.isEmpty() && !QFile::exists(backupOfFile)) {
+ setError(UserDefinedError, tr("Cannot find backup file for %1.").arg(filename));
+ return false;
+ }
+
+ if (!deleteFileNowOrLater(filename)) {
+ setError(UserDefinedError, tr("Cannot restore backup file for %1.").arg(filename));
+ return false;
+ }
+
+ // got deleted? We might be done, if it didn't exist before
+ if (backupOfFile.isEmpty())
+ return true;
+
+ QFile backupF(backupOfFile);
+ const bool success = backupF.rename(filename);
+ if (!success)
+ setError(UserDefinedError, tr("Cannot restore backup file for %1: %2").arg(filename, backupF.errorString()));
+
+ return success;
+}
+
+bool PrependFileOperation::testOperation()
+{
+ // TODO
+ return true;
+}
+
+PrependFileOperation *PrependFileOperation::clone() const
+{
+ return new PrependFileOperation;
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+// KDUpdater::ExecuteOperation
+////////////////////////////////////////////////////////////////////////////
+
+ExecuteOperation::ExecuteOperation()
+ : QObject()
+{
+ setName(QLatin1String("Execute"));
+}
+
+void ExecuteOperation::backup()
+{
+ // this is not possible, since the process can do whatever...
+}
+
+#if defined(SUPPORT_DETACHED_PROCESS_EXECUTION) && defined(Q_WS_WIN)
+// stolen from qprocess_win.cpp
+static QString qt_create_commandline(const QString &program, const QStringList &arguments)
+{
+ QString args;
+ if (!program.isEmpty()) {
+ QString programName = program;
+ if (!programName.startsWith(QLatin1Char('\"')) && !programName.endsWith(QLatin1Char('\"')) && programName.contains(QLatin1Char(' ')))
+ programName = QLatin1Char('\"') + programName + QLatin1Char('\"');
+ programName.replace(QLatin1Char('/'), QLatin1Char('\\'));
+
+ // add the prgram as the first arg ... it works better
+ args = programName + QLatin1Char(' ');
+ }
+
+ for (int i = 0; i < arguments.size(); ++i) {
+ QString tmp = arguments.at(i);
+ // in the case of \" already being in the string the \ must also be escaped
+ tmp.replace(QLatin1String("\\\""), QLatin1String("\\\\\""));
+ // escape a single " because the arguments will be parsed
+ tmp.replace(QLatin1Char('\"'), QLatin1String("\\\""));
+ if (tmp.isEmpty() || tmp.contains(QLatin1Char(' ')) || tmp.contains(QLatin1Char('\t'))) {
+ // The argument must not end with a \ since this would be interpreted
+ // as escaping the quote -- rather put the \ behind the quote: e.g.
+ // rather use "foo"\ than "foo\"
+ QString endQuote(QLatin1Char('\"'));
+ int i = tmp.length();
+ while (i > 0 && tmp.at(i - 1) == QLatin1Char('\\')) {
+ --i;
+ endQuote += QLatin1Char('\\');
+ }
+ args += QLatin1String(" \"") + tmp.left(i) + endQuote;
+ } else {
+ args += QLatin1Char(' ') + tmp;
+ }
+ }
+ return args;
+}
+#endif
+
+bool ExecuteOperation::performOperation()
+{
+ // This operation receives only one argument. It is the complete
+ // command line of the external program to execute.
+ QStringList args = this->arguments();
+ if (args.isEmpty()) {
+ setError(InvalidArguments);
+ setErrorString(tr("Invalid arguments: %1 arguments given, 2 expected.").arg(args.count()));
+ return false;
+ }
+
+ QList<int> allowedExitCodes;
+
+ QRegExp re(QLatin1String("^\\{((-?\\d+,)*-?\\d+)\\}$"));
+ if (re.exactMatch(args.first())) {
+ const QStringList numbers = re.cap(1).split(QLatin1Char(','));
+ for (QStringList::const_iterator it = numbers.begin(); it != numbers.end(); ++it)
+ allowedExitCodes.push_back(it->toInt());
+ args.pop_front();
+ } else {
+ allowedExitCodes.push_back(0);
+ }
+
+ bool success = false;
+#ifdef SUPPORT_DETACHED_PROCESS_EXECUTION
+ // unix style: when there's an ampersand after the command, it's started detached
+ if (args.count() >= 2 && args.last() == QLatin1String("&")) {
+ args.pop_back();
+#ifdef Q_WS_WIN
+ QString arguments = qt_create_commandline(args.front(), args.mid(1));
+
+ PROCESS_INFORMATION pinfo;
+
+ STARTUPINFOW startupInfo = { sizeof(STARTUPINFO), 0, 0, 0,
+ static_cast< ulong >(CW_USEDEFAULT), static_cast< ulong >(CW_USEDEFAULT),
+ static_cast< ulong >(CW_USEDEFAULT), static_cast< ulong >(CW_USEDEFAULT),
+ 0, 0, 0, STARTF_USESHOWWINDOW, SW_HIDE, 0, 0, 0, 0, 0
+ };
+ success = CreateProcess(0, const_cast< wchar_t* >(static_cast< const wchar_t* >(arguments.utf16())),
+ 0, 0, FALSE, CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE, 0,
+ 0,
+ &startupInfo, &pinfo);
+
+#else
+ success = QProcess::startDetached(args.front(), args.mid(1));
+#endif
+ }
+ else
+#endif
+ {
+ Environment::instance().applyTo(&process); //apply non-persistent variables
+ process.start(args.front(), args.mid(1));
+
+ QEventLoop loop;
+ QObject::connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)), &loop, SLOT(quit()));
+ QObject::connect(&process, SIGNAL(readyRead()), this, SLOT(readProcessOutput()));
+ success = process.waitForStarted(-1);
+ if (success) {
+ loop.exec();
+ setValue(QLatin1String("ExitCode"), process.exitCode());
+ success = allowedExitCodes.contains(process.exitCode());
+ }
+ }
+ if (!success) {
+ setError(UserDefinedError);
+ setErrorString(tr("Execution failed: %1").arg(args.join(QLatin1String(" "))));
+ }
+
+ return success;
+}
+
+/*!
+ Cancels the ExecuteOperation. This methods tries to terminate the process
+ gracefully by calling QProcess::terminate. After 10 seconds, the process gets killed.
+ */
+void ExecuteOperation::cancelOperation()
+{
+ if (process.state() == QProcess::Running)
+ process.terminate();
+ if (!process.waitForFinished(10000))
+ process.kill();
+}
+
+void ExecuteOperation::readProcessOutput()
+{
+ QByteArray output = process.readAll();
+ if (!output.isEmpty())
+ emit outputTextChanged(QString::fromLocal8Bit(output));
+}
+
+bool ExecuteOperation::undoOperation()
+{
+ // this is not possible, since the process can do whatever...
+ return false;
+}
+
+bool ExecuteOperation::testOperation()
+{
+ // TODO
+ return true;
+}
+
+ExecuteOperation *ExecuteOperation::clone() const
+{
+ return new ExecuteOperation;
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+// KDUpdater::UpdatePackageOperation
+////////////////////////////////////////////////////////////////////////////
+
+UpdatePackageOperation::UpdatePackageOperation()
+{
+ setName(QLatin1String("UpdatePackage"));
+}
+
+void UpdatePackageOperation::backup()
+{
+ const PackageInfo info = application()->packagesInfo()->packageInfo(application()->packagesInfo()->findPackageInfo(arguments().first()));
+ setValue(QLatin1String("oldVersion"), info.version);
+ setValue(QLatin1String("oldDate"), info.lastUpdateDate);
+}
+
+bool UpdatePackageOperation::performOperation()
+{
+ // This operation receives three arguments : the name of the package
+ // the new version and the release date
+ const QStringList args = this->arguments();
+ if (args.count() != 3) {
+ setError(InvalidArguments, tr("Invalid arguments: %1 arguments given, 3 expected.").arg(args.count()));
+ return false;
+ }
+
+ const QString &packageName = args.at(0);
+ const QString &version = args.at(1);
+ const QDate date = QDate::fromString(args.at(2));
+ const bool success = application()->packagesInfo()->updatePackage(packageName, version, date);
+ if (!success)
+ setError(UserDefinedError, tr("Cannot update %1-%2").arg(packageName, version));
+
+ return success;
+}
+
+bool UpdatePackageOperation::undoOperation()
+{
+ const QString packageName = arguments().first();
+ const QString version = arguments().at(1);
+ const QString oldVersion = value(QLatin1String("oldVersion")).toString();
+ const QDate oldDate = value(QLatin1String("oldDate")).toDate();
+ const bool success = application()->packagesInfo()->updatePackage(packageName, oldVersion, oldDate);
+ if (!success)
+ setError(UserDefinedError, tr("Cannot restore %1-%2").arg(packageName, version));
+
+ return success;
+}
+
+bool UpdatePackageOperation::testOperation()
+{
+ // TODO
+ return true;
+}
+
+UpdatePackageOperation *UpdatePackageOperation::clone() const
+{
+ return new UpdatePackageOperation;
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+// KDUpdater::UpdateCompatOperation
+////////////////////////////////////////////////////////////////////////////
+
+UpdateCompatOperation::UpdateCompatOperation()
+{
+ setName(QLatin1String("UpdateCompatLevel"));
+}
+
+void UpdateCompatOperation::backup()
+{
+ setValue(QLatin1String("oldCompatLevel"), application()->packagesInfo()->compatLevel());
+}
+
+bool UpdateCompatOperation::performOperation()
+{
+ // This operation receives one argument : the new compat level
+ const QStringList args = this->arguments();
+ if (args.count() != 1) {
+ setError(InvalidArguments, tr("Invalid arguments: %1 arguments given, 1 expected.").arg(args.count()));
+ return false;
+ }
+
+ const int level = args.first().toInt();
+ application()->packagesInfo()->setCompatLevel(level);
+ return true;
+}
+
+bool UpdateCompatOperation::undoOperation()
+{
+ if (!hasValue(QLatin1String("oldCompatLevel"))) {
+ setError(UserDefinedError, tr("Cannot restore previous compat-level"));
+ return false;
+ }
+
+ application()->packagesInfo()->setCompatLevel(value(QLatin1String("oldCompatLevel")).toInt());
+ return true;
+}
+
+bool UpdateCompatOperation::testOperation()
+{
+ // TODO
+ return true;
+}
+
+UpdateCompatOperation *UpdateCompatOperation::clone() const
+{
+ return new UpdateCompatOperation;
+}