diff options
Diffstat (limited to 'src/libs/kdtools/updateoperation.cpp')
-rw-r--r-- | src/libs/kdtools/updateoperation.cpp | 527 |
1 files changed, 527 insertions, 0 deletions
diff --git a/src/libs/kdtools/updateoperation.cpp b/src/libs/kdtools/updateoperation.cpp new file mode 100644 index 000000000..965871fda --- /dev/null +++ b/src/libs/kdtools/updateoperation.cpp @@ -0,0 +1,527 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Klaralvdalens Datakonsult AB (KDAB) +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Installer Framework. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "updateoperation.h" + +#include "constants.h" +#include "fileutils.h" +#include "packagemanagercore.h" + +#include <QDataStream> +#include <QDebug> +#include <QDir> +#include <QFileInfo> +#include <QTemporaryFile> + +using namespace KDUpdater; + +/*! + \inmodule kdupdater + \class KDUpdater::UpdateOperation + \brief The UpdateOperation class is an abstract base class for update operations. + + The KDUpdater::UpdateOperation is an abstract class that specifies an interface for + update operations. Concrete implementations of this class must perform a single update + operation, such as copy, move, or delete. + + \note Two separate threads cannot be using a single instance of KDUpdater::UpdateOperation + at the same time. +*/ + +/*! + \enum UpdateOperation::Error + This enum code specifies error codes related to operation arguments and + operation runtime failures. + + \value NoError + No error occurred. + \value InvalidArguments + Number of arguments does not match or an invalid argument was set. + \value UserDefinedError + An error occurred during operation run. Use UpdateOperation::errorString() + to get the human-readable description of the error that occurred. +*/ + +/* + \internal + Returns a filename for a temporary file based on \a templateName. +*/ +static QString backupFileName(const QString &templateName) +{ + const QFileInfo templ(templateName); + QTemporaryFile file( QDir::temp().absoluteFilePath(templ.fileName())); + file.open(); + const QString name = file.fileName(); + file.close(); + file.remove(); + return name; +} + +/*! + \internal +*/ +UpdateOperation::UpdateOperation(QInstaller::PackageManagerCore *core) + : m_error(0) + , m_core(core) +{ + // Store the value for compatibility reasons. + m_values[QLatin1String("installer")] = QVariant::fromValue(core); +} + +/*! + \internal +*/ +UpdateOperation::~UpdateOperation() +{ + if (auto *core = packageManager()) + core->addFilesForDelayedDeletion(filesForDelayedDeletion()); +} + +/*! + Returns the update operation name. + + \sa setName() +*/ +QString UpdateOperation::name() const +{ + return m_name; +} + +/*! + Returns a command line string that describes the update operation. The returned string will be + of the form: + + \c{<name> <arg1> <arg2> <arg3> ....} +*/ +QString UpdateOperation::operationCommand() const +{ + QString argsStr = m_arguments.join(QLatin1String( " " )); + return QString::fromLatin1( "%1 %2" ).arg(m_name, argsStr); +} + +/*! + Returns \c true if a value called \a name exists, otherwise returns \c false. +*/ +bool UpdateOperation::hasValue(const QString &name) const +{ + return m_values.contains(name); +} + +/*! + Clears the value of \a name and removes it. +*/ +void UpdateOperation::clearValue(const QString &name) +{ + m_values.remove(name); +} + +/*! + Returns the value of \a name. If the value does not exist, returns an empty QVariant. +*/ +QVariant UpdateOperation::value(const QString &name) const +{ + return m_values.value(name); +} + +/*! + Sets the value of \a name to \a value. +*/ +void UpdateOperation::setValue(const QString &name, const QVariant &value) +{ + m_values[name] = value; +} + +/*! + Sets the name of the operation to \a name. Subclasses will have to provide a unique name to + describe the operation. +*/ +void UpdateOperation::setName(const QString &name) +{ + m_name = name; +} + +/*! + Sets the arguments for the update operation to \a args. +*/ +void UpdateOperation::setArguments(const QStringList &args) +{ + m_arguments = args; +} + +/*! + Returns the arguments of the update operation. +*/ +QStringList UpdateOperation::arguments() const +{ + return m_arguments; +} + +bool UpdateOperation::checkArgumentCount(int minArgCount, int maxArgCount, + const QString &argDescription) +{ + const int argCount = arguments().count(); + if (argCount < minArgCount || argCount > maxArgCount) { + setError(InvalidArguments); + QString countRange; + if (minArgCount == maxArgCount) + countRange = tr("exactly %1").arg(minArgCount); + else if (maxArgCount == INT_MAX) + countRange = tr("at least %1").arg(minArgCount); + else if (minArgCount == 0) + countRange = tr("not more than %1").arg(maxArgCount); + else if (minArgCount == maxArgCount - 1) + countRange = tr("%1 or %2").arg(minArgCount).arg(maxArgCount); + else + countRange = tr("%1 to %2").arg(minArgCount).arg(maxArgCount); + + if (argDescription.isEmpty()) + setErrorString(tr("Invalid arguments in %1: %n arguments given, " + "%2 arguments expected.", 0, argCount) + .arg(name(), countRange)); + else + setErrorString(tr("Invalid arguments in %1: %n arguments given, " + "%2 arguments expected in the form: %3.", 0, argCount) + .arg(name(), countRange, argDescription)); + return false; + } + return true; +} + +bool UpdateOperation::checkArgumentCount(int argCount) +{ + return checkArgumentCount(argCount, argCount); +} + +struct StartsWith +{ + StartsWith(const QString &searchTerm) + : m_searchTerm(searchTerm) {} + + bool operator()(const QString &searchString) + { + return searchString.startsWith(m_searchTerm); + } + + QString m_searchTerm; +}; + +/*! + Searches the arguments for the key specified by \a key. If it can find the + key, it returns the value set for it. Otherwise, it returns \a defaultValue. + Arguments are specified in the following form: \c{key=value}. +*/ +QString UpdateOperation::argumentKeyValue(const QString &key, const QString &defaultValue) const +{ + const QString keySeparater(key + QLatin1String("=")); + const QStringList tArguments(arguments()); + QStringList::const_iterator it = std::find_if(tArguments.begin(), tArguments.end(), + StartsWith(keySeparater)); + if (it == tArguments.end()) + return defaultValue; + + const QString value = it->mid(keySeparater.size()); + + it = std::find_if(++it, tArguments.end(), StartsWith(keySeparater)); + if (it != tArguments.end()) { + qWarning().nospace() << "There are multiple keys in the arguments calling " << name() << ". " + << "Only the first found " << key << " is used: " + << arguments().join(QLatin1String("; ")); + } + return value; +} + +/*! + Returns a human-readable description of the last error that occurred. +*/ +QString UpdateOperation::errorString() const +{ + return m_errorString; +} + +/*! + Returns the error that was found during the processing of the operation. If no + error was found, returns NoError. Subclasses can set more detailed error codes (optional). + + \note To check if an operation was successful, use the return value of performOperation(), + undoOperation(), or testOperation(). +*/ +int UpdateOperation::error() const +{ + return m_error; +} + +/*! + Sets the human-readable description of the last error that occurred to \a str. +*/ +void UpdateOperation::setErrorString(const QString &str) +{ + m_errorString = str; +} + +/*! + Sets the error condition to be \a error. The human-readable message is set to \a errorString. + + \sa UpdateOperation::error() + \sa UpdateOperation::errorString() +*/ +void UpdateOperation::setError(int error, const QString &errorString) +{ + m_error = error; + if (!errorString.isNull()) + m_errorString = errorString; +} + +/*! + Clears the previously set arguments. +*/ +void UpdateOperation::clear() +{ + m_arguments.clear(); +} + +/*! + Returns the list of files that are scheduled for later deletion. +*/ +QStringList UpdateOperation::filesForDelayedDeletion() const +{ + return m_delayedDeletionFiles; +} + +/*! + Returns the package manager core this operation belongs to. +*/ +QInstaller::PackageManagerCore *UpdateOperation::packageManager() const +{ + return m_core; +} + +/*! + Registers a list of \a files to be deleted later once the application was restarted and the + file or files are not used anymore. +*/ +void UpdateOperation::registerForDelayedDeletion(const QStringList &files) +{ + m_delayedDeletionFiles << files; +} + +/*! + Tries to delete \a file. If \a file cannot be deleted, it is registered for delayed deletion. + + If a backup copy of the file cannot be created, returns \c false and displays the error + message specified by \a errorString. +*/ +bool UpdateOperation::deleteFileNowOrLater(const QString &file, QString *errorString) +{ + if (file.isEmpty() || QFile::remove(file)) + return true; + + if (!QFile::exists(file)) + return true; + + const QString backup = backupFileName(file); + QFile f(file); + if (!f.rename(backup)) { + if (errorString) + *errorString = tr("Renaming file \"%1\" to \"%2\" failed: %3").arg( + QDir::toNativeSeparators(file), QDir::toNativeSeparators(backup), f.errorString()); + return false; + } + registerForDelayedDeletion(QStringList(backup)); + return true; +} + +/*! + \fn virtual void KDUpdater::UpdateOperation::backup() = 0; + + Subclasses must implement this function to back up any data before performing the action. +*/ + +/*! + \fn virtual bool KDUpdater::UpdateOperation::performOperation() = 0; + + Subclasses must implement this function to perform the update operation. + + Returns \c true if the operation is successful. +*/ + +/*! + \fn virtual bool KDUpdater::UpdateOperation::undoOperation() = 0; + + Subclasses must implement this function to perform the undo of the update operation. + + Returns \c true if the operation is successful. +*/ + +/*! + \fn virtual bool KDUpdater::UpdateOperation::testOperation() = 0; + + Subclasses must implement this function to perform the test operation. + + Returns \c true if the operation is successful. +*/ + +/*! + Saves operation arguments and values as an XML document and returns the + document. You can override this method to store your + own extra-data. Extra-data can be any data that you need to store to perform or undo the + operation. The default implementation is taking care of arguments and values set via + UpdateOperation::setValue(). +*/ +QDomDocument UpdateOperation::toXml() const +{ + QDomDocument doc; + QDomElement root = doc.createElement(QLatin1String("operation")); + doc.appendChild(root); + + QDomElement args = doc.createElement(QLatin1String("arguments")); + const QString target = m_core ? m_core->value(QInstaller::scTargetDir) : QString(); + Q_FOREACH (const QString &s, arguments()) { + QDomElement arg = doc.createElement(QLatin1String("argument")); + arg.appendChild(doc.createTextNode(QInstaller::replacePath(s, target, + QLatin1String(QInstaller::scRelocatable)))); + args.appendChild(arg); + } + root.appendChild(args); + if (m_values.isEmpty()) + return doc; + + // append all values set with setValue + QDomElement values = doc.createElement(QLatin1String("values")); + for (QVariantMap::const_iterator it = m_values.constBegin(); it != m_values.constEnd(); ++it) { + // the installer can't be put into XML, ignore + if (it.key() == QLatin1String("installer")) + continue; + + QDomElement value = doc.createElement(QLatin1String("value")); + QVariant variant = it.value(); + value.setAttribute(QLatin1String("name"), it.key()); + value.setAttribute(QLatin1String("type"), QLatin1String(variant.typeName())); + + if (variant.type() != QVariant::List && variant.type() != QVariant::StringList + && variant.canConvert(QVariant::String)) { + // it can convert to string? great! + value.appendChild(doc.createTextNode(QInstaller::replacePath(variant.toString(), + target, QLatin1String(QInstaller::scRelocatable)))); + } else { + // no? then we have to go the hard way... + if (variant.type() == QVariant::StringList) { + QStringList list = variant.toStringList(); + for (int i = 0; i < list.count(); ++i) { + list[i] = QInstaller::replacePath(list.at(i), target, + QLatin1String(QInstaller::scRelocatable)); + } + variant = QVariant::fromValue(list); + } + QByteArray data; + QDataStream stream(&data, QIODevice::WriteOnly); + stream << variant; + value.appendChild(doc.createTextNode(QLatin1String( data.toBase64().data()))); + } + values.appendChild(value); + } + root.appendChild(values); + return doc; +} + +/*! + Restores operation arguments and values from the XML document \a doc. Returns \c true on + success, otherwise \c false. \note: Clears all previously set values and arguments. +*/ +bool UpdateOperation::fromXml(const QDomDocument &doc) +{ + QString target = QCoreApplication::applicationDirPath(); + QInstaller::isInBundle(target, &target); // Does not change target on non OSX platforms. + + QStringList args; + const QDomElement root = doc.documentElement(); + const QDomElement argsElem = root.firstChildElement(QLatin1String("arguments")); + Q_ASSERT(! argsElem.isNull()); + for (QDomNode n = argsElem.firstChild(); ! n.isNull(); n = n.nextSibling()) { + const QDomElement e = n.toElement(); + if (!e.isNull() && e.tagName() == QLatin1String("argument")) { + args << QInstaller::replacePath(e.text(), QLatin1String(QInstaller::scRelocatable), + target); + } + } + setArguments(args); + + m_values.clear(); + const QDomElement values = root.firstChildElement(QLatin1String("values")); + for (QDomNode n = values.firstChild(); !n.isNull(); n = n.nextSibling()) { + const QDomElement v = n.toElement(); + if (v.isNull() || v.tagName() != QLatin1String("value")) + continue; + + const QString name = v.attribute(QLatin1String("name")); + const QString type = v.attribute(QLatin1String("type")); + const QString value = v.text(); + + const QVariant::Type t = QVariant::nameToType(type.toLatin1().data()); + QVariant var = qVariantFromValue(value); + if (t == QVariant::List || t == QVariant::StringList || !var.convert(t)) { + QDataStream stream(QByteArray::fromBase64( value.toLatin1())); + stream >> var; + if (t == QVariant::StringList) { + QStringList list = var.toStringList(); + for (int i = 0; i < list.count(); ++i) { + list[i] = QInstaller::replacePath(list.at(i), + QLatin1String(QInstaller::scRelocatable), target); + } + var = QVariant::fromValue(list); + } + } + + m_values[name] = var; + } + + return true; +} + +/*! + \overload + + Restores operation arguments and values from the XML file at path \a xml. Returns \c true on + success, otherwise \c false. +*/ +bool UpdateOperation::fromXml(const QString &xml) +{ + QDomDocument doc; + QString errorMsg; + int errorLine; + int errorColumn; + if (!doc.setContent( xml, &errorMsg, &errorLine, &errorColumn)) { + qWarning() << "Error parsing xml error=" << errorMsg << "line=" << errorLine << "column=" << errorColumn; + return false; + } + return fromXml(doc); +} |