diff options
Diffstat (limited to 'src/libs/installer')
156 files changed, 32732 insertions, 0 deletions
diff --git a/src/libs/installer/addqtcreatorarrayvalueoperation.cpp b/src/libs/installer/addqtcreatorarrayvalueoperation.cpp new file mode 100644 index 000000000..23ca7a21a --- /dev/null +++ b/src/libs/installer/addqtcreatorarrayvalueoperation.cpp @@ -0,0 +1,171 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "addqtcreatorarrayvalueoperation.h" + +#include "constants.h" +#include "qtcreator_constants.h" +#include "packagemanagercore.h" + +#include <QtCore/QSettings> +#include <QtCore/QSet> + +using namespace QInstaller; + +static QString groupName(const QString &groupName) +{ + return groupName == QLatin1String("General") ? QString() : groupName; +} + +AddQtCreatorArrayValueOperation::AddQtCreatorArrayValueOperation() +{ + setName(QLatin1String("AddQtCreatorArrayValue")); +} + +void AddQtCreatorArrayValueOperation::backup() +{ +} + +bool AddQtCreatorArrayValueOperation::performOperation() +{ + const QStringList args = arguments(); + + if (args.count() != 4) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in %0: %1 arguments given, exactly 4 expected (group, " + "arrayname, key, value).").arg(name()).arg( arguments().count())); + return false; + } + + + PackageManagerCore *const core = qVariantValue<PackageManagerCore*>(value(QLatin1String("installer"))); + if (!core) { + setError(UserDefinedError); + setErrorString(tr("Needed installer object in %1 operation is empty.").arg(name())); + return false; + } + const QString &rootInstallPath = core->value(scTargetDir); + + QSettings settings(rootInstallPath + QLatin1String(QtCreatorSettingsSuffixPath), + QSettings::IniFormat); + + const QString &group = groupName(args.at(0)); + const QString &arrayName = args.at(1); + const QString &key = args.at(2); + const QString &value = args.at(3); + + if (!group.isEmpty()) + settings.beginGroup(group); + + QList<QString> oldArrayValues; + int arraySize = settings.beginReadArray(arrayName); + for (int i = 0; i < arraySize; ++i) { + settings.setArrayIndex(i); + //if it is already there we have nothing todo + if (settings.value(key).toString() == value) + return true; + oldArrayValues.append(settings.value(key).toString()); + } + settings.endArray(); + + + settings.beginWriteArray(arrayName); + + for (int i = 0; i < oldArrayValues.size(); ++i) { + settings.setArrayIndex(i); + settings.setValue(key, oldArrayValues.value(i)); + } + settings.setArrayIndex(oldArrayValues.size()); //means next index after the last insert + settings.setValue(key, value); + settings.endArray(); + + settings.sync(); //be save ;) + setValue(QLatin1String("ArrayValueSet"), true); + return true; +} + +bool AddQtCreatorArrayValueOperation::undoOperation() +{ + if (value(QLatin1String("ArrayValueSet")).isNull()) + return true; + const QStringList args = arguments(); + + PackageManagerCore *const core = qVariantValue<PackageManagerCore*>(value(QLatin1String("installer"))); + if (!core) { + setError(UserDefinedError); + setErrorString(tr("Needed installer object in %1 operation is empty.").arg(name())); + return false; + } + const QString &rootInstallPath = core->value(scTargetDir); + + QSettings settings(rootInstallPath + QLatin1String(QtCreatorSettingsSuffixPath), + QSettings::IniFormat); + + const QString &group = groupName(args.at(0)); + const QString &arrayName = args.at(1); + const QString &key = args.at(2); + const QString &value = args.at(3); + + if (!group.isEmpty()) + settings.beginGroup(group); + + QList<QString> oldArrayValues; + int arraySize = settings.beginReadArray(arrayName); + for (int i = 0; i < arraySize; ++i) { + settings.setArrayIndex(i); + if (settings.value(key).toString() != value) + oldArrayValues.append(settings.value(key).toString()); + } + settings.endArray(); + + + settings.beginWriteArray(arrayName); + + for (int i = 0; i < oldArrayValues.size(); ++i) { + settings.setArrayIndex(i); + settings.setValue(key, oldArrayValues.value(i)); + } + settings.endArray(); + + settings.sync(); //be save ;) + return true; +} + +bool AddQtCreatorArrayValueOperation::testOperation() +{ + return true; +} + +Operation *AddQtCreatorArrayValueOperation::clone() const +{ + return new AddQtCreatorArrayValueOperation(); +} diff --git a/src/libs/installer/addqtcreatorarrayvalueoperation.h b/src/libs/installer/addqtcreatorarrayvalueoperation.h new file mode 100644 index 000000000..156560422 --- /dev/null +++ b/src/libs/installer/addqtcreatorarrayvalueoperation.h @@ -0,0 +1,54 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef ADDQTCREATORVALUEOPERATION_H +#define ADDQTCREATORVALUEOPERATION_H + +#include "qinstallerglobal.h" + +namespace QInstaller { + +class AddQtCreatorArrayValueOperation : public Operation +{ +public: + AddQtCreatorArrayValueOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation *clone() const; +}; + +} // namespace QInstaller + +#endif // ADDQTCREATORVALUEOPERATION_H diff --git a/src/libs/installer/adminauthorization.cpp b/src/libs/installer/adminauthorization.cpp new file mode 100644 index 000000000..8b87d60c1 --- /dev/null +++ b/src/libs/installer/adminauthorization.cpp @@ -0,0 +1,48 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "adminauthorization.h" + +AdminAuthorizationBase::AdminAuthorizationBase() + : m_authorized(false) +{ +} + +bool AdminAuthorizationBase::isAuthorized() const +{ + return m_authorized; +} + +void AdminAuthorizationBase::setAuthorized() +{ + m_authorized = true; +} diff --git a/src/libs/installer/adminauthorization.h b/src/libs/installer/adminauthorization.h new file mode 100644 index 000000000..96180a30d --- /dev/null +++ b/src/libs/installer/adminauthorization.h @@ -0,0 +1,83 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef ADMINAUTHORIZATION_H +#define ADMINAUTHORIZATION_H + +#include <QtCore/QObject> + +class AdminAuthorizationBase +{ +protected: + AdminAuthorizationBase(); + +public: + virtual ~AdminAuthorizationBase() {} + + virtual bool authorize() = 0; + bool isAuthorized() const; + +protected: + void setAuthorized(); + +private: + bool m_authorized; +}; + +class AdminAuthorization : public QObject, public AdminAuthorizationBase +{ + Q_OBJECT + Q_PROPERTY(bool authorized READ isAuthorized) + +public: + AdminAuthorization(); +#ifdef Q_OS_MAC + ~AdminAuthorization(); +#endif + + bool execute(QWidget *dialogParent, const QString &programs, const QStringList &arguments); + static bool hasAdminRights(); + +public Q_SLOTS: + bool authorize(); + +Q_SIGNALS: + void authorized(); + +#ifdef Q_OS_MAC +private: + class Private; + Private *d; +#endif +}; + +#endif // ADMINAUTHORIZATION_H diff --git a/src/libs/installer/adminauthorization_mac.cpp b/src/libs/installer/adminauthorization_mac.cpp new file mode 100644 index 000000000..ce0a17095 --- /dev/null +++ b/src/libs/installer/adminauthorization_mac.cpp @@ -0,0 +1,120 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "adminauthorization.h" + +#include <Security/Authorization.h> +#include <Security/AuthorizationTags.h> + +#include <QtCore/QStringList> +#include <QtCore/QVector> + +#include <unistd.h> + + +// -- AdminAuthorization::Private + +class AdminAuthorization::Private +{ +public: + Private() : auth(0) { } + + AuthorizationRef auth; +}; + + +// -- AdminAuthorization + +AdminAuthorization::AdminAuthorization() + : d(new Private) +{ + AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &d->auth); +} + +AdminAuthorization::~AdminAuthorization() +{ + AuthorizationFree(d->auth, kAuthorizationFlagDestroyRights); + delete d; +} + +bool AdminAuthorization::authorize() +{ + if (geteuid() == 0) + setAuthorized(); + + if (isAuthorized()) + return true; + + AuthorizationItem item; + item.name = kAuthorizationRightExecute; + item.valueLength = 0; + item.value = NULL; + item.flags = 0; + + AuthorizationRights rights; + rights.count = 1; + rights.items = &item; + + const AuthorizationFlags flags = kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed + | kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights; + + const OSStatus result = AuthorizationCopyRights(d->auth, &rights, kAuthorizationEmptyEnvironment, + flags, 0); + if (result != errAuthorizationSuccess) + return false; + + seteuid(0); + setAuthorized(); + emit authorized(); + return true; +} + +bool AdminAuthorization::execute(QWidget *, const QString &program, const QStringList &arguments) +{ + QVector<char *> args; + QVector<QByteArray> utf8Args; + foreach (const QString &argument, arguments) { + utf8Args.push_back(argument.toUtf8()); + args.push_back(utf8Args.last().data()); + } + args.push_back(0); + + const QByteArray utf8Program = program.toUtf8(); + const OSStatus result = AuthorizationExecuteWithPrivileges(d->auth, utf8Program.data(), + kAuthorizationFlagDefaults, args.data(), 0); + return result == errAuthorizationSuccess; +} + +bool AdminAuthorization::hasAdminRights() +{ + return geteuid() == 0; +} diff --git a/src/libs/installer/adminauthorization_win.cpp b/src/libs/installer/adminauthorization_win.cpp new file mode 100644 index 000000000..7cfd6b3dc --- /dev/null +++ b/src/libs/installer/adminauthorization_win.cpp @@ -0,0 +1,103 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ +#include "adminauthorization.h" + +#include "utils.h" + +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QStringList> +#include <QtCore/QVector> + +#include <windows.h> + +AdminAuthorization::AdminAuthorization() +{ +} + +bool AdminAuthorization::authorize() +{ + setAuthorized(); + emit authorized(); + return true; +} + +bool AdminAuthorization::hasAdminRights() +{ + SID_IDENTIFIER_AUTHORITY authority = SECURITY_NT_AUTHORITY; + PSID adminGroup; + // Initialize SID. + if (!AllocateAndInitializeSid(&authority, + 2, + SECURITY_BUILTIN_DOMAIN_RID, + DOMAIN_ALIAS_RID_ADMINS, + 0, 0, 0, 0, 0, 0, + &adminGroup)) + return false; + + BOOL isInAdminGroup = FALSE; + if (!CheckTokenMembership(0, adminGroup, &isInAdminGroup)) + isInAdminGroup = FALSE; + + FreeSid(adminGroup); + return isInAdminGroup; +} + +bool AdminAuthorization::execute(QWidget *, const QString &program, const QStringList &arguments) +{ + const QString file = QDir::toNativeSeparators(program); + const QString args = QInstaller::createCommandline(QString(), arguments); + + const int len = GetShortPathNameW((wchar_t *)file.utf16(), 0, 0); + if (len == 0) + return false; + wchar_t *const buffer = new wchar_t[len]; + GetShortPathName((wchar_t *)file.utf16(), buffer, len); + + SHELLEXECUTEINFOW TempInfo = { 0 }; + TempInfo.cbSize = sizeof(SHELLEXECUTEINFOW); + TempInfo.fMask = 0; + TempInfo.hwnd = 0; + TempInfo.lpVerb = L"runas"; + TempInfo.lpFile = buffer; + TempInfo.lpParameters = (wchar_t *)args.utf16(); + TempInfo.lpDirectory = 0; + TempInfo.nShow = SW_NORMAL; + + + qDebug() << QString::fromLatin1(" starting elevated process %1 %2 with ::ShellExecuteExW( &TempInfo );" + ).arg(program, arguments.join(QLatin1String(" "))); + const bool result = ::ShellExecuteExW(&TempInfo); + qDebug() << QLatin1String("after starting elevated process"); + delete[] buffer; + return result; +} diff --git a/src/libs/installer/adminauthorization_x11.cpp b/src/libs/installer/adminauthorization_x11.cpp new file mode 100644 index 000000000..592f56ef7 --- /dev/null +++ b/src/libs/installer/adminauthorization_x11.cpp @@ -0,0 +1,252 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "adminauthorization.h" + +#include <QtCore/QFile> + +#include <QtGui/QApplication> +#include <QtGui/QInputDialog> +#include <QtGui/QMessageBox> + +#include <cstdlib> +#include <unistd.h> +#include <fcntl.h> + +#ifdef Q_OS_LINUX +#include <linux/limits.h> +#include <pty.h> +#else +#include <util.h> +#endif + +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include <iostream> + +#define SU_COMMAND "/usr/bin/sudo" +//#define SU_COMMAND "/bin/echo" + +AdminAuthorization::AdminAuthorization() +{ +} + +bool AdminAuthorization::authorize() +{ + return true; +} + +static QString getPassword(QWidget *) +{ + if (QApplication::type() == QApplication::GuiClient) { + bool ok = false; + const QString result = QInputDialog::getText(0, QObject::tr( "Authorization required" ), QObject::tr( "Enter your password to authorize for sudo:" ), QLineEdit::Password, QString(), &ok); + return ok ? result : QString(); + } else { + std::cout << QObject::tr("Authorization required").toStdString() << std::endl; + std::cout << QObject::tr("Enter your password to authorize for sudo:").toStdString() << std::endl; + std::string password; + std::cin >> password; + return QString::fromStdString(password); + } +} + +static void printError(QWidget *parent, const QString &value) +{ + if (QApplication::type() == QApplication::GuiClient) + QMessageBox::critical(parent, QObject::tr( "Error acquiring admin rights" ), value, QMessageBox::Ok, QMessageBox::Ok); + else + std::cout << value.toStdString() << std::endl; +} + +bool AdminAuthorization::execute(QWidget *parent, const QString &program, const QStringList &arguments) +{ + // as we cannot pipe the password to su in QProcess, we need to setup a pseudo-terminal for it + int masterFD = -1; + int slaveFD = -1; + char ptsn[ PATH_MAX ]; + + if (::openpty(&masterFD, &slaveFD, ptsn, 0, 0)) + return false; + + masterFD = ::posix_openpt(O_RDWR | O_NOCTTY); + if (masterFD < 0) + return false; + + const QByteArray ttyName = ::ptsname(masterFD); + + if (::grantpt(masterFD)) { + ::close(masterFD); + return false; + } + + ::revoke(ttyName); + ::unlockpt(masterFD); + + slaveFD = ::open(ttyName, O_RDWR | O_NOCTTY); + if (slaveFD < 0) { + ::close(masterFD); + return false; + } + + ::fcntl(masterFD, F_SETFD, FD_CLOEXEC); + ::fcntl(slaveFD, F_SETFD, FD_CLOEXEC); + int pipedData[2]; + if (pipe(pipedData) != 0) + return false; + + int flags = ::fcntl(pipedData[0], F_GETFD); + if (flags != -1) + ::fcntl(pipedData[0], F_SETFL, flags | O_NONBLOCK); + + pid_t child = fork(); + + if (child < -1) { + ::close(masterFD); + ::close(slaveFD); + ::close(pipedData[0]); + ::close(pipedData[1]); + return false; + } + + // parent process + else if (child > 0) { + ::close(slaveFD); + //close writing end of pipe + ::close(pipedData[1]); + + QRegExp re(QLatin1String("[Pp]assword.*:")); + QByteArray errData; + flags = ::fcntl(masterFD, F_GETFD); +// if (flags != -1) +// ::fcntl(masterFD, F_SETFL, flags | O_NONBLOCK); + int bytes = 0; + int errBytes = 0; + char buf[1024]; + while (bytes >= 0) { + int state; + if (::waitpid(child, &state, WNOHANG) == -1) + break; + bytes = ::read(masterFD, buf, 1023); + errBytes = ::read(pipedData[0], buf, 1023); + if (errBytes > 0) + errData.append(buf, errBytes); + if (bytes > 0) { + const QString line = QString::fromLatin1(buf, bytes); + if (re.indexIn(line) != -1) { + const QString password = getPassword(parent); + if (password == QString()) { + QByteArray pwd = password.toLatin1(); + for (int i = 0; i < 3; ++i) { + ::write(masterFD, pwd.data(), pwd.length()); + ::write(masterFD, "\n", 1); + } + return false; + } + QByteArray pwd = password.toLatin1(); + ::write(masterFD, pwd.data(), pwd.length()); + ::write(masterFD, "\n", 1); + ::read(masterFD, buf, pwd.length() + 1); + } + } + if (bytes == 0) + ::usleep(100000); + } + if (!errData.isEmpty()) { + printError(parent, QString::fromLocal8Bit(errData.constData())); + return false; + } + + int status; + child = ::wait(&status); + const int exited = WIFEXITED(status); + const int exitStatus = WEXITSTATUS(status); + ::close(pipedData[1]); + if (exited) + return exitStatus == 0; + + return false; + } + + // child process + else { + ::close(pipedData[0]); + // Reset signal handlers + for (int sig = 1; sig < NSIG; ++sig) + signal(sig, SIG_DFL); + signal(SIGHUP, SIG_IGN); + + ::setsid(); + + ::ioctl(slaveFD, TIOCSCTTY, 1); + int pgrp = ::getpid(); + ::tcsetpgrp(slaveFD, pgrp); + + ::dup2(slaveFD, 0); + ::dup2(slaveFD, 1); + ::dup2(pipedData[1], 2); + + // close all file descriptors + struct rlimit rlp; + getrlimit(RLIMIT_NOFILE, &rlp); + for (int i = 3; i < static_cast<int>(rlp.rlim_cur); ++i) + ::close(i); + + char **argp = (char **) ::malloc(arguments.count() + 4 * sizeof(char *)); + QList<QByteArray> args; + args.push_back(SU_COMMAND); + args.push_back("-b"); + args.push_back(program.toLocal8Bit()); + for (QStringList::const_iterator it = arguments.begin(); it != arguments.end(); ++it) + args.push_back(it->toLocal8Bit()); + + int i = 0; + for (QList<QByteArray>::iterator it = args.begin(); it != args.end(); ++it, ++i) + argp[i] = it->data(); + argp[i] = 0; + + ::unsetenv("LANG"); + ::unsetenv("LC_ALL"); + + ::execv(SU_COMMAND, argp); + _exit(0); + return false; + } +} + +// has no guarantee to work +bool AdminAuthorization::hasAdminRights() +{ + return getuid() == 0; +} diff --git a/src/libs/installer/binaryformat.cpp b/src/libs/installer/binaryformat.cpp new file mode 100644 index 000000000..133df3690 --- /dev/null +++ b/src/libs/installer/binaryformat.cpp @@ -0,0 +1,1115 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "binaryformat.h" + +#include "errors.h" +#include "fileutils.h" +#include "lib7z_facade.h" +#include "utils.h" +#include "zipjob.h" + +#include <kdupdaterupdateoperationfactory.h> + +#include <QtCore/QResource> +#include <QtCore/QTemporaryFile> + +#include <errno.h> + +using namespace QInstaller; +using namespace QInstallerCreator; + +/* +TRANSLATOR QInstallerCreator::Archive +*/ + +/* +TRANSLATOR QInstallerCreator::Component +*/ + +static inline QByteArray &theBuffer(int size) +{ + static QByteArray b; + if (size > b.size()) + b.resize(size); + return b; +} + +void QInstaller::appendFileData(QIODevice *out, QIODevice *in) +{ + Q_ASSERT(!in->isSequential()); + const qint64 size = in->size(); + blockingCopy(in, out, size); +} + + +void QInstaller::retrieveFileData(QIODevice *out, QIODevice *in) +{ + qint64 size = QInstaller::retrieveInt64(in); + appendData(in, out, size); +/* QByteArray &b = theBuffer(size); + blockingRead(in, b.data(), size); + blockingWrite(out, b.constData(), size);*/ +} + +void QInstaller::appendInt64(QIODevice *out, qint64 n) +{ + blockingWrite(out, reinterpret_cast<const char*>(&n), sizeof(n)); +} + +void QInstaller::appendInt64Range(QIODevice *out, const Range<qint64> &r) +{ + appendInt64(out, r.start()); + appendInt64(out, r.length()); +} + +qint64 QInstaller::retrieveInt64(QIODevice *in) +{ + qint64 n = 0; + blockingRead(in, reinterpret_cast<char*>(&n), sizeof(n)); + return n; +} + +Range<qint64> QInstaller::retrieveInt64Range(QIODevice *in) +{ + const quint64 start = retrieveInt64(in); + const quint64 length = retrieveInt64(in); + return Range<qint64>::fromStartAndLength(start, length); +} + +void QInstaller::appendData(QIODevice *out, QIODevice *in, qint64 size) +{ + while (size > 0) { + const qint64 nextSize = qMin(size, 16384LL); + QByteArray &b = theBuffer(nextSize); + blockingRead(in, b.data(), nextSize); + blockingWrite(out, b.constData(), nextSize); + size -= nextSize; + } +} + +void QInstaller::appendString(QIODevice *out, const QString &str) +{ + appendByteArray(out, str.toUtf8()); +} + +void QInstaller::appendByteArray(QIODevice *out, const QByteArray &ba) +{ + appendInt64(out, ba.size()); + blockingWrite(out, ba.constData(), ba.size()); +} + +void QInstaller::appendStringList(QIODevice *out, const QStringList &list) +{ + appendInt64(out, list.size()); + foreach (const QString &s, list) + appendString(out, s); +} + +void QInstaller::appendDictionary(QIODevice *out, const QHash<QString,QString> &dict) +{ + appendInt64(out, dict.size()); + foreach (const QString &key, dict.keys()) { + appendString(out, key); + appendString(out, dict.value(key)); + } +} + +qint64 QInstaller::appendCompressedData(QIODevice *out, QIODevice *in, qint64 size) +{ + QByteArray ba; + ba.resize(size); + blockingRead(in, ba.data(), size); + + QByteArray cba = qCompress(ba); + blockingWrite(out, cba, cba.size()); + return cba.size(); +} + +QString QInstaller::retrieveString(QIODevice *in) +{ + const QByteArray b = retrieveByteArray(in); + return QString::fromUtf8(b); +} + +QByteArray QInstaller::retrieveByteArray(QIODevice *in) +{ + QByteArray ba; + const qint64 n = retrieveInt64(in); + ba.resize(n); + blockingRead(in, ba.data(), n); + return ba; +} + +QStringList QInstaller::retrieveStringList(QIODevice *in) +{ + QStringList list; + for (qint64 i = retrieveInt64(in); --i >= 0;) + list << retrieveString(in); + return list; +} + +QHash<QString,QString> QInstaller::retrieveDictionary(QIODevice *in) +{ + QHash<QString,QString> dict; + for (qint64 i = retrieveInt64(in); --i >= 0;) { + QString key = retrieveString(in); + dict.insert(key, retrieveString(in)); + } + return dict; +} + +QByteArray QInstaller::retrieveData(QIODevice *in, qint64 size) +{ + QByteArray ba; + ba.resize(size); + blockingRead(in, ba.data(), size); + return ba; +} + +QByteArray QInstaller::retrieveCompressedData(QIODevice *in, qint64 size) +{ + QByteArray ba; + ba.resize(size); + blockingRead(in, ba.data(), size); + return qUncompress(ba); +} + +qint64 QInstaller::findMagicCookie(QFile *in, quint64 magicCookie) +{ + Q_ASSERT(in); + Q_ASSERT(in->isOpen()); + Q_ASSERT(in->isReadable()); + const qint64 oldPos = in->pos(); + const qint64 MAX_SEARCH = 1024 * 1024; // stop searching after one MB + qint64 searched = 0; + try { + while (searched < MAX_SEARCH) { + const qint64 pos = in->size() - searched - sizeof(qint64); + if (pos < 0) + throw Error(QObject::tr("Searched whole file, no marker found")); + if (!in->seek(pos)) { + throw Error(QObject::tr("Could not seek to %1 in file %2: %3").arg(QString::number(pos), + in->fileName(), in->errorString())); + } + const quint64 num = static_cast<quint64>(retrieveInt64(in)); + if (num == magicCookie) { + in->seek(oldPos); + return pos; + } + searched += 1; + } + throw Error(QObject::tr("No marker found, stopped after %1 bytes.").arg(QString::number(MAX_SEARCH))); + } catch (const Error& err) { + in->seek(oldPos); + throw err; + } catch (...) { + in->seek(oldPos); + throw Error(QObject::tr("No marker found, unknown exception caught.")); + } + return -1; // never reached +} + +/*! + Creates an archive providing the data in \a path. + \a path can be a path to a file or to a directory. If it's a file, it's considered to be + pre-zipped and gets delivered as it is. If it's a directory, it gets zipped by Archive. + */ +Archive::Archive(const QString &path) + : m_device(0), + m_isTempFile(false), + m_path(path), + m_name(QFileInfo(path).fileName().toUtf8()) +{ +} + +Archive::Archive(const QByteArray &identifier, const QByteArray &data) + : m_device(0), + m_isTempFile(true), + m_path(generateTemporaryFileName()), + m_name(identifier) +{ + QFile file(m_path); + file.open(QIODevice::WriteOnly); + file.write(data); +} + +/*! + Creates an archive identified by \a identifier providing a data \a segment within a \a device. + */ +Archive::Archive(const QByteArray &identifier, const QSharedPointer<QFile> &device, const Range<qint64> &segment) + : m_device(device), + m_segment(segment), + m_isTempFile(false), + m_name(identifier) +{ +} + +Archive::~Archive() +{ + if (isOpen()) + close(); + if (m_isTempFile) + QFile::remove(m_path); +} + +/*! + Copies the archives contents to the path \a name. + If the archive is a zipped directory, \a name is treated as a directory. The archive gets extracted there. + + If the archive is a plain file and \a name an existing directory, it gets created + with it's name. Otherwise it gets saved as \a name. + Note that if a file with the \a name already exists, copy() return false (i.e. Archive will not overwrite it). + */ +bool Archive::copy(const QString &name) +{ + const QFileInfo fileInfo(name); + if (isZippedDirectory()) { + if (fileInfo.exists() && !fileInfo.isDir()) + return false; + + errno = 0; + const QString absoluteFilePath = fileInfo.absoluteFilePath(); + if (!fileInfo.exists() && !QDir().mkpath(absoluteFilePath)) { + setErrorString(tr("Could not create %1: %2").arg(name, QString::fromLocal8Bit(strerror(errno)))); + return false; + } + + if (isOpen()) + close(); + open(QIODevice::ReadOnly); + + UnzipJob job; + job.setInputDevice(this); + job.setOutputPath(absoluteFilePath); + job.run(); + } else { + if (isOpen()) + close(); + open(QIODevice::ReadOnly); + + QFile target(fileInfo.isDir() ? QString::fromLatin1("%1/%2").arg(name) + .arg(QString::fromUtf8(m_name.data(), m_name.count())) : name); + if (target.exists()) + return false; + target.open(QIODevice::WriteOnly); + blockingCopy(this, &target, size()); + } + close(); + return true; +} + +/*! + \reimp + */ +bool Archive::seek(qint64 pos) +{ + if (m_inputFile.isOpen()) + return m_inputFile.seek(pos) && QIODevice::seek(pos); + return QIODevice::seek(pos); +} + +/*! + Returns true, if this archive was created by zipping a directory. + */ +bool Archive::isZippedDirectory() const +{ + if (m_device == 0) { + // easy, just check whether it's a dir + return QFileInfo(m_path).isDir(); + } + + // more complex, check the zip header magic + Archive* const arch = const_cast<Archive*> (this); + + const bool notOpened = !isOpen(); + if (notOpened) + arch->open(QIODevice::ReadOnly); + const qint64 p = pos(); + arch->seek(0); + + const QByteArray ba = arch->read(4); + const bool result = ba == QByteArray("\x50\x4b\x03\04"); + + arch->seek(p); + if (notOpened) + arch->close(); + return result; +} + +QByteArray Archive::name() const +{ + return m_name; +} + +void Archive::setName(const QByteArray &name) +{ + m_name = name; +} + +/*! + \reimpl + */ +void Archive::close() +{ + m_inputFile.close(); + if (QFileInfo(m_path).isDir()) + m_inputFile.remove(); + QIODevice::close(); +} + +/*! + \reimp + */ +bool Archive::open(OpenMode mode) +{ + if (isOpen()) + return false; + + const bool writeOnly = (mode & QIODevice::WriteOnly) != QIODevice::NotOpen; + const bool append = (mode & QIODevice::Append) != QIODevice::NotOpen; + + // no write support + if (writeOnly || append) + return false; + + if (m_device != 0) + return QIODevice::open(mode); + + const QFileInfo fi(m_path); + if (fi.isFile()) { + m_inputFile.setFileName(m_path); + if (!m_inputFile.open(mode)) { + setErrorString(tr("Could not open archive file %1 for reading.").arg(m_path)); + return false; + } + setOpenMode(mode); + return true; + } + + if (fi.isDir()) { + if (m_inputFile.fileName().isEmpty() || !m_inputFile.exists()) { + if (!createZippedFile()) + return false; + } + Q_ASSERT(!m_inputFile.fileName().isEmpty()); + if (!m_inputFile.open(mode)) + return false; + setOpenMode(mode); + return true; + } + + setErrorString(tr("Could not create archive from %1: Not a file.").arg(m_path)); + return false; +} + +bool Archive::createZippedFile() +{ + QTemporaryFile file; + file.setAutoRemove(false); + if (!file.open()) + return false; + + m_inputFile.setFileName(file.fileName()); + file.close(); + m_inputFile.open(QIODevice::ReadWrite); + try { + Lib7z::createArchive(&m_inputFile, QStringList() << m_path); + } catch(Lib7z::SevenZipException &e) { + m_inputFile.close(); + setErrorString(e.message()); + return false; + } + + if (!Lib7z::isSupportedArchive(&m_inputFile)) { + m_inputFile.close(); + setErrorString(tr("Error while packing directory at %1").arg(m_path)); + return false; + } + m_inputFile.close(); + return true; +} + +/*! + \reimp + */ +qint64 Archive::size() const +{ + // if we got a device, we just pass the length of the segment + if (m_device != 0) + return m_segment.length(); + + const QFileInfo fi(m_path); + // if we got a regular file, we pass the size of the file + if (fi.isFile()) + return fi.size(); + + if (fi.isDir()) { + if (m_inputFile.fileName().isEmpty() || !m_inputFile.exists()) { + if (!const_cast< Archive* >(this)->createZippedFile()) { + throw Error(QObject::tr("Cannot create zipped file for path %1: %2").arg(m_path, + errorString())); + } + } + Q_ASSERT(!m_inputFile.fileName().isEmpty()); + return m_inputFile.size(); + } + return 0; +} + +/*! + \reimp + */ +qint64 Archive::readData(char* data, qint64 maxSize) +{ + if (m_device == 0) + return m_inputFile.read(data, maxSize); + + const qint64 p = m_device->pos(); + m_device->seek(m_segment.start() + pos()); + const qint64 amountRead = m_device->read(data, qMin<quint64>(maxSize, m_segment.length() - pos())); + m_device->seek(p); + return amountRead; +} + +/*! + \reimp + */ +qint64 Archive::writeData(const char* data, qint64 maxSize) +{ + Q_UNUSED(data); + Q_UNUSED(maxSize); + // should never be called, as we're read only + return -1; +} + +QByteArray Component::name() const +{ + return m_name; +} + +void Component::setName(const QByteArray &ba) +{ + m_name = ba; +} + +Range<qint64> Component::binarySegment() const +{ + return m_binarySegment; +} + +void Component::setBinarySegment(const Range<qint64> &r) +{ + m_binarySegment = r; +} + +Component Component::readFromIndexEntry(const QSharedPointer<QFile> &in, qint64 offset) +{ + Component c; + c.m_name = retrieveByteArray(in.data()); + c.m_binarySegment = retrieveInt64Range(in.data()).moved(offset); + + c.readData(in, offset); + + return c; +} + +void Component::writeIndexEntry(QIODevice *out, qint64 positionOffset) const +{ + appendByteArray(out, m_name); + const Range<qint64> relative = m_binarySegment.moved(positionOffset); + appendInt64(out, binarySegment().start()); + appendInt64(out, binarySegment().length()); +} + +void Component::writeData(QIODevice *out, qint64 offset) const +{ + const qint64 dataBegin = out->pos() + offset; + + appendInt64(out, m_archives.count()); + + qint64 start = out->pos() + offset; + + // Why 16 + 16? This is 24, not 32??? + const int foo = 3 * sizeof(qint64); + // add 16 + 16 + number of name characters for each archive (the size of the table) + foreach (const QSharedPointer<Archive> &archive, m_archives) + start += foo + archive->name().count(); + + QList<qint64> starts; + foreach (const QSharedPointer<Archive> &archive, m_archives) { + appendByteArray(out, archive->name()); + starts.push_back(start); + appendInt64Range(out, Range<qint64>::fromStartAndLength(start, archive->size())); + start += archive->size(); + } + + foreach (const QSharedPointer<Archive> &archive, m_archives) { + if (!archive->open(QIODevice::ReadOnly)) { + throw Error(tr("Could not open archive %1: %2").arg(QLatin1String(archive->name()), + archive->errorString())); + } + + const qint64 expectedStart = starts.takeFirst(); + const qint64 actualStart = out->pos() + offset; + Q_UNUSED(expectedStart); + Q_UNUSED(actualStart); + Q_ASSERT(expectedStart == actualStart); + blockingCopy(archive.data(), out, archive->size()); + } + + m_binarySegment = Range<qint64>::fromStartAndEnd(dataBegin, out->pos() + offset); +} + +void Component::readData(const QSharedPointer<QFile> &in, qint64 offset) +{ + const qint64 pos = in->pos(); + + in->seek(m_binarySegment.start()); + const qint64 count = retrieveInt64(in.data()); + + QVector<QByteArray> names; + QVector<Range<qint64> > ranges; + for (int i = 0; i < count; ++i) { + names.push_back(retrieveByteArray(in.data())); + ranges.push_back(retrieveInt64Range(in.data()).moved(offset)); + } + + for (int i = 0; i < ranges.count(); ++i) + m_archives.append(QSharedPointer<Archive>(new Archive(names.at(i), in, ranges.at(i)))); + + in->seek(pos); +} + +QString Component::dataDirectory() const +{ + return m_dataDirectory; +} + +void Component::setDataDirectory(const QString &path) +{ + m_dataDirectory = path; +} + +bool Component::operator<(const Component& other) const +{ + if (m_name != other.name()) + return m_name < other.m_name; + return m_binarySegment < other.m_binarySegment; +} + +bool Component::operator==(const Component& other) const +{ + return m_name == other.m_name && m_binarySegment == other.m_binarySegment; +} + +/*! + Destroys this component. + */ +Component::~Component() +{ +} + +/*! + Appends \a archive to this component. The component takes ownership of \a archive. + */ +void Component::appendArchive(const QSharedPointer<Archive>& archive) +{ + Q_ASSERT(archive); + archive->setParent(0); + m_archives.push_back(archive); +} + +/*! + Returns the archives associated with this component. + */ +QVector<QSharedPointer<Archive> > Component::archives() const +{ + return m_archives; +} + +QSharedPointer<Archive> Component::archiveByName(const QByteArray &name) const +{ + foreach (const QSharedPointer<Archive>& i, m_archives) { + if (i->name() == name) + return i; + } + return QSharedPointer<Archive>(); +} + + +// -- ComponentIndex + +ComponentIndex::ComponentIndex() +{ +} + +ComponentIndex ComponentIndex::read(const QSharedPointer<QFile> &dev, qint64 offset) +{ + ComponentIndex result; + const qint64 size = retrieveInt64(dev.data()); + for (int i = 0; i < size; ++i) + result.insertComponent(Component::readFromIndexEntry(dev, offset)); + retrieveInt64(dev.data()); + return result; +} + +void ComponentIndex::writeIndex(QIODevice *out, qint64 offset) const +{ + // Q: why do we write the size twice? + // A: for us to be able to read it beginning from the end of the file as well + appendInt64(out, componentCount()); + foreach (const Component& i, components()) + i.writeIndexEntry(out, offset); + appendInt64(out, componentCount()); +} + +void ComponentIndex::writeComponentData(QIODevice *out, qint64 offset) const +{ + appendInt64(out, componentCount()); + + foreach (const Component &component, m_components) + component.writeData(out, offset); +} + +Component ComponentIndex::componentByName(const QByteArray &id) const +{ + return m_components.value(id); +} + +void ComponentIndex::insertComponent(const Component& c) +{ + m_components.insert(c.name(), c); +} + +void ComponentIndex::removeComponent(const QByteArray &name) +{ + m_components.remove(name); +} + +QVector<Component> ComponentIndex::components() const +{ + return m_components.values().toVector(); +} + +int ComponentIndex::componentCount() const +{ + return m_components.size(); +} + + +static QVector<QByteArray> sResourceVec; +/*! + \internal + Registers the resource found at \a segment within \a file into the Qt resource system. + */ +static const uchar* addResourceFromBinary(QFile* file, const Range<qint64> &segment) +{ + if (segment.length() <= 0) + return 0; + + if (!file->seek(segment.start())) { + throw Error(QObject::tr("Could not seek to in-binary resource. (offset: %1, length: %2)") + .arg(QString::number(segment.start()), QString::number(segment.length()))); + } + sResourceVec.append(retrieveData(file, segment.length())); + + if (!QResource::registerResource((const uchar*)(sResourceVec.last().constData()), + QLatin1String(":/metadata"))) { + throw Error(QObject::tr("Could not register in-binary resource.")); + } + return (const uchar*)(sResourceVec.last().constData()); +} + + +// -- BinaryContentPrivate + +BinaryContentPrivate::BinaryContentPrivate(const QString &path) + : m_magicMarker(0) + , m_dataBlockStart(0) + , m_appBinary(new QFile(path)) + , m_binaryDataFile(0) + , m_binaryFormatEngineHandler(m_componentIndex) +{ +} + +BinaryContentPrivate::BinaryContentPrivate(const BinaryContentPrivate &other) + : QSharedData(other) + , m_magicMarker(other.m_magicMarker) + , m_dataBlockStart(other.m_dataBlockStart) + , m_appBinary(other.m_appBinary) + , m_binaryDataFile(other.m_binaryDataFile) + , m_performedOperations(other.m_performedOperations) + , m_performedOperationsData(other.m_performedOperationsData) + , m_resourceMappings(other.m_resourceMappings) + , m_metadataResourceSegments(other.m_metadataResourceSegments) + , m_componentIndex(other.m_componentIndex) + , m_binaryFormatEngineHandler(other.m_binaryFormatEngineHandler) +{ +} + +BinaryContentPrivate::~BinaryContentPrivate() +{ + foreach (const uchar *rccData, m_resourceMappings) + QResource::unregisterResource(rccData); + sResourceVec.clear(); + m_resourceMappings.clear(); +} + + +// -- BinaryContent + +BinaryContent::BinaryContent(const QString &path) + : d(new BinaryContentPrivate(path)) +{ +} + +BinaryContent::~BinaryContent() +{ +} + +/*! + Reads binary content stored in the current application binary. Maps the embedded resources into memory + and instantiates performed operations if available. +*/ +BinaryContent BinaryContent::readAndRegisterFromApplicationFile() +{ + BinaryContent c = BinaryContent::readFromApplicationFile(); + c.registerEmbeddedQResources(); + c.registerPerformedOperations(); + return c; +} + +/*! + Reads binary content stored in the passed application binary. Maps the embedded resources into memory + and instantiates performed operations if available. +*/ +BinaryContent BinaryContent::readAndRegisterFromBinary(const QString &path) +{ + BinaryContent c = BinaryContent::readFromBinary(path); + c.registerEmbeddedQResources(); + c.registerPerformedOperations(); + return c; +} + +/*! + Reads binary content stored in the current application binary. +*/ +BinaryContent BinaryContent::readFromApplicationFile() +{ + return BinaryContent::readFromBinary(QCoreApplication::applicationFilePath());; +} + +/*! + * \class QInstaller::BinaryContent + * + * BinaryContent handles binary information embedded into executables. + * Qt resources as well as component information can be stored. + * + * Explanation of the binary blob at the end of the installer or separate data file: + * + * \verbatim + * Meta data segment 0 + * Meta data segment ... + * Meta data segment n + * ------------------------------------------------------ + * Component data segment 0 + * Component data segment .. + * Component data segment n + * ------------------------------------------------------ + * Component index segment + * ------------------------------------------------------ + * quint64 offset of component index segment + * quint64 length of component index segment + * ------------------------------------------------------ + * qint64 offset of meta data segment 0 + * qint64 length of meta data segment 0 + * qint64 offset of meta data segment .. + * qint64 length of meta data segment .. + * qint64 offset of meta data segment n + * qint64 length of meta data segment n + * ------------------------------------------------------ + * operations start offest + * operations end + * quint64 embedded resource count + * quint64 data block size + * quint64 Magic marker + * quint64 Magic cookie (0xc2 0x63 0x0a 0x1c 0x99 0xd6 0x68 0xf8) + * <eof> + * + * All offsets are addresses relative to the end of the file. + * + * Meta data segments are stored as Qt resources, which must be "mounted" + * via QResource::registerResource() + * + * Component index segment: + * quint64 number of index entries + * QString identifier of component 0 + * quint64 offset of component data segment 0 + * quint64 length of component data segment 0 + * QString identifier of component .. + * quint64 offset of component data segment .. + * quint64 length of component data segment .. + * QString identifier of component n + * quint64 offset of component data segment n + * quint64 length of component data segment n + * quint64 number of index entries + * + * Component data segment: + * quint64 number of archives in this component + * QString name of archive 0 + * quint64 offset of archive 0 + * quint64 length of archive 0 + * QString name of archive .. + * quint64 offset of archive .. + * quint64 length of archive .. + * QString name of archive n + * quint64 offset of archive n + * quint64 length of archive n + * Archive 0 + * Archive .. + * Archive n + * \endverbatim + */ + +BinaryContent BinaryContent::readFromBinary(const QString &path) +{ + BinaryContent c(path); + if (!c.d->m_appBinary->open(QIODevice::ReadOnly)) + throw Error(QObject::tr("Could not open binary %1: %2").arg(path, c.d->m_appBinary->errorString())); + + // check for supported binary, will throw if we can't find a marker + const BinaryLayout layout = readBinaryLayout(c.d->m_appBinary.data(), + findMagicCookie(c.d->m_appBinary.data(), QInstaller::MagicCookie)); + + bool retry = true; + if (layout.magicMarker != MagicInstallerMarker) { + QString binaryDataPath = path; + QFileInfo fi(path + QLatin1String("/../../..")); + if (QFileInfo(fi.absoluteFilePath()).isBundle()) + binaryDataPath = fi.absoluteFilePath(); + fi.setFile(binaryDataPath); + + c.d->m_binaryDataFile = QSharedPointer<QFile>(new QFile(fi.absolutePath() + QLatin1Char('/') + + fi.baseName() + QLatin1String(".dat"))); + if (c.d->m_binaryDataFile->exists() && c.d->m_binaryDataFile->open(QIODevice::ReadOnly)) { + // check for supported binary data file, will throw if we can't find a marker + try { + const qint64 cookiePos = findMagicCookie(c.d->m_binaryDataFile.data(), + QInstaller::MagicCookieDat); + const BinaryLayout binaryLayout = readBinaryLayout(c.d->m_binaryDataFile.data(), cookiePos); + readBinaryData(c, c.d->m_binaryDataFile, binaryLayout); + retry = false; + } catch (const Error &error) { + // this seems to be an unsupported dat file, try to read from original binary + c.d->m_binaryDataFile.clear(); + qDebug() << error.message(); + } + } else { + c.d->m_binaryDataFile.clear(); + } + } + + if (retry) + readBinaryData(c, c.d->m_appBinary, layout); + + return c; +} + +/* static */ +BinaryLayout BinaryContent::readBinaryLayout(QIODevice *const file, qint64 cookiePos) +{ + const qint64 indexSize = 5 * sizeof(qint64); + if (!file->seek(cookiePos - indexSize)) + throw Error(QObject::tr("Could not seek to binary layout section.")); + + BinaryLayout layout; + layout.operationsStart = retrieveInt64(file); + layout.operationsEnd = retrieveInt64(file); + layout.resourceCount = retrieveInt64(file); + layout.dataBlockSize = retrieveInt64(file); + layout.magicMarker = retrieveInt64(file); + layout.magicCookie = retrieveInt64(file); + layout.indexSize = indexSize + sizeof(qint64); + layout.endOfData = file->pos(); + + qDebug() << "Operations start:" << layout.operationsStart; + qDebug() << "Operations end:" << layout.operationsEnd; + qDebug() << "Resource count:" << layout.resourceCount; + qDebug() << "Data block size:" << layout.dataBlockSize; + qDebug() << "Magic marker:" << layout.magicMarker; + qDebug() << "Magic cookie:" << layout.magicCookie; + qDebug() << "Index size:" << layout.indexSize; + qDebug() << "End of data:" << layout.endOfData; + + const qint64 resourceOffsetAndLengtSize = 2 * sizeof(qint64); + const qint64 dataBlockStart = layout.endOfData - layout.dataBlockSize; + for (int i = 0; i < layout.resourceCount; ++i) { + if (!file->seek(layout.endOfData - layout.indexSize - resourceOffsetAndLengtSize * (i + 1))) + throw Error(QObject::tr("Could not seek to metadata index.")); + + const qint64 metadataResourceOffset = retrieveInt64(file); + const qint64 metadataResourceLength = retrieveInt64(file); + layout.metadataResourceSegments.append(Range<qint64>::fromStartAndLength(metadataResourceOffset + + dataBlockStart, metadataResourceLength)); + } + + return layout; +} + +/* static */ +void BinaryContent::readBinaryData(BinaryContent &content, const QSharedPointer<QFile> &file, + const BinaryLayout &layout) +{ + content.d->m_magicMarker = layout.magicMarker; + content.d->m_metadataResourceSegments = layout.metadataResourceSegments; + + const qint64 dataBlockStart = layout.endOfData - layout.dataBlockSize; + const qint64 operationsStart = layout.operationsStart + dataBlockStart; + if (!file->seek(operationsStart)) + throw Error(QObject::tr("Could not seek to operation list.")); + + const qint64 operationsCount = retrieveInt64(file.data()); + qDebug() << "Number of operations:" << operationsCount; + + for (int i = 0; i < operationsCount; ++i) { + const QString name = retrieveString(file.data()); + const QString data = retrieveString(file.data()); + content.d->m_performedOperationsData.append(qMakePair(name, data)); + } + + // seek to the position of the component index + const qint64 resourceOffsetAndLengtSize = 2 * sizeof(qint64); + const qint64 resourceSectionSize = resourceOffsetAndLengtSize * layout.resourceCount; + if (!file->seek(layout.endOfData - layout.indexSize - resourceSectionSize - resourceOffsetAndLengtSize)) + throw Error(QObject::tr("Could not seek to component index information.")); + + const qint64 compIndexStart = retrieveInt64(file.data()) + dataBlockStart; + if (!file->seek(compIndexStart)) + throw Error(QObject::tr("Could not seek to component index.")); + + content.d->m_componentIndex = QInstallerCreator::ComponentIndex::read(file, dataBlockStart); + content.d->m_binaryFormatEngineHandler.setComponentIndex(content.d->m_componentIndex); + + if (isVerbose()) { + const QVector<QInstallerCreator::Component> components = content.d->m_componentIndex.components(); + qDebug() << "Number of components loaded:" << components.count(); + foreach (const QInstallerCreator::Component &component, components) { + const QVector<QSharedPointer<Archive> > archives = component.archives(); + qDebug() << component.name().data() << "loaded..."; + QStringList archivesWithSize; + foreach (const QSharedPointer<Archive> &archive, archives) { + QString archiveWithSize(QLatin1String("%1 - %2 Bytes")); + archiveWithSize = archiveWithSize.arg(QString::fromLocal8Bit(archive->name()), + QString::number(archive->size())); + archivesWithSize.append(archiveWithSize); + } + if (!archivesWithSize.isEmpty()) { + qDebug() << " - " << archives.count() << "archives: " + << qPrintable(archivesWithSize.join(QLatin1String("; "))); + } + } + } +} + +/*! + Registers already performed operations. +*/ +int BinaryContent::registerPerformedOperations() +{ + if (d->m_performedOperations.count() > 0) + return d->m_performedOperations.count(); + + for (int i = 0; i < d->m_performedOperationsData.count(); ++ i) { + const QPair<QString, QString> opPair = d->m_performedOperationsData.at(i); + QScopedPointer<Operation> op(KDUpdater::UpdateOperationFactory::instance().create(opPair.first)); + Q_ASSERT_X(!op.isNull(), __FUNCTION__, QString::fromLatin1("Invalid operation name: %1.") + .arg(opPair.first).toLatin1()); + + if (!op->fromXml(opPair.second)) { + qWarning() << "Failed to load XML for operation:" << opPair.first; + continue; + } + d->m_performedOperations.append(op.take()); + } + return d->m_performedOperations.count(); +} + +/*! + Returns the operations performed during installation. Returns an empty list if no operations are + instantiated, performed or the binary is the installer application. +*/ +OperationList BinaryContent::performedOperations() const +{ + return d->m_performedOperations; +} + +/*! + Returns the magic marker found in the binary. Returns 0 if no marker has been found. +*/ +qint64 BinaryContent::magicMarker() const +{ + return d->m_magicMarker; +} + +/*! + Registers the Qt resources embedded in this binary. + */ +int BinaryContent::registerEmbeddedQResources() +{ + if (d->m_resourceMappings.count() > 0) + return d->m_resourceMappings.count(); + + const bool hasBinaryDataFile = !d->m_binaryDataFile.isNull(); + QFile *const data = hasBinaryDataFile ? d->m_binaryDataFile.data() : d->m_appBinary.data(); + if (!data->isOpen() && !data->open(QIODevice::ReadOnly)) { + throw Error(QObject::tr("Could not open binary %1: %2").arg(data->fileName(), + data->errorString())); + } + + foreach (const Range<qint64> &i, d->m_metadataResourceSegments) + d->m_resourceMappings.append(addResourceFromBinary(data, i)); + + d->m_appBinary.clear(); + if (hasBinaryDataFile) + d->m_binaryDataFile.clear(); + + return d->m_resourceMappings.count(); +} + +/*! + Returns the binary component index as read from the file. +*/ +QInstallerCreator::ComponentIndex BinaryContent::componentIndex() const +{ + return d->m_componentIndex; +} diff --git a/src/libs/installer/binaryformat.h b/src/libs/installer/binaryformat.h new file mode 100644 index 000000000..e2e1f6a0f --- /dev/null +++ b/src/libs/installer/binaryformat.h @@ -0,0 +1,248 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef BINARYFORMAT_H +#define BINARYFORMAT_H + +#include "binaryformatenginehandler.h" +#include "range.h" +#include "qinstallerglobal.h" + +#include <QtCore/QCoreApplication> +#include <QtCore/QFile> +#include <QtCore/QHash> +#include <QtCore/QStack> +#include <QtCore/QVector> +#include <QtCore/QSharedPointer> + +namespace QInstaller { + static const qint64 MagicInstallerMarker = 0x12023233UL; + static const qint64 MagicUninstallerMarker = 0x12023234UL; + + static const qint64 MagicUpdaterMarker = 0x12023235UL; + static const qint64 MagicPackageManagerMarker = 0x12023236UL; + + // this cookie is put at the end of the file to determine whether we have data + static const quint64 MagicCookie = 0xc2630a1c99d668f8LL; + static const quint64 MagicCookieDat = 0xc2630a1c99d668f9LL; + + qint64 INSTALLER_EXPORT findMagicCookie(QFile *file, quint64 magicCookie = MagicCookie); + void INSTALLER_EXPORT appendFileData(QIODevice *out, QIODevice *in); + void INSTALLER_EXPORT appendInt64(QIODevice *out, qint64 n); + void INSTALLER_EXPORT appendInt64Range(QIODevice *out, const Range<qint64> &r); + void INSTALLER_EXPORT appendData(QIODevice *out, QIODevice *in, qint64 size); + void INSTALLER_EXPORT appendByteArray(QIODevice *out, const QByteArray &ba); + void INSTALLER_EXPORT appendString(QIODevice *out, const QString &str); + void INSTALLER_EXPORT appendStringList(QIODevice *out, const QStringList &list); + void INSTALLER_EXPORT appendDictionary(QIODevice *out, const QHash<QString,QString> &dict); + qint64 INSTALLER_EXPORT appendCompressedData(QIODevice *out, QIODevice *in, qint64 size); + + void INSTALLER_EXPORT retrieveFileData(QIODevice *out, QIODevice *in); + qint64 INSTALLER_EXPORT retrieveInt64(QIODevice *in); + Range<qint64> INSTALLER_EXPORT retrieveInt64Range(QIODevice *in); + QByteArray INSTALLER_EXPORT retrieveByteArray(QIODevice *in); + QString INSTALLER_EXPORT retrieveString(QIODevice *in); + QStringList INSTALLER_EXPORT retrieveStringList(QIODevice *in); + QHash<QString,QString> INSTALLER_EXPORT retrieveDictionary(QIODevice *in); + QByteArray INSTALLER_EXPORT retrieveData(QIODevice *in, qint64 size); + QByteArray INSTALLER_EXPORT retrieveCompressedData(QIODevice *in, qint64 size); +} + +namespace QInstallerCreator { +class Component; + +class INSTALLER_EXPORT Archive : public QIODevice +{ + Q_OBJECT +public: + explicit Archive(const QString &path); + Archive(const QByteArray &name, const QByteArray &data); + Archive(const QByteArray &name, const QSharedPointer<QFile> &device, const Range<qint64> &segment); + ~Archive(); + + bool open(OpenMode mode); + void close(); + + bool seek(qint64 pos); + qint64 size() const; + + bool createZippedFile(); + bool isZippedDirectory() const; + bool copy(const QString &name); + + QByteArray name() const; + void setName(const QByteArray &name); + +protected: + qint64 readData(char *data, qint64 maxSize); + qint64 writeData(const char *data, qint64 maxSize); + + Range< qint64 > binarySegment() const; + +private: + //used when when reading from the installer + QSharedPointer<QFile> m_device; + const Range<qint64> m_segment; + + //used when creating the installer, archive input file + QFile m_inputFile; + const bool m_isTempFile; + const QString m_path; + QByteArray m_name; +}; + +class INSTALLER_EXPORT Component +{ + Q_DECLARE_TR_FUNCTIONS(Component) + +public: + virtual ~Component(); + + static Component readFromIndexEntry(const QSharedPointer<QFile> &dev, qint64 offset); + void writeIndexEntry(QIODevice *dev, qint64 offset) const; + + void writeData(QIODevice *dev, qint64 positionOffset) const; + void readData(const QSharedPointer<QFile> &dev, qint64 offset); + + QByteArray name() const; + void setName(const QByteArray &ba); + + QString dataDirectory() const; + void setDataDirectory(const QString &path); + + Range<qint64> binarySegment() const; + void setBinarySegment(const Range<qint64> &r); + + void appendArchive(const QSharedPointer<Archive> &archive); + QSharedPointer<Archive> archiveByName(const QByteArray &name) const; + QVector< QSharedPointer<Archive> > archives() const; + + bool operator<(const Component &other) const; + bool operator==(const Component &other) const; + +private: + QByteArray m_name; + QVector<QSharedPointer<Archive> > m_archives; + mutable Range<qint64> m_binarySegment; + QString m_dataDirectory; +}; + + +class INSTALLER_EXPORT ComponentIndex +{ +public: + ComponentIndex(); + static ComponentIndex read(const QSharedPointer<QFile> &dev, qint64 offset); + void writeIndex(QIODevice *dev, qint64 offset) const; + void writeComponentData(QIODevice *dev, qint64 offset) const; + Component componentByName(const QByteArray &name) const; + void insertComponent(const Component &name); + void removeComponent(const QByteArray &name); + QVector<Component> components() const; + int componentCount() const; + +private: + QHash<QByteArray, Component> m_components; +}; +} + +namespace QInstaller { + +struct BinaryLayout +{ + QVector<Range<qint64> > metadataResourceSegments; + qint64 operationsStart; + qint64 operationsEnd; + qint64 resourceCount; + qint64 dataBlockSize; + qint64 magicMarker; + quint64 magicCookie; + qint64 indexSize; + qint64 endOfData; +}; + +class BinaryContentPrivate : public QSharedData +{ +public: + BinaryContentPrivate(const QString &path); + BinaryContentPrivate(const BinaryContentPrivate &other); + ~BinaryContentPrivate(); + + qint64 m_magicMarker; + qint64 m_dataBlockStart; + + QSharedPointer<QFile> m_appBinary; + QSharedPointer<QFile> m_binaryDataFile; + + QList<Operation *> m_performedOperations; + QList<QPair<QString, QString> > m_performedOperationsData; + + QVector<const uchar *> m_resourceMappings; + QVector<Range<qint64> > m_metadataResourceSegments; + + QInstallerCreator::ComponentIndex m_componentIndex; + QInstallerCreator::BinaryFormatEngineHandler m_binaryFormatEngineHandler; +}; + +class INSTALLER_EXPORT BinaryContent +{ + explicit BinaryContent(const QString &path); + +public: + virtual ~BinaryContent(); + + static BinaryContent readAndRegisterFromApplicationFile(); + static BinaryContent readAndRegisterFromBinary(const QString &path); + + static BinaryContent readFromApplicationFile(); + static BinaryContent readFromBinary(const QString &path); + + static BinaryLayout readBinaryLayout(QIODevice *const file, qint64 cookiePos); + + int registerPerformedOperations(); + OperationList performedOperations() const; + + qint64 magicMarker() const; + int registerEmbeddedQResources(); + QInstallerCreator::ComponentIndex componentIndex() const; + +private: + static void readBinaryData(BinaryContent &content, const QSharedPointer<QFile> &file, + const BinaryLayout &layout); + +private: + QSharedDataPointer<BinaryContentPrivate> d; +}; + +} + +#endif // BINARYFORMAT_H diff --git a/src/libs/installer/binaryformatengine.cpp b/src/libs/installer/binaryformatengine.cpp new file mode 100644 index 000000000..05affaf4b --- /dev/null +++ b/src/libs/installer/binaryformatengine.cpp @@ -0,0 +1,297 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "binaryformatengine.h" + +using namespace QInstallerCreator; + +namespace { + +class StringListIterator : public QAbstractFileEngineIterator +{ +public: + StringListIterator( const QStringList &list, QDir::Filters filters, const QStringList &nameFilters) + : QAbstractFileEngineIterator(filters, nameFilters), + list(list), + index(-1) + { + } + + bool hasNext() const + { + return index < list.size() - 1; + } + + QString next() + { + if(!hasNext()) + return QString(); + ++index; + return currentFilePath(); + } + + QString currentFileName() const + { + return index < 0 ? QString() : list[index]; + } + +private: + const QStringList list; + int index; +}; + +} // anon namespace + +BinaryFormatEngine::BinaryFormatEngine(const ComponentIndex &index, const QString &fileName) + : m_index(index) + , m_hasComponent(false) + , m_hasArchive(false) + , m_archive(0) +{ + setArchive(fileName); +} + +BinaryFormatEngine::~BinaryFormatEngine() +{ +} + +void BinaryFormatEngine::setArchive(const QString &file) +{ + m_fileNamePath = file; + + static const QChar sep = QLatin1Char('/'); + static const QString prefix = QLatin1String("installer://"); + Q_ASSERT(file.toLower().startsWith(prefix)); + + // cut the prefix + QString path = file.mid(prefix.length()); + while (path.endsWith(sep)) + path.chop(1); + + QString arch; + const QString comp = path.section(sep, 0, 0); + m_hasComponent = !comp.isEmpty(); + m_hasArchive = path.contains(sep); + if (m_hasArchive) + arch = path.section(sep, 1, 1); + + m_component = m_index.componentByName(comp.toUtf8()); + m_archive = m_component.archiveByName(arch.toUtf8()); +} + +/** + * \reimp + */ +void BinaryFormatEngine::setFileName(const QString &file) +{ + setArchive(file); +} + +/** + * \reimp + */ +bool BinaryFormatEngine::close() +{ + if (m_archive == 0) + return false; + + const bool result = m_archive->isOpen(); + m_archive->close(); + return result; +} + +/** + * \reimp + */ +bool BinaryFormatEngine::open(QIODevice::OpenMode mode) +{ + return m_archive == 0 ? false : m_archive->open(mode); +} + +/** + * \reimp + */ +qint64 BinaryFormatEngine::pos() const +{ + return m_archive == 0 ? 0 : m_archive->pos(); +} + +/** + * \reimp + */ +qint64 BinaryFormatEngine::read(char *data, qint64 maxlen) +{ + return m_archive == 0 ? -1 : m_archive->read(data, maxlen); +} + +/** + * \reimp + */ +bool BinaryFormatEngine::seek(qint64 offset) +{ + return m_archive == 0 ? false : m_archive->seek(offset); +} + +/** + * \reimp + */ +QString BinaryFormatEngine::fileName(FileName file) const +{ + switch(file) { + case BaseName: + return m_fileNamePath.section(QChar::fromLatin1('/'), -1, -1, QString::SectionSkipEmpty); + case PathName: + case AbsolutePathName: + case CanonicalPathName: + return m_fileNamePath.section(QChar::fromLatin1('/'), 0, -2, QString::SectionSkipEmpty); + case DefaultName: + case AbsoluteName: + case CanonicalName: + return m_fileNamePath; + default: + return QString(); + } +} + +/** + * \reimp + */ +bool BinaryFormatEngine::copy(const QString &newName) +{ + if (QFile::exists(newName)) + return false; + + QFile target(newName); + if (!target.open(QIODevice::WriteOnly)) + return false; + + qint64 bytesLeft = size(); + if (!open(QIODevice::ReadOnly)) + return false; + + char data[4096]; + while(bytesLeft > 0) { + const qint64 len = qMin<qint64>(bytesLeft, 4096); + const qint64 bytesRead = read(data, len); + if (bytesRead != len) { + close(); + return false; + } + const qint64 bytesWritten = target.write(data, len); + if (bytesWritten != len) { + close(); + return false; + } + bytesLeft -= len; + } + close(); + + return true; +} + +/** + * \reimp + */ +QAbstractFileEngine::FileFlags BinaryFormatEngine::fileFlags(FileFlags type) const +{ + FileFlags result; + if ((type & FileType) && m_archive != 0) + result |= FileType; + if ((type & DirectoryType) && !m_hasArchive) + result |= DirectoryType; + if ((type & ExistsFlag) && m_hasArchive && m_archive != 0) + result |= ExistsFlag; + if ((type & ExistsFlag) && !m_hasArchive && !m_component.name().isEmpty()) + result |= ExistsFlag; + + return result; +} + +/** + * \reimp + */ +QAbstractFileEngineIterator *BinaryFormatEngine::beginEntryList(QDir::Filters filters, const QStringList &filterNames) +{ + const QStringList entries = entryList(filters, filterNames); + return new StringListIterator(entries, filters, filterNames); +} + +/** + * \reimp + */ +QStringList BinaryFormatEngine::entryList(QDir::Filters filters, const QStringList &filterNames) const +{ + if (m_hasArchive) + return QStringList(); + + QStringList result; + + if (m_hasComponent && (filters & QDir::Files)) { + const QVector< QSharedPointer<Archive> > archives = m_component.archives(); + foreach (const QSharedPointer<Archive> &i, archives) + result.push_back(QString::fromUtf8(i->name())); + } + else if (!m_hasComponent && (filters & QDir::Dirs)) { + const QVector<Component> components = m_index.components(); + foreach (const Component &i, components) + result.push_back(QString::fromUtf8(i.name())); + } + + if (filterNames.isEmpty()) + return result; + + QList<QRegExp> regexps; + foreach (const QString &i, filterNames) + regexps.push_back(QRegExp(i, Qt::CaseInsensitive, QRegExp::Wildcard)); + + QStringList entries; + foreach (const QString &i, result) { + bool matched = false; + foreach (const QRegExp ®, regexps) { + matched = reg.exactMatch(i); + if (matched) + break; + } + if (matched) + entries.push_back(i); + } + + return entries; +} + +/** + * \reimp + */ +qint64 BinaryFormatEngine::size() const +{ + return m_archive == 0 ? 0 : m_archive->size(); +} diff --git a/src/libs/installer/binaryformatengine.h b/src/libs/installer/binaryformatengine.h new file mode 100644 index 000000000..d5ac18a45 --- /dev/null +++ b/src/libs/installer/binaryformatengine.h @@ -0,0 +1,78 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef BINARYFORMATENGINE_H +#define BINARYFORMATENGINE_H + +#include <QAbstractFileEngine> + +#include "binaryformat.h" + +namespace QInstallerCreator { + +class BinaryFormatEngine : public QAbstractFileEngine +{ +public: + BinaryFormatEngine(const ComponentIndex &index, const QString &fileName); + ~BinaryFormatEngine(); + + void setFileName(const QString &file); + + Iterator *beginEntryList(QDir::Filters filters, const QStringList &filterNames); + + bool copy(const QString &newName); + bool close(); + bool open(QIODevice::OpenMode mode); + qint64 pos() const; + qint64 read(char *data, qint64 maxlen); + bool seek(qint64 offset); + qint64 size() const; + + QString fileName(FileName file = DefaultName) const; + FileFlags fileFlags(FileFlags type = FileInfoAll) const; + QStringList entryList(QDir::Filters filters, const QStringList &filterNames) const; + +protected: + void setArchive(const QString &file); + +private: + const ComponentIndex m_index; + bool m_hasComponent; + bool m_hasArchive; + Component m_component; + QSharedPointer<Archive> m_archive; + QString m_fileNamePath; +}; + +} // namespace QInstallerCreator + +#endif diff --git a/src/libs/installer/binaryformatenginehandler.cpp b/src/libs/installer/binaryformatenginehandler.cpp new file mode 100644 index 000000000..2b1bbcad4 --- /dev/null +++ b/src/libs/installer/binaryformatenginehandler.cpp @@ -0,0 +1,116 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "binaryformatenginehandler.h" +#include "binaryformatengine.h" +#include "binaryformat.h" + +#include <QDebug> +#include <QFile> +#include <QFSFileEngine> + +using namespace QInstallerCreator; + +static BinaryFormatEngineHandler *s_instance = 0; + + +class BinaryFormatEngineHandler::Private +{ +public: + Private(const ComponentIndex &i) + : index(i) + { + } + + ComponentIndex index; +}; + +BinaryFormatEngineHandler::BinaryFormatEngineHandler(const ComponentIndex &index) + : d(new Private(index)) +{ + s_instance = this; +} + +BinaryFormatEngineHandler::BinaryFormatEngineHandler(const BinaryFormatEngineHandler &other) + : QAbstractFileEngineHandler(), + d(new Private(other.d->index)) +{ + s_instance = this; +} + +BinaryFormatEngineHandler::~BinaryFormatEngineHandler() +{ + if (s_instance == this) + s_instance = 0; + delete d; +} + +void BinaryFormatEngineHandler::setComponentIndex(const ComponentIndex &index) +{ + d->index = index; +} + +QAbstractFileEngine *BinaryFormatEngineHandler::create(const QString &fileName) const +{ + return fileName.startsWith(QLatin1String("installer://"), Qt::CaseInsensitive ) ? new BinaryFormatEngine(d->index, fileName) : 0; +} + +BinaryFormatEngineHandler *BinaryFormatEngineHandler::instance() +{ + return s_instance; +} + +void BinaryFormatEngineHandler::registerArchive(const QString &pathName, const QString &archive) +{ + static const QChar sep = QChar::fromLatin1('/'); + static const QString prefix = QString::fromLatin1("installer://"); + Q_ASSERT(pathName.toLower().startsWith(prefix)); + + // cut the prefix + QString path = pathName.mid(prefix.length()); + while (path.endsWith(sep)) + path.chop(1); + + const QString comp = path.section(sep, 0, 0); + const QString archiveName = path.section(sep, 1, 1); + + Component c = d->index.componentByName(comp.toUtf8()); + if (c.name().isEmpty()) + c.setName(comp.toUtf8()); + + QList< QSharedPointer<Archive> > registered; + QSharedPointer<Archive> newArchive(new Archive(archive)); + newArchive->setName(archiveName.toUtf8()); + registered.push_back(newArchive); + c.appendArchive(newArchive); + d->index.insertComponent(c); +} diff --git a/src/libs/installer/binaryformatenginehandler.h b/src/libs/installer/binaryformatenginehandler.h new file mode 100644 index 000000000..82269eda9 --- /dev/null +++ b/src/libs/installer/binaryformatenginehandler.h @@ -0,0 +1,66 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef BINARYFORMATENGINEHANDLER_H +#define BINARYFORMATENGINEHANDLER_H + +#include "installer_global.h" + +#include <QtCore/QAbstractFileEngineHandler> + + +namespace QInstallerCreator { + +class ComponentIndex; + +class INSTALLER_EXPORT BinaryFormatEngineHandler : public QAbstractFileEngineHandler +{ +public: + explicit BinaryFormatEngineHandler(const ComponentIndex &index); + BinaryFormatEngineHandler(const BinaryFormatEngineHandler &other); + ~BinaryFormatEngineHandler(); + QAbstractFileEngine *create(const QString &fileName) const; + + void setComponentIndex(const ComponentIndex &index); + + static BinaryFormatEngineHandler *instance(); + + void registerArchive(const QString &fileName, const QString &path); + +private: + class Private; + Private *const d; +}; + +} + +#endif diff --git a/src/libs/installer/component.cpp b/src/libs/installer/component.cpp new file mode 100644 index 000000000..4f8345836 --- /dev/null +++ b/src/libs/installer/component.cpp @@ -0,0 +1,1207 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ +#include "component.h" + +#include "errors.h" +#include "fileutils.h" +#include "fsengineclient.h" +#include "lib7z_facade.h" +#include "packagemanagercore.h" +#include "qinstallerglobal.h" +#include "messageboxhandler.h" + +#include <kdupdaterupdatesourcesinfo.h> +#include <kdupdaterupdateoperationfactory.h> + +#include <QtCore/QDirIterator> +#include <QtCore/QTranslator> + +#include <QtGui/QApplication> + +#include <QtUiTools/QUiLoader> + +#include <algorithm> + +using namespace QInstaller; + +static const QLatin1String scScript("Script"); +static const QLatin1String scDefault("Default"); +static const QLatin1String scAutoDependOn("AutoDependOn"); +static const QLatin1String scVirtual("Virtual"); +static const QLatin1String scInstalled("Installed"); +static const QLatin1String scUpdateText("UpdateText"); +static const QLatin1String scUninstalled("Uninstalled"); +static const QLatin1String scCurrentState("CurrentState"); +static const QLatin1String scForcedInstallation("ForcedInstallation"); + +static QRegExp scCommaRegExp(QLatin1String("\\b(,|, )\\b")); + +/*! + \class QInstaller::Component + Component describes a component within the installer. +*/ + +/*! + Constructor. Creates a new Component inside of \a installer. +*/ +Component::Component(PackageManagerCore *core) + : d(new ComponentPrivate(core, this)) +{ + setPrivate(d); + + connect(this, SIGNAL(valueChanged(QString, QString)), this, SLOT(updateModelData(QString, QString))); +} + +/*! + Destroys the Component. +*/ +Component::~Component() +{ + if (parentComponent() != 0) + d->m_parentComponent->d->m_allChildComponents.removeAll(this); + + //why can we delete all create operations if the component gets destroyed + if (!d->m_newlyInstalled) + qDeleteAll(d->m_operations); + + //we need to copy the list, because we are changing it with removeAll at top + //(this made the iterators broken in the past) + QList<Component*> copiedChildrenList = d->m_allChildComponents; + copiedChildrenList.detach(); //this makes it a real copy + + qDeleteAll(copiedChildrenList); + delete d; + d = 0; +} + +void Component::loadDataFromPackage(const LocalPackage &package) +{ + setValue(scName, package.name); + // pixmap ??? + setValue(scDisplayName, package.title); + setValue(scDescription, package.description); + setValue(scVersion, package.version); + setValue(scInheritVersion, package.inheritVersionFrom); + setValue(scInstalledVersion, package.version); + setValue(QLatin1String("LastUpdateDate"), package.lastUpdateDate.toString()); + setValue(QLatin1String("InstallDate"), package.installDate.toString()); + setValue(scUncompressedSize, QString::number(package.uncompressedSize)); + + QString dependstr; + foreach (const QString &val, package.dependencies) + dependstr += val + QLatin1String(","); + + if (package.dependencies.count() > 0) + dependstr.chop(1); + setValue(scDependencies, dependstr); + + setValue(scForcedInstallation, package.forcedInstallation ? scTrue : scFalse); + if (package.forcedInstallation & !PackageManagerCore::noForceInstallation()) { + setEnabled(false); + setCheckable(false); + setCheckState(Qt::Checked); + } + setValue(scVirtual, package.virtualComp ? scTrue : scFalse); + setValue(scCurrentState, scInstalled); +} + +void Component::loadDataFromPackage(const Package &package) +{ + Q_ASSERT(&package); + Q_ASSERT(!package.name().isEmpty()); + + setValue(scName, package.data(scName).toString()); + setValue(scDisplayName, package.data(scDisplayName).toString()); + setValue(scDescription, package.data(scDescription).toString()); + setValue(scDefault, package.data(scDefault).toString()); + setValue(scAutoDependOn, package.data(scAutoDependOn).toString()); + setValue(scCompressedSize, QString::number(package.compressedSize())); + setValue(scUncompressedSize, QString::number(package.uncompressedSize())); + setValue(scRemoteVersion, package.data(scRemoteVersion).toString()); + setValue(scInheritVersion, package.data(scInheritVersion).toString()); + setValue(scDependencies, package.data(scDependencies).toString()); + setValue(scDownloadableArchives, package.data(scDownloadableArchives).toString()); + setValue(scVirtual, package.data(scVirtual).toString()); + setValue(scSortingPriority, package.data(scSortingPriority).toString()); + + setValue(scEssential, package.data(scEssential).toString()); + setValue(scUpdateText, package.data(scUpdateText).toString()); + setValue(scNewComponent, package.data(scNewComponent).toString()); + setValue(scRequiresAdminRights, package.data(scRequiresAdminRights).toString()); + + setValue(scScript, package.data(scScript).toString()); + setValue(scReplaces, package.data(scReplaces).toString()); + setValue(scReleaseDate, package.data(scReleaseDate).toString()); + + QString forced = package.data(scForcedInstallation, scFalse).toString().toLower(); + if (PackageManagerCore::noForceInstallation()) + forced = scFalse; + setValue(scForcedInstallation, forced); + if (forced == scTrue) { + setEnabled(false); + setCheckable(false); + setCheckState(Qt::Checked); + } + + setLocalTempPath(QInstaller::pathFromUrl(package.sourceInfo().url)); + const QStringList uis = package.data(QLatin1String("UserInterfaces")).toString().split(scCommaRegExp, + QString::SkipEmptyParts); + if (!uis.isEmpty()) + loadUserInterfaces(QDir(QString::fromLatin1("%1/%2").arg(localTempPath(), name())), uis); + + const QStringList qms = package.data(QLatin1String("Translations")).toString().split(scCommaRegExp, + QString::SkipEmptyParts); + if (!qms.isEmpty()) + loadTranslations(QDir(QString::fromLatin1("%1/%2").arg(localTempPath(), name())), qms); + + QHash<QString, QVariant> licenseHash = package.data(QLatin1String("Licenses")).toHash(); + if (!licenseHash.isEmpty()) + loadLicenses(QString::fromLatin1("%1/%2/").arg(localTempPath(), name()), licenseHash); +} + +quint64 Component::updateUncompressedSize() +{ + quint64 size = 0; + + if (isSelected()) + size = (quint64)value(scUncompressedSize).toDouble(); + + foreach (Component* comp, d->m_allChildComponents) + size += comp->updateUncompressedSize(); + + setValue(scUncompressedSizeSum, QString::number(size)); + setData(uncompressedSize(), UncompressedSize); + + return size; +} + +QString Component::uncompressedSize() const +{ + double size = value(scUncompressedSizeSum).toDouble(); + if (size < 1000.0) + return tr("%L1 Bytes").arg(size); + size /= 1024.0; + if (size < 1000.0) + return tr("%L1 kBytes").arg(size, 0, 'f', 2); + size /= 1024.0; + if (size < 1000.0) + return tr("%L1 MBytes").arg(size, 0, 'f', 2); + size /= 1024.0; + + return tr("%L1 GBytes").arg(size, 0, 'f', 2); +} + +void Component::markAsPerformedInstallation() +{ + d->m_newlyInstalled = true; +} + +/*! + \property Component::removeBeforeUpdate + Specifies whether this component gets removed by the installer system before it gets updated. Get this + property's value by using %removeBeforeUpdate(), and set it using %setRemoveBeforeUpdate(). The default + value is true. +*/ +bool Component::removeBeforeUpdate() const +{ + return d->m_removeBeforeUpdate; +} + +void Component::setRemoveBeforeUpdate(bool removeBeforeUpdate) +{ + d->m_removeBeforeUpdate = removeBeforeUpdate; +} + +/* + Returns a key/value based hash of all variables set for this component. +*/ +QHash<QString,QString> Component::variables() const +{ + return d->m_vars; +} + +/*! + Returns the value of variable name \a key. + If \a key is not known yet, \a defaultValue is returned. +*/ +QString Component::value(const QString &key, const QString &defaultValue) const +{ + return d->m_vars.value(key, defaultValue); +} + +/*! + Sets the value of the variable with \a key to \a value. +*/ +void Component::setValue(const QString &key, const QString &value) +{ + if (d->m_vars.value(key) == value) + return; + + if (key == scName) + d->m_componentName = value; + + d->m_vars[key] = value; + emit valueChanged(key, value); +} + +/*! + Returns the installer this component belongs to. +*/ +PackageManagerCore *Component::packageManagerCore() const +{ + return d->m_core; +} + +/*! + Returns the parent of this component. If this component is com.nokia.sdk.qt, its + parent is com.nokia.sdk, as far as this exists. +*/ +Component *Component::parentComponent() const +{ + return d->m_parentComponent; +} + +/*! + Appends \a component as a child of this component. If \a component already has a parent, + it is removed from the previous parent. +*/ +void Component::appendComponent(Component *component) +{ + if (!component->isVirtual()) { + d->m_childComponents.append(component); + std::sort(d->m_childComponents.begin(), d->m_childComponents.end(), Component::SortingPriorityLessThan()); + } else { + d->m_virtualChildComponents.append(component); + } + + d->m_allChildComponents = d->m_childComponents + d->m_virtualChildComponents; + if (Component *parent = component->parentComponent()) + parent->removeComponent(component); + component->d->m_parentComponent = this; + setTristate(d->m_childComponents.count() > 0); +} + +/*! + Removes \a component if it is a child of this component. The component object still exists after the + function returns. It's up to the caller to delete the passed \a component. +*/ +void Component::removeComponent(Component *component) +{ + if (component->parentComponent() == this) { + component->d->m_parentComponent = 0; + d->m_childComponents.removeAll(component); + d->m_virtualChildComponents.removeAll(component); + d->m_allChildComponents = d->m_childComponents + d->m_virtualChildComponents; + } +} + +/*! + Returns a list of child components. If \a recursive is set to true, the returned list + contains not only the direct children, but all ancestors. Note: The returned list does include ALL + children, non virtual components as well as virtual components. +*/ +QList<Component*> Component::childComponents(bool recursive, RunMode runMode) const +{ + QList<Component*> result; + if (runMode == UpdaterMode) + return result; + + if (!recursive) + return d->m_allChildComponents; + + foreach (Component *component, d->m_allChildComponents) { + result.append(component); + result += component->childComponents(true, runMode); + } + return result; +} + +/*! + Contains this component's name (unique identifier). +*/ +QString Component::name() const +{ + return d->m_componentName; +} + +/*! + Contains this component's display name (as visible to the user). +*/ +QString Component::displayName() const +{ + return value(scDisplayName); +} + +void Component::loadComponentScript() +{ + const QString script = value(scScript); + if (!localTempPath().isEmpty() && !script.isEmpty()) + loadComponentScript(QString::fromLatin1("%1/%2/%3").arg(localTempPath(), name(), script)); +} + +/*! + Loads the script at \a fileName into this component's script engine. The installer and all its + components as well as other useful stuff are being exported into the script. + Read \link componentscripting Component Scripting \endlink for details. + \throws Error when either the script at \a fileName couldn't be opened, or the QScriptEngine + couldn't evaluate the script. +*/ +void Component::loadComponentScript(const QString &fileName) +{ + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) { + throw Error(tr("Could not open the requested script file at %1: %2.").arg(fileName, file.errorString())); + } + + d->scriptEngine()->evaluate(QLatin1String(file.readAll()), fileName); + if (d->scriptEngine()->hasUncaughtException()) { + throw Error(tr("Exception while loading the component script: %1") + .arg(uncaughtExceptionString(d->scriptEngine()/*, QFileInfo(file).absoluteFilePath()*/))); + } + + const QList<Component*> components = d->m_core->availableComponents(); + QScriptValue comps = d->scriptEngine()->newArray(components.count()); + for (int i = 0; i < components.count(); ++i) + comps.setProperty(i, d->scriptEngine()->newQObject(components[i])); + + d->scriptEngine()->globalObject().property(QLatin1String("installer")) + .setProperty(QLatin1String("components"), comps); + + QScriptValue comp = d->scriptEngine()->evaluate(QLatin1String("Component"), fileName); + if (!d->scriptEngine()->hasUncaughtException()) { + d->m_scriptComponent = comp; + d->m_scriptComponent.construct(); + } + + //evaluate("Component") and construct can have an exception + if (d->scriptEngine()->hasUncaughtException()) { + throw Error(tr("Exception while loading the component script: %1") + .arg(uncaughtExceptionString(d->scriptEngine(), QFileInfo(file).absoluteFilePath()))); + } + + emit loaded(); + languageChanged(); + + //Solves a freeze seen on updater/ package manger restart. + QCoreApplication::processEvents(); +} + +/*! + \internal + Calls the script method \link retranslateUi() \endlink, if any. This is done whenever a + QTranslator file is being loaded. +*/ +void Component::languageChanged() +{ + callScriptMethod(QLatin1String("retranslateUi")); +} + +/*! + Tries to call the method with \a name within the script and returns the result. If the method + doesn't exist, an invalid result is returned. If the method has an uncaught exception, its + string representation is thrown as an Error exception. + + \note The method is not called, if the current script context is the same method, to avoid + infinite recursion. +*/ +QScriptValue Component::callScriptMethod(const QString &methodName, const QScriptValueList &arguments) const +{ + if (!d->m_unexistingScriptMethods.value(methodName, true)) + return QScriptValue(); + + // don't allow such a recursion + if (d->scriptEngine()->currentContext()->backtrace().first().startsWith(methodName)) + return QScriptValue(); + + QScriptValue method = d->m_scriptComponent.property(QString::fromLatin1("prototype")) + .property(methodName); + if (!method.isValid()) // this marks the method to be called not any longer + d->m_unexistingScriptMethods[methodName] = false; + + const QScriptValue result = method.call(d->m_scriptComponent, arguments); + if (!result.isValid()) + return result; + + if (d->scriptEngine()->hasUncaughtException()) + throw Error(uncaughtExceptionString(d->scriptEngine()/*, name()*/)); + + return result; +} + +/*! + Loads the translations matching the name filters \a qms inside \a directory. Only translations + with a \link QFileInfo::baseName() baseName \endlink matching the current locales \link + QLocale::name() name \endlink are loaded. + Read \ref componenttranslation for details. +*/ +void Component::loadTranslations(const QDir &directory, const QStringList &qms) +{ + QDirIterator it(directory.path(), qms, QDir::Files); + while (it.hasNext()) { + const QString filename = it.next(); + if (QFileInfo(filename).baseName().toLower() != QLocale().name().toLower()) + continue; + + QScopedPointer<QTranslator> translator(new QTranslator(this)); + if (!translator->load(filename)) + throw Error(tr("Could not open the requested translation file at %1").arg(filename)); + qApp->installTranslator(translator.take()); + } +} + +/*! + Loads the user interface files matching the name filters \a uis inside \a directory. The loaded + interface can be accessed via userInterfaces by using the class name set in the ui file. + Read \ref componentuserinterfaces for details. +*/ +void Component::loadUserInterfaces(const QDir &directory, const QStringList &uis) +{ + if (QApplication::type() == QApplication::Tty) + return; + + QDirIterator it(directory.path(), uis, QDir::Files); + while (it.hasNext()) { + QFile file(it.next()); + if (!file.open(QIODevice::ReadOnly)) { + throw Error(tr("Could not open the requested UI file at %1: %2").arg(it.fileName(), + file.errorString())); + } + + static QUiLoader loader; + loader.setTranslationEnabled(true); + loader.setLanguageChangeEnabled(true); + QWidget *const w = loader.load(&file, MessageBoxHandler::currentBestSuitParent()); + d->m_userInterfaces.insert(w->objectName(), w); + } +} + +/*! + Loads the text of the Licenses contained in the licenseHash. + This is saved into a new hash containing the filename and the text of that file. +*/ +void Component::loadLicenses(const QString &directory, const QHash<QString, QVariant> &licenseHash) +{ + QHash<QString, QVariant>::const_iterator it; + for (it = licenseHash.begin(); it != licenseHash.end(); ++it) { + const QString &fileName = it.value().toString(); + QFileInfo fileInfo(fileName); + QFile file(QString::fromLatin1("%1%2_%3.%4").arg(directory, fileInfo.baseName(), + QLocale().name().toLower(), fileInfo.completeSuffix())); + if (!file.open(QIODevice::ReadOnly)) { + // No translated license, use untranslated file + qDebug("Unable to open translated license file. Using untranslated fallback."); + file.setFileName(directory + fileName); + if (!file.open(QIODevice::ReadOnly)) { + throw Error(tr("Could not open the requested license file at %1: %2").arg(fileName, + file.errorString())); + } + } + d->m_licenses.insert(it.key(), qMakePair(fileName, QTextStream(&file).readAll())); + } +} + +/*! + Contains a list of all user interface class names known to this component. +*/ +QStringList Component::userInterfaces() const +{ + return d->m_userInterfaces.keys(); +} + +QHash<QString, QPair<QString, QString> > Component::licenses() const +{ + return d->m_licenses; +} + +/*! + Returns the QWidget created for \a name or 0 if the widget already has been deleted or cannot be found. +*/ +QWidget *Component::userInterface(const QString &name) const +{ + return d->m_userInterfaces.value(name).data(); +} + +/*! + Creates all operations needed to install this component's \a path. \a path is a full qualified + filename including the component's name. This methods gets called from + Component::createOperationsForArchive. You can override this method by providing a method with + the same name in the component script. + + \note RSA signature files are omitted by this method. + \note If you call this method from a script, it won't call the scripts method with the same name. + + The default implementation is recursively creating Copy and Mkdir operations for all files + and folders within \a path. +*/ +void Component::createOperationsForPath(const QString &path) +{ + const QFileInfo fi(path); + + // don't copy over a checksum file + if (fi.suffix() == QLatin1String("sha1") && QFileInfo(fi.dir(), fi.completeBaseName()).exists()) + return; + + // the script can override this method + if (callScriptMethod(QLatin1String("createOperationsForPath"), QScriptValueList() << path).isValid()) + return; + + QString target; + static const QString zipPrefix = QString::fromLatin1("7z://installer://"); + // if the path is an archive, remove the archive file name from the target path + if (path.startsWith(zipPrefix)) { + target = path.mid(zipPrefix.length() + name().length() + 1); // + 1 for the / + const int nextSlash = target.indexOf(QLatin1Char('/')); + if (nextSlash != -1) + target = target.mid(nextSlash); + else + target.clear(); + target.prepend(QLatin1String("@TargetDir@")); + } else { + static const QString prefix = QString::fromLatin1("installer://"); + target = QString::fromLatin1("@TargetDir@%1").arg(path.mid(prefix.length() + name().length())); + } + + if (fi.isFile()) { + static const QString copy = QString::fromLatin1("Copy"); + addOperation(copy, fi.filePath(), target); + } else if (fi.isDir()) { + qApp->processEvents(); + static const QString mkdir = QString::fromLatin1("Mkdir"); + addOperation(mkdir, target); + + QDirIterator it(fi.filePath()); + while (it.hasNext()) + createOperationsForPath(it.next()); + } +} + +/*! + Creates all operations needed to install this component's \a archive. This method gets called + from Component::createOperations. You can override this method by providing a method with the + same name in the component script. + + \note If you call this method from a script, it won't call the scripts method with the same name. + + The default implementation calls createOperationsForPath for everything contained in the archive. + If \a archive is a compressed archive known to the installer system, an Extract operation is + created, instead. +*/ +void Component::createOperationsForArchive(const QString &archive) +{ + // the script can override this method + if (callScriptMethod(QLatin1String("createOperationsForArchive"), QScriptValueList() << archive).isValid()) + return; + + const QFileInfo fi(QString::fromLatin1("installer://%1/%2").arg(name(), archive)); + const bool isZip = Lib7z::isSupportedArchive(fi.filePath()); + + if (isZip) { + // archives get completely extracted per default (if the script isn't doing other stuff) + addOperation(QLatin1String("Extract"), fi.filePath(), QLatin1String("@TargetDir@")); + } else { + createOperationsForPath(fi.filePath()); + } +} + +void Component::beginInstallation() +{ + // the script can override this method + if (callScriptMethod(QLatin1String("beginInstallation")).isValid()) { + return; + } +} + + +/*! + Creates all operations needed to install this component. + You can override this method by providing a method with the same name in the component script. + + \note If you call this method from a script, it won't call the scripts method with the same name. + + The default implementation calls createOperationsForArchive for all archives in this component. +*/ +void Component::createOperations() +{ + // the script can override this method + if (callScriptMethod(QLatin1String("createOperations")).isValid()) { + d->m_operationsCreated = true; + return; + } + + foreach (const QString &archive, archives()) + createOperationsForArchive(archive); + + d->m_operationsCreated = true; +} + +/*! + Registers the file or directory at \a path for being removed when this component gets uninstalled. + In case of a directory, this will be recursive. If \a wipe is set to true, the directory will + also be deleted if it contains changes done by the user after installation. +*/ +void Component::registerPathForUninstallation(const QString &path, bool wipe) +{ + d->m_pathesForUninstallation.append(qMakePair(path, wipe)); +} + +/*! + Returns the list of paths previously registered for uninstallation with + #registerPathForUninstallation. +*/ +QList<QPair<QString, bool> > Component::pathesForUninstallation() const +{ + return d->m_pathesForUninstallation; +} + +/*! + Contains the names of all archives known to this component. This does not contain archives added + with #addDownloadableArchive. +*/ +QStringList Component::archives() const +{ + return QDir(QString::fromLatin1("installer://%1/").arg(name())).entryList(); +} + +/*! + Adds the archive \a path to this component. This can only be called when this component was + downloaded from an online repository. When adding \a path, it will be downloaded from the + repository when the installation starts. + + Read \ref sec_repogen for details. \sa fromOnlineRepository +*/ +void Component::addDownloadableArchive(const QString &path) +{ + Q_ASSERT(isFromOnlineRepository()); + + const QString versionPrefix = value(scRemoteVersion); + qDebug() << "addDownloadable" << path; + d->m_downloadableArchives.append(versionPrefix + path); +} + +/*! + Removes the archive \a path previously added via addDownloadableArchive from this component. + This can only be called when this component was downloaded from an online repository. + + Read \ref sec_repogen for details. +*/ +void Component::removeDownloadableArchive(const QString &path) +{ + Q_ASSERT(isFromOnlineRepository()); + d->m_downloadableArchives.removeAll(path); +} + +/*! + Returns the archives to be downloaded from the online repository before installation. +*/ +QStringList Component::downloadableArchives() const +{ + return d->m_downloadableArchives; +} + +/*! + Adds a request for quitting the process @p process before installing/updating/uninstalling the + component. +*/ +void Component::addStopProcessForUpdateRequest(const QString &process) +{ + d->m_stopProcessForUpdateRequests.append(process); +} + +/*! + Removes the request for quitting the process @p process again. +*/ +void Component::removeStopProcessForUpdateRequest(const QString &process) +{ + d->m_stopProcessForUpdateRequests.removeAll(process); +} + +/*! + Convenience: Add/remove request depending on @p requested (add if @p true, remove if @p false). +*/ +void Component::setStopProcessForUpdateRequest(const QString &process, bool requested) +{ + if (requested) + addStopProcessForUpdateRequest(process); + else + removeStopProcessForUpdateRequest(process); +} + +/*! + The list of processes this component needs to be closed before installing/updating/uninstalling +*/ +QStringList Component::stopProcessForUpdateRequests() const +{ + return d->m_stopProcessForUpdateRequests; +} + +/*! + Returns the operations needed to install this component. If autoCreateOperations is true, + createOperations is called, if no operations have been auto-created yet. +*/ +OperationList Component::operations() const +{ + if (d->m_autoCreateOperations && !d->m_operationsCreated) { + const_cast<Component*>(this)->createOperations(); + + if (!d->m_minimumProgressOperation) { + d->m_minimumProgressOperation = KDUpdater::UpdateOperationFactory::instance() + .create(QLatin1String("MinimumProgress")); + d->m_operations.append(d->m_minimumProgressOperation); + } + + if (!d->m_licenses.isEmpty()) { + d->m_licenseOperation = KDUpdater::UpdateOperationFactory::instance() + .create(QLatin1String("License")); + d->m_licenseOperation->setValue(QLatin1String("installer"), QVariant::fromValue(d->m_core)); + + QVariantMap licenses; + const QList<QPair<QString, QString> > values = d->m_licenses.values(); + for (int i = 0; i < values.count(); ++i) + licenses.insert(values.at(i).first, values.at(i).second); + d->m_licenseOperation->setValue(QLatin1String("licenses"), licenses); + d->m_operations.append(d->m_licenseOperation); + } + } + return d->m_operations; +} + +/*! + Adds \a operation to the list of operations needed to install this component. +*/ +void Component::addOperation(Operation *operation) +{ + d->m_operations.append(operation); + if (FSEngineClientHandler::instance().isActive()) + operation->setValue(QLatin1String("admin"), true); +} + +/*! + Adds \a operation to the list of operations needed to install this component. \a operation + is executed with elevated rights. +*/ +void Component::addElevatedOperation(Operation *operation) +{ + if (value(scRequiresAdminRights, scFalse) != scTrue) { + qWarning() << QString::fromLatin1("component %1 uses addElevatedOperation in the script, but it doesn't" + "have the needed RequiresAdminRights tag").arg(name()); + } + addOperation(operation); + operation->setValue(QLatin1String("admin"), true); +} + +bool Component::operationsCreatedSuccessfully() const +{ + return d->m_operationsCreatedSuccessfully; +} + +Operation *Component::createOperation(const QString &operation, const QString ¶meter1, + const QString ¶meter2, const QString ¶meter3, const QString ¶meter4, const QString ¶meter5, + const QString ¶meter6, const QString ¶meter7, const QString ¶meter8, const QString ¶meter9, + const QString ¶meter10) +{ + Operation *op = KDUpdater::UpdateOperationFactory::instance().create(operation); + if (op == 0) { + const QMessageBox::StandardButton button = + MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), + QLatin1String("OperationDoesNotExistError"), tr("Error"), tr("Error: Operation %1 does not exist") + .arg(operation), QMessageBox::Abort | QMessageBox::Ignore); + if (button == QMessageBox::Abort) + d->m_operationsCreatedSuccessfully = false; + return op; + } + + if (op->name() == QLatin1String("Delete")) + op->setValue(QLatin1String("performUndo"), false); + op->setValue(QLatin1String("installer"), qVariantFromValue(d->m_core)); + + QStringList arguments; + if (!parameter1.isNull()) + arguments.append(parameter1); + if (!parameter2.isNull()) + arguments.append(parameter2); + if (!parameter3.isNull()) + arguments.append(parameter3); + if (!parameter4.isNull()) + arguments.append(parameter4); + if (!parameter5.isNull()) + arguments.append(parameter5); + if (!parameter6.isNull()) + arguments.append(parameter6); + if (!parameter7.isNull()) + arguments.append(parameter7); + if (!parameter8.isNull()) + arguments.append(parameter8); + if (!parameter9.isNull()) + arguments.append(parameter9); + if (!parameter10.isNull()) + arguments.append(parameter10); + op->setArguments(d->m_core->replaceVariables(arguments)); + + return op; +} +/*! + Creates and adds an installation operation for \a operation. Add any number of \a parameter1, + \a parameter2, \a parameter3, \a parameter4, \a parameter5 and \a parameter6. The contents of + the parameters get variables like "@TargetDir@" replaced with their values, if contained. + \sa installeroperations +*/ +bool Component::addOperation(const QString &operation, const QString ¶meter1, const QString ¶meter2, + const QString ¶meter3, const QString ¶meter4, const QString ¶meter5, const QString ¶meter6, + const QString ¶meter7, const QString ¶meter8, const QString ¶meter9, const QString ¶meter10) +{ + if (Operation *op = createOperation(operation, parameter1, parameter2, parameter3, parameter4, parameter5, + parameter6, parameter7, parameter8, parameter9, parameter10)) { + addOperation(op); + return true; + } + + return false; +} + +/*! + Creates and adds an installation operation for \a operation. Add any number of \a parameter1, + \a parameter2, \a parameter3, \a parameter4, \a parameter5 and \a parameter6. The contents of + the parameters get variables like "@TargetDir@" replaced with their values, if contained. + \a operation is executed with elevated rights. + \sa installeroperations +*/ +bool Component::addElevatedOperation(const QString &operation, const QString ¶meter1, + const QString ¶meter2, const QString ¶meter3, const QString ¶meter4, const QString ¶meter5, + const QString ¶meter6, const QString ¶meter7, const QString ¶meter8, const QString ¶meter9, + const QString ¶meter10) +{ + if (Operation *op = createOperation(operation, parameter1, parameter2, parameter3, parameter4, parameter5, + parameter6, parameter7, parameter8, parameter9, parameter10)) { + addElevatedOperation(op); + return true; + } + + return false; +} + +/*! + Specifies whether operations should be automatically created when the installation starts. This + would be done by calling #createOperations. If you set this to false, it's completely up to the + component's script to create all operations. +*/ +bool Component::autoCreateOperations() const +{ + return d->m_autoCreateOperations; +} + +void Component::setAutoCreateOperations(bool autoCreateOperations) +{ + d->m_autoCreateOperations = autoCreateOperations; +} + +bool Component::isVirtual() const +{ + return value(scVirtual, scFalse).toLower() == scTrue; +} + +/*! + \property Component::selected + Specifies whether this component is selected for installation. Get this property's value by using + %isSelected(), and set it using %setSelected(). +*/ +bool Component::isSelected() const +{ + return checkState() != Qt::Unchecked; +} + +bool Component::forcedInstallation() const +{ + return value(scForcedInstallation, scFalse).toLower() == scTrue; +} + +/*! + Marks the component for installation. Emits the selectedChanged() signal if the check state changes. +*/ +void Component::setSelected(bool selected) +{ + Q_UNUSED(selected) + qDebug() << Q_FUNC_INFO << QString::fromLatin1("on \"%1\" is deprecated!!!").arg(d->m_componentName); +} + +void Component::addDependency(const QString &newDependency) +{ + QString oldDependencies = value(scDependencies); + if (oldDependencies.isEmpty()) + setValue(scDependencies, newDependency); + else + setValue(scDependencies, oldDependencies + QLatin1String(", ") + newDependency); +} + + +/*! + Contains this component dependencies. + Read \ref componentdependencies for details. +*/ +QStringList Component::dependencies() const +{ + return value(scDependencies).split(scCommaRegExp, QString::SkipEmptyParts); +} + +QStringList Component::autoDependencies() const +{ + QStringList autoDependencyStringList = + value(scAutoDependOn).split(scCommaRegExp, QString::SkipEmptyParts); + autoDependencyStringList.removeAll(QLatin1String("script")); + return autoDependencyStringList; +} + +/*! + Set's the components state to installed. +*/ +void Component::setInstalled() +{ + setValue(scCurrentState, scInstalled); +} + +/*! + Determines if the component comes as an auto dependency. Returns true if the component needs to be + installed. +*/ +bool Component::isAutoDependOn(const QSet<QString> &componentsToInstall) const +{ + // If there is no auto depend on value or the value is empty, we have nothing todo. The component does + // not need to be installed as an auto dependency. + QStringList autoDependOnList = autoDependencies(); + if (autoDependOnList.isEmpty()) + return false; + + // The script can override this method and determines if the component needs to be installed. + if (autoDependOnList.first().compare(QLatin1String("script"), Qt::CaseInsensitive) == 0) { + QScriptValue valueFromScript; + try { + valueFromScript = callScriptMethod(QLatin1String("isAutoDependOn")); + } catch (const Error &error) { + MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), + QLatin1String("isAutoDependOnError"), tr("Can't resolve isAutoDependOn in %1" + ).arg(name()), error.message()); + return false; + } + + if (valueFromScript.isValid()) + return valueFromScript.toBool(); + qDebug() << "value from script is not valid"; + return false; + } + + QSet<QString> components = componentsToInstall; + const QStringList installedPackages = d->m_core->localInstalledPackages().keys(); + foreach (const QString &name, installedPackages) + components.insert(name); + + foreach (const QString &component, components) { + autoDependOnList.removeAll(component); + if (autoDependOnList.isEmpty()) { + // If all components in the isAutoDependOn field are already installed or selected for + // installation, this component needs to be installed as well. + return true; + } + } + + return false; +} + +/*! + Determines if the component is a default one. +*/ +bool Component::isDefault() const +{ + // the script can override this method + if (value(scDefault).compare(QLatin1String("script"), Qt::CaseInsensitive) == 0) { + QScriptValue valueFromScript; + try { + valueFromScript = callScriptMethod(QLatin1String("isDefault")); + } catch (const Error &error) { + MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), + QLatin1String("isDefaultError"), tr("Can't resolve isDefault in %1").arg(name()), + error.message()); + return false; + } + if (valueFromScript.isValid()) + return valueFromScript.toBool(); + qDebug() << "value from script is not valid"; + return false; + } + + return value(scDefault).compare(scTrue, Qt::CaseInsensitive) == 0; +} + +/*! + Determines if the component is installed. +*/ +bool Component::isInstalled() const +{ + return scInstalled == value(scCurrentState); +} + +/*! + Determines if the user wants to install the component +*/ +bool Component::installationRequested() const +{ + return !isInstalled() && isSelected(); +} + +/*! + Sets a flag that the core found an update +*/ +void Component::setUpdateAvailable(bool isUpdateAvailable) +{ + d->m_updateIsAvailable = isUpdateAvailable; +} + +/*! + Determines if the user wants to install the update for this component +*/ +bool Component::updateRequested() +{ + return d->m_updateIsAvailable && isSelected(); +} + +/*! + Returns true if that component will be changed (update/installation/uninstallation) +*/ +bool Component::componentChangeRequested() +{ + return updateRequested() || installationRequested() || uninstallationRequested(); +} + + +/*! + Sets the component state to uninstalled. +*/ +void Component::setUninstalled() +{ + setValue(scCurrentState, scUninstalled); +} + +/*! + Determines if the component is uninstalled. +*/ +bool Component::isUninstalled() const +{ + return scUninstalled == value(scCurrentState); +} + +/*! + Determines if the user wants to uninstall the component. +*/ +bool Component::uninstallationRequested() const +{ + if (packageManagerCore()->isUpdater()) + return false; + return isInstalled() && !isSelected(); +} + +/*! + \property Component::fromOnlineRepository + + Determines whether this component has been loaded from an online repository. Get this property's + value by using %isFromOnlineRepository. \sa addDownloadableArchive +*/ +bool Component::isFromOnlineRepository() const +{ + return !repositoryUrl().isEmpty(); +} + +/*! + Contains the repository Url this component is downloaded from. + When this component is not downloaded from an online repository, returns an empty #QUrl. +*/ +QUrl Component::repositoryUrl() const +{ + return d->m_repositoryUrl; +} + +/*! + Sets this components #repositoryUrl. +*/ +void Component::setRepositoryUrl(const QUrl &url) +{ + d->m_repositoryUrl = url; +} + + +QString Component::localTempPath() const +{ + return d->m_localTempPath; +} + +void Component::setLocalTempPath(const QString &tempLocalPath) +{ + d->m_localTempPath = tempLocalPath; +} + +void Component::updateModelData(const QString &key, const QString &data) +{ + if (key == scVirtual) { + if (data.toLower() == scTrue) + setData(d->m_core->virtualComponentsFont(), Qt::FontRole); + } + + if (key == scRemoteDisplayVersion) + setData(data, RemoteDisplayVersion); + + if (key == scDisplayName) + setData(data, Qt::DisplayRole); + + if (key == scDisplayVersion) + setData(data, LocalDisplayVersion); + + if (key == scUncompressedSize) + setData(uncompressedSize(), UncompressedSize); + + const QString &updateInfo = value(scUpdateText); + if (!d->m_core->isUpdater() || updateInfo.isEmpty()) { + setData(QLatin1String("<html><body>") + value(scDescription) + QLatin1String("</body></html>"), + Qt::ToolTipRole); + } else { + setData(value(scDescription) + QLatin1String("<br><br>") + tr("Update Info: ") + updateInfo, Qt::ToolTipRole); + } +} + + +QDebug QInstaller::operator<<(QDebug dbg, Component *component) +{ + dbg << "component: " << component->name() << "\n"; + dbg << "\tisSelected: \t" << component->isSelected() << "\n"; + dbg << "\tisInstalled: \t" << component->isInstalled() << "\n"; + dbg << "\tisUninstalled: \t" << component->isUninstalled() << "\n"; + dbg << "\tupdateRequested: \t" << component->updateRequested() << "\n"; + dbg << "\tinstallationRequested: \t" << component->installationRequested() << "\n"; + dbg << "\tuninstallationRequested: \t" << component->uninstallationRequested() << "\n"; + return dbg; +} diff --git a/src/libs/installer/component.h b/src/libs/installer/component.h new file mode 100644 index 000000000..14d16722c --- /dev/null +++ b/src/libs/installer/component.h @@ -0,0 +1,233 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef COMPONENT_H +#define COMPONENT_H + +#include "constants.h" +#include "component_p.h" +#include "qinstallerglobal.h" + +#include <QtCore/QDir> +#include <QtCore/QMetaType> +#include <QtCore/QObject> +#include <QtCore/QUrl> + +#include <QtScript/QScriptable> +#include <QtScript/QScriptValueList> + +QT_FORWARD_DECLARE_CLASS(QDebug) + +namespace KDUpdater { + class Update; + struct PackageInfo; +} + +namespace QInstaller { + +class PackageManagerCore; + +class INSTALLER_EXPORT Component : public QObject, public QScriptable, public ComponentModelHelper +{ + Q_OBJECT + Q_DISABLE_COPY(Component) + + Q_PROPERTY(QString name READ name) + Q_PROPERTY(QString displayName READ displayName) + Q_PROPERTY(bool selected READ isSelected WRITE setSelected) + Q_PROPERTY(bool autoCreateOperations READ autoCreateOperations WRITE setAutoCreateOperations) + Q_PROPERTY(QStringList archives READ archives) + Q_PROPERTY(QStringList userInterfaces READ userInterfaces) + Q_PROPERTY(QStringList dependencies READ dependencies) + Q_PROPERTY(QStringList autoDependencies READ autoDependencies) + Q_PROPERTY(bool fromOnlineRepository READ isFromOnlineRepository) + Q_PROPERTY(QUrl repositoryUrl READ repositoryUrl) + Q_PROPERTY(bool removeBeforeUpdate READ removeBeforeUpdate WRITE setRemoveBeforeUpdate) + Q_PROPERTY(bool default READ isDefault) + Q_PROPERTY(bool installed READ isInstalled) + Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled) + +public: + explicit Component(PackageManagerCore *core); + ~Component(); + + struct SortingPriorityLessThan + { + bool operator() (const Component *lhs, const Component *rhs) const + { + return lhs->value(scSortingPriority).toInt() < rhs->value(scSortingPriority).toInt(); + } + }; + + void loadDataFromPackage(const Package &package); + void loadDataFromPackage(const LocalPackage &package); + + QHash<QString, QString> variables() const; + Q_INVOKABLE void setValue(const QString &key, const QString &value); + Q_INVOKABLE QString value(const QString &key, const QString &defaultValue = QString()) const; + + QStringList archives() const; + PackageManagerCore *packageManagerCore() const; + + Component *parentComponent() const; + void appendComponent(Component *component); + void removeComponent(Component *component); + QList<Component*> childComponents(bool recursive, RunMode runMode) const; + + void loadComponentScript(); + + //move this to private + void loadComponentScript(const QString &fileName); + void loadTranslations(const QDir &directory, const QStringList &qms); + void loadUserInterfaces(const QDir &directory, const QStringList &uis); + void loadLicenses(const QString &directory, const QHash<QString, QVariant> &hash); + void markAsPerformedInstallation(); + + QStringList userInterfaces() const; + QHash<QString, QPair<QString, QString> > licenses() const; + Q_INVOKABLE QWidget *userInterface(const QString &name) const; + Q_INVOKABLE virtual void beginInstallation(); + Q_INVOKABLE virtual void createOperations(); + Q_INVOKABLE virtual void createOperationsForArchive(const QString &archive); + Q_INVOKABLE virtual void createOperationsForPath(const QString &path); + + Q_INVOKABLE QList<QPair<QString, bool> > pathesForUninstallation() const; + Q_INVOKABLE void registerPathForUninstallation(const QString &path, bool wipe = false); + + OperationList operations() const; + + void addOperation(Operation *operation); + Q_INVOKABLE bool addOperation(const QString &operation, const QString ¶meter1 = QString(), + const QString ¶meter2 = QString(), const QString ¶meter3 = QString(), + const QString ¶meter4 = QString(), const QString ¶meter5 = QString(), + const QString ¶meter6 = QString(), const QString ¶meter7 = QString(), + const QString ¶meter8 = QString(), const QString ¶meter9 = QString(), + const QString ¶meter10 = QString()); + + void addElevatedOperation(Operation *operation); + Q_INVOKABLE bool addElevatedOperation(const QString &operation, + const QString ¶meter1 = QString(), const QString ¶meter2 = QString(), + const QString ¶meter3 = QString(), const QString ¶meter4 = QString(), + const QString ¶meter5 = QString(), const QString ¶meter6 = QString(), + const QString ¶meter7 = QString(), const QString ¶meter8 = QString(), + const QString ¶meter9 = QString(), const QString ¶meter10 = QString()); + + QStringList downloadableArchives() const; + Q_INVOKABLE void addDownloadableArchive(const QString &path); + Q_INVOKABLE void removeDownloadableArchive(const QString &path); + + QStringList stopProcessForUpdateRequests() const; + Q_INVOKABLE void addStopProcessForUpdateRequest(const QString &process); + Q_INVOKABLE void removeStopProcessForUpdateRequest(const QString &process); + Q_INVOKABLE void setStopProcessForUpdateRequest(const QString &process, bool requested); + + QString name() const; + QString displayName() const; + QString uncompressedSize() const; + quint64 updateUncompressedSize(); + + QUrl repositoryUrl() const; + void setRepositoryUrl(const QUrl &url); + + bool removeBeforeUpdate() const; + void setRemoveBeforeUpdate(bool removeBeforeUpdate); + + Q_INVOKABLE void addDependency(const QString &newDependency); + QStringList dependencies() const; + QStringList autoDependencies() const; + + void languageChanged(); + QString localTempPath() const; + + bool autoCreateOperations() const; + bool operationsCreatedSuccessfully() const; + + Q_INVOKABLE bool isDefault() const; + Q_INVOKABLE bool isAutoDependOn(const QSet<QString> &componentsToInstall) const; + + Q_INVOKABLE void setInstalled(); + Q_INVOKABLE bool isInstalled() const; + Q_INVOKABLE bool installationRequested() const; + + Q_INVOKABLE void setUninstalled(); + Q_INVOKABLE bool isUninstalled() const; + Q_INVOKABLE bool uninstallationRequested() const; + + Q_INVOKABLE bool isFromOnlineRepository() const; + + Q_INVOKABLE void setUpdateAvailable(bool isUpdateAvailable); + Q_INVOKABLE bool updateRequested(); + + Q_INVOKABLE bool componentChangeRequested(); + + + bool isVirtual() const; + bool isSelected() const; + bool forcedInstallation() const; + +public Q_SLOTS: + void setSelected(bool selected); + void setAutoCreateOperations(bool autoCreateOperations); + +Q_SIGNALS: + void loaded(); + void selectedChanged(bool selected); + void valueChanged(const QString &key, const QString &value); + +protected: + QScriptValue callScriptMethod(const QString &name, + const QScriptValueList ¶meters = QScriptValueList()) const; + +private Q_SLOTS: + void updateModelData(const QString &key, const QString &value); + +private: + void setLocalTempPath(const QString &tempPath); + + Operation *createOperation(const QString &operation, const QString ¶meter1 = QString(), + const QString ¶meter2 = QString(), const QString ¶meter3 = QString(), + const QString ¶meter4 = QString(), const QString ¶meter5 = QString(), + const QString ¶meter6 = QString(), const QString ¶meter7 = QString(), + const QString ¶meter8 = QString(), const QString ¶meter9 = QString(), + const QString ¶meter10 = QString()); + +private: + ComponentPrivate *d; +}; + +QDebug operator<<(QDebug dbg, Component *component); + +} // namespace QInstaller + +Q_DECLARE_METATYPE(QInstaller::Component*) + +#endif // COMPONENT_H diff --git a/src/libs/installer/component_p.cpp b/src/libs/installer/component_p.cpp new file mode 100644 index 000000000..4294ac8a8 --- /dev/null +++ b/src/libs/installer/component_p.cpp @@ -0,0 +1,359 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "component_p.h" + +#include "component.h" +#include "messageboxhandler.h" +#include "packagemanagercore.h" + +#include <QtGui/QApplication> +#include <QtGui/QDesktopServices> + + +namespace QInstaller { + +// -- ComponentPrivate + +ComponentPrivate::ComponentPrivate(PackageManagerCore *core, Component *qq) + : q(qq), + m_core(core), + m_parentComponent(0), + m_licenseOperation(0), + m_minimumProgressOperation(0), + m_newlyInstalled (false), + m_operationsCreated(false), + m_removeBeforeUpdate(true), + m_autoCreateOperations(true), + m_operationsCreatedSuccessfully(true), + m_updateIsAvailable(false), + m_scriptEngine(0) +{ +} + +ComponentPrivate::~ComponentPrivate() +{ + // Before we can delete the added widgets, they need to be removed from the wizard first. + QMap<QString, QPointer<QWidget> >::const_iterator it; + foreach (const QString &widgetName, m_userInterfaces.keys()) { + m_core->removeWizardPage(q, widgetName); + m_core->removeWizardPageItem(q, widgetName); + } + + // Use QPointer here instead of raw pointers. This is a requirement that needs to be met cause possible + // Ui elements get added during component script run and might be destroyed by the package manager gui + // before the actual component gets destroyed. Avoids a possible delete call on a dangling pointer. + foreach (const QPointer<QWidget> widget, m_userInterfaces) + delete widget.data(); +} + +QScriptEngine *ComponentPrivate::scriptEngine() +{ + if (m_scriptEngine != 0) + return m_scriptEngine; + + + m_scriptEngine = new QScriptEngine(q); + + // register translation stuff + m_scriptEngine->installTranslatorFunctions(); + + // register QMessageBox::StandardButton enum in the script connection + registerMessageBox(m_scriptEngine); + + // register QDesktopServices in the script connection + QScriptValue desktopServices = m_scriptEngine->newArray(); + setProperty(desktopServices, QLatin1String("DesktopLocation"), QDesktopServices::DesktopLocation); + setProperty(desktopServices, QLatin1String("DocumentsLocation"), QDesktopServices::DocumentsLocation); + setProperty(desktopServices, QLatin1String("FontsLocation"), QDesktopServices::FontsLocation); + setProperty(desktopServices, QLatin1String("ApplicationsLocation"), QDesktopServices::ApplicationsLocation); + setProperty(desktopServices, QLatin1String("MusicLocation"), QDesktopServices::MusicLocation); + setProperty(desktopServices, QLatin1String("MoviesLocation"), QDesktopServices::MoviesLocation); + setProperty(desktopServices, QLatin1String("PicturesLocation"), QDesktopServices::PicturesLocation); + setProperty(desktopServices, QLatin1String("TempLocation"), QDesktopServices::TempLocation); + setProperty(desktopServices, QLatin1String("HomeLocation"), QDesktopServices::HomeLocation); + setProperty(desktopServices, QLatin1String("DataLocation"), QDesktopServices::DataLocation); + setProperty(desktopServices, QLatin1String("CacheLocation"), QDesktopServices::CacheLocation); + + desktopServices.setProperty(QLatin1String("openUrl"), m_scriptEngine->newFunction(qDesktopServicesOpenUrl)); + desktopServices.setProperty(QLatin1String("displayName"), + m_scriptEngine->newFunction(qDesktopServicesDisplayName)); + desktopServices.setProperty(QLatin1String("storageLocation"), + m_scriptEngine->newFunction(qDesktopServicesStorageLocation)); + + // register ::WizardPage enum in the script connection + QScriptValue qinstaller = m_scriptEngine->newArray(); + setProperty(qinstaller, QLatin1String("Introduction"), PackageManagerCore::Introduction); + setProperty(qinstaller, QLatin1String("LicenseCheck"), PackageManagerCore::LicenseCheck); + setProperty(qinstaller, QLatin1String("TargetDirectory"), PackageManagerCore::TargetDirectory); + setProperty(qinstaller, QLatin1String("ComponentSelection"), PackageManagerCore::ComponentSelection); + setProperty(qinstaller, QLatin1String("StartMenuSelection"), PackageManagerCore::StartMenuSelection); + setProperty(qinstaller, QLatin1String("ReadyForInstallation"), PackageManagerCore::ReadyForInstallation); + setProperty(qinstaller, QLatin1String("PerformInstallation"), PackageManagerCore::PerformInstallation); + setProperty(qinstaller, QLatin1String("InstallationFinished"), PackageManagerCore::InstallationFinished); + setProperty(qinstaller, QLatin1String("End"), PackageManagerCore::End); + + // register ::Status enum in the script connection + setProperty(qinstaller, QLatin1String("Success"), PackageManagerCore::Success); + setProperty(qinstaller, QLatin1String("Failure"), PackageManagerCore::Failure); + setProperty(qinstaller, QLatin1String("Running"), PackageManagerCore::Running); + setProperty(qinstaller, QLatin1String("Canceled"), PackageManagerCore::Canceled); + + // maybe used by old scripts + setProperty(qinstaller, QLatin1String("InstallerFailed"), PackageManagerCore::Failure); + setProperty(qinstaller, QLatin1String("InstallerSucceeded"), PackageManagerCore::Success); + setProperty(qinstaller, QLatin1String("InstallerUnfinished"), PackageManagerCore::Unfinished); + setProperty(qinstaller, QLatin1String("InstallerCanceledByUser"), PackageManagerCore::Canceled); + + QScriptValue installerObject = m_scriptEngine->newQObject(m_core); + installerObject.setProperty(QLatin1String("componentByName"), m_scriptEngine + ->newFunction(qInstallerComponentByName, 1)); + + m_scriptEngine->globalObject().setProperty(QLatin1String("QInstaller"), qinstaller); + m_scriptEngine->globalObject().setProperty(QLatin1String("installer"), installerObject); + m_scriptEngine->globalObject().setProperty(QLatin1String("QDesktopServices"), desktopServices); + m_scriptEngine->globalObject().setProperty(QLatin1String("component"), m_scriptEngine->newQObject(q)); + + return m_scriptEngine; +} + +void ComponentPrivate::setProperty(QScriptValue &scriptValue, const QString &propertyName, int value) +{ + scriptValue.setProperty(propertyName, m_scriptEngine->newVariant(value)); +} + + +// -- ComponentModelHelper + +ComponentModelHelper::ComponentModelHelper() +{ + setCheckState(Qt::Unchecked); + setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable); +} + +/*! + Returns the number of child components. Depending if virtual components are visible or not the count might + differ from what one will get if calling Component::childComponents(...).count(). +*/ +int ComponentModelHelper::childCount() const +{ + if (m_componentPrivate->m_core->virtualComponentsVisible()) + return m_componentPrivate->m_allChildComponents.count(); + return m_componentPrivate->m_childComponents.count(); +} + +/*! + Returns the index of this component as seen from it's parent. +*/ +int ComponentModelHelper::indexInParent() const +{ + int index = 0; + if (Component *parent = m_componentPrivate->m_parentComponent->parentComponent()) + index = parent->childComponents(false, AllMode).indexOf(m_componentPrivate->m_parentComponent); + return (index >= 0 ? index : 0); +} + +/*! + Returns all children and whose children depending if virtual components are visible or not. +*/ +QList<Component*> ComponentModelHelper::childs() const +{ + QList<Component*> *components = &m_componentPrivate->m_childComponents; + if (m_componentPrivate->m_core->virtualComponentsVisible()) + components = &m_componentPrivate->m_allChildComponents; + + QList<Component*> result; + foreach (Component *component, *components) { + result.append(component); + result += component->childs(); + } + return result; +} + +/*! + Returns the component at index position in the list. Index must be a valid position in + the list (i.e., index >= 0 && index < childCount()). Otherwise it returns 0. +*/ +Component *ComponentModelHelper::childAt(int index) const +{ + if (index >= 0 && index < childCount()) { + if (m_componentPrivate->m_core->virtualComponentsVisible()) + return m_componentPrivate->m_allChildComponents.value(index, 0); + return m_componentPrivate->m_childComponents.value(index, 0); + } + return 0; +} + +/*! + Determines if the components installations status can be changed. The default value is true. +*/ +bool ComponentModelHelper::isEnabled() const +{ + return (flags() & Qt::ItemIsEnabled) != 0; +} + +/*! + Enables oder disables ability to change the components installations status. +*/ +void ComponentModelHelper::setEnabled(bool enabled) +{ + changeFlags(enabled, Qt::ItemIsEnabled); +} + +/*! + Returns whether the component is tristate; that is, if it's checkable with three separate states. + The default value is false. +*/ +bool ComponentModelHelper::isTristate() const +{ + return (flags() & Qt::ItemIsTristate) != 0; +} + +/*! + Sets whether the component is tristate. If tristate is true, the component is checkable with three + separate states; otherwise, the component is checkable with two states. + + (Note that this also requires that the component is checkable; see isCheckable().) +*/ +void ComponentModelHelper::setTristate(bool tristate) +{ + changeFlags(tristate, Qt::ItemIsTristate); +} + +/*! + Returns whether the component is user-checkable. The default value is true. +*/ +bool ComponentModelHelper::isCheckable() const +{ + return (flags() & Qt::ItemIsUserCheckable) != 0; +} + +/*! + Sets whether the component is user-checkable. If checkable is true, the component can be checked by the + user; otherwise, the user cannot check the component. The delegate will render a checkable component + with a check box next to the component's text. +*/ +void ComponentModelHelper::setCheckable(bool checkable) +{ + if (checkable && !isCheckable()) { + // make sure there's data for the check state role + if (!data(Qt::CheckStateRole).isValid()) + setData(Qt::Unchecked, Qt::CheckStateRole); + } + changeFlags(checkable, Qt::ItemIsUserCheckable); +} + +/*! + Returns whether the component is selectable by the user. The default value is true. +*/ +bool ComponentModelHelper::isSelectable() const +{ + return (flags() & Qt::ItemIsSelectable) != 0; +} + +/*! + Sets whether the component is selectable. If selectable is true, the component can be selected by the + user; otherwise, the user cannot select the component. +*/ +void ComponentModelHelper::setSelectable(bool selectable) +{ + changeFlags(selectable, Qt::ItemIsSelectable); +} + +/*! + Returns the item flags for the component. The item flags determine how the user can interact with the + component. +*/ +Qt::ItemFlags ComponentModelHelper::flags() const +{ + QVariant variant = data(Qt::UserRole - 1); + if (!variant.isValid()) + return (Qt::ItemIsEnabled | Qt::ItemIsSelectable| Qt::ItemIsUserCheckable); + return Qt::ItemFlags(variant.toInt()); +} + +/*! + Sets the item flags for the component to flags. The item flags determine how the user can interact with + the component. This is often used to disable an component. +*/ +void ComponentModelHelper::setFlags(Qt::ItemFlags flags) +{ + setData(int(flags), Qt::UserRole - 1); +} + +/*! + Returns the checked state of the component. +*/ +Qt::CheckState ComponentModelHelper::checkState() const +{ + return Qt::CheckState(qvariant_cast<int>(data(Qt::CheckStateRole))); +} + +/*! + Sets the check state of the component to be state. +*/ +void ComponentModelHelper::setCheckState(Qt::CheckState state) +{ + setData(state, Qt::CheckStateRole); +} + +/*! + Returns the component's data for the given role, or an invalid QVariant if there is no data for role. +*/ +QVariant ComponentModelHelper::data(int role) const +{ + return m_values.value((role == Qt::EditRole ? Qt::DisplayRole : role), QVariant()); +} + +/*! + Sets the component's data for the given role to the specified value. +*/ +void ComponentModelHelper::setData(const QVariant &value, int role) +{ + m_values.insert((role == Qt::EditRole ? Qt::DisplayRole : role), value); +} + +// -- protected + +void ComponentModelHelper::setPrivate(ComponentPrivate *componentPrivate) +{ + m_componentPrivate = componentPrivate; +} + +// -- private + +void ComponentModelHelper::changeFlags(bool enable, Qt::ItemFlags itemFlags) +{ + setFlags(enable ? flags() |= itemFlags : flags() &= ~itemFlags); +} + +} // namespace QInstaller diff --git a/src/libs/installer/component_p.h b/src/libs/installer/component_p.h new file mode 100644 index 000000000..6cdbdcf80 --- /dev/null +++ b/src/libs/installer/component_p.h @@ -0,0 +1,157 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef COMPONENT_P_H +#define COMPONENT_P_H + +#include "qinstallerglobal.h" + +#include <QtCore/QPointer> +#include <QtCore/QStringList> +#include <QtCore/QUrl> + +#include <QtScript/QScriptEngine> + +namespace QInstaller { + +class Component; +class PackageManagerCore; + +class ComponentPrivate +{ + QInstaller::Component* const q; + +public: + explicit ComponentPrivate(PackageManagerCore *core, Component *qq); + ~ComponentPrivate(); + + QScriptEngine *scriptEngine(); + + void setProperty(QScriptValue &scriptValue, const QString &propertyName, int value); + + PackageManagerCore *m_core; + Component *m_parentComponent; + OperationList m_operations; + Operation *m_licenseOperation; + Operation *m_minimumProgressOperation; + + bool m_newlyInstalled; + bool m_operationsCreated; + bool m_removeBeforeUpdate; + bool m_autoCreateOperations; + bool m_operationsCreatedSuccessfully; + bool m_updateIsAvailable; + + QString m_componentName; + QUrl m_repositoryUrl; + QString m_localTempPath; + QScriptValue m_scriptComponent; + QHash<QString, QString> m_vars; + QList<Component*> m_childComponents; + QList<Component*> m_allChildComponents; + QList<Component*> m_virtualChildComponents; + QStringList m_downloadableArchives; + QStringList m_stopProcessForUpdateRequests; + QHash<QString, bool> m_unexistingScriptMethods; + QMap<QString, QPointer<QWidget> > m_userInterfaces; + + // < display name, < file name, file content > > + QHash<QString, QPair<QString, QString> > m_licenses; + QList<QPair<QString, bool> > m_pathesForUninstallation; + +private: + QScriptEngine* m_scriptEngine; +}; + + +// -- ComponentModelHelper + +class ComponentModelHelper +{ +public: + enum Roles { + LocalDisplayVersion = Qt::UserRole + 1, + RemoteDisplayVersion = LocalDisplayVersion + 1, + UncompressedSize = RemoteDisplayVersion + 1 + }; + + enum Column { + NameColumn = 0, + InstalledVersionColumn, + NewVersionColumn, + UncompressedSizeColumn + }; + + explicit ComponentModelHelper(); + + int childCount() const; + int indexInParent() const; + + QList<Component*> childs() const; + Component* childAt(int index) const; + + bool isEnabled() const; + void setEnabled(bool enabled); + + bool isTristate() const; + void setTristate(bool tristate); + + bool isCheckable() const; + void setCheckable(bool checkable); + + bool isSelectable() const; + void setSelectable(bool selectable); + + Qt::ItemFlags flags() const; + void setFlags(Qt::ItemFlags flags); + + Qt::CheckState checkState() const; + void setCheckState(Qt::CheckState state); + + QVariant data(int role = Qt::UserRole + 1) const; + void setData(const QVariant &value, int role = Qt::UserRole + 1); + +protected: + void setPrivate(ComponentPrivate *componentPrivate); + +private: + void changeFlags(bool enable, Qt::ItemFlags itemFlags); + +private: + QHash<int, QVariant> m_values; + + ComponentPrivate *m_componentPrivate; +}; + +} // namespace QInstaller + +#endif // COMPONENT_P_H diff --git a/src/libs/installer/componentmodel.cpp b/src/libs/installer/componentmodel.cpp new file mode 100644 index 000000000..ec61ee84e --- /dev/null +++ b/src/libs/installer/componentmodel.cpp @@ -0,0 +1,528 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "componentmodel.h" + +#include "component.h" +#include "packagemanagercore.h" + +namespace QInstaller { + +/*! + \fn void defaultCheckStateChanged(bool changed) + + This signal is emitted whenever the default check state of a model is changed. The \a changed value + indicates whether the model has it's initial checked state or some components changed it's checked state. +*/ + +/*! + \fn void checkStateChanged(const QModelIndex &index) + + This signal is emitted whenever the default check state of a component is changed. The \a index value + indicates the QModelIndex representation of the component as seen from the model. +*/ + + +/*! + Constructs an component model with the given number of \a columns and \a core as parent. +*/ +ComponentModel::ComponentModel(int columns, PackageManagerCore *core) + : QAbstractItemModel(core) + , m_core(core) + , m_rootIndex(0) +{ + m_headerData.insert(0, columns, QVariant()); + + connect(this, SIGNAL(modelReset()), this, SLOT(slotModelReset())); + connect(this, SIGNAL(checkStateChanged(QModelIndex)), this, SLOT(slotCheckStateChanged(QModelIndex))); +} + +/*! + Destroys the component model. +*/ +ComponentModel::~ComponentModel() +{ +} + +/*! + Returns the number of items under the given \a parent. When the parent is valid it means that rowCount is + returning the number of items of parent. +*/ +int ComponentModel::rowCount(const QModelIndex &parent) const +{ + if (Component *component = componentFromIndex(parent)) + return component->childCount(); + return m_rootComponentList.count(); +} + +/*! + Returns the number of columns of the given \a parent. +*/ +int ComponentModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return m_headerData.size(); +} + +/*! + Returns the parent of the child item with the given \a parent. If the item has no parent, an invalid + QModelIndex is returned. +*/ +QModelIndex ComponentModel::parent(const QModelIndex &child) const +{ + if (!child.isValid()) + return QModelIndex(); + + if (Component *childComponent = componentFromIndex(child)) { + if (Component *parent = childComponent->parentComponent()) { + if (!m_rootComponentList.contains(parent)) + return createIndex(parent->indexInParent(), 0, parent); + return createIndex(child.row(), 0, parent); + } + } + + return QModelIndex(); +} + +/*! + Returns the index of the item in the model specified by the given \a row, \a column and \a parent index. +*/ +QModelIndex ComponentModel::index(int row, int column, const QModelIndex &parent) const +{ + if (parent.isValid() && (row >= rowCount(parent) || column >= columnCount())) + return QModelIndex(); + + if (Component *parentComponent = componentFromIndex(parent)) { + if (Component *childComponent = parentComponent->childAt(row)) + return createIndex(row, column, childComponent); + } else if (row < m_rootComponentList.count()) { + return createIndex(row, column, m_rootComponentList.at(row)); + } + + return QModelIndex(); +} + +/*! + Returns the data stored under the given \a role for the item referred to by the \a index. + + \note An \bold invalid QVariant is returned if the given index is invalid. \bold Qt::CheckStateRole is + only supported for the first column of the model. \bold Qt::EditRole, \bold Qt::DisplayRole and \bold + Qt::ToolTipRole are specifically handled for columns greater than the first column and translate to \bold + Qt::UserRole \bold + \bold index.column(). + +*/ +QVariant ComponentModel::data(const QModelIndex &index, int role) const +{ + if (Component *component = componentFromIndex(index)) { + if (index.column() > 0) { + if (role == Qt::CheckStateRole) + return QVariant(); + if (role == Qt::EditRole || role == Qt::DisplayRole || role == Qt::ToolTipRole) + return component->data(Qt::UserRole + index.column()); + } + return component->data(role); + } + return QVariant(); +} + +/*! + Sets the \a role data for the item at \a index to \a value. Returns true if successful; otherwise returns + false. The dataChanged() signal is emitted if the data was successfully set. The checkStateChanged() and + defaultCheckStateChanged() signal are emitted in addition if the check state of the item is set. +*/ +bool ComponentModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid()) + return false; + + Component *component = componentFromIndex(index); + if (!component) + return false; + + component->setData(value, role); + + emit dataChanged(index, index); + if (role == Qt::CheckStateRole) { + emit checkStateChanged(index); + foreach (Component* comp, m_rootComponentList) { + comp->updateUncompressedSize(); + } + } + + return true; +} + +/*! + Returns the data for the given \a role and \a section in the header with the specified \a orientation. + An \bold invalid QVariant is returned if \a section is out of bounds, \a orientation is not Qt::Horizontal + or \a role is anything else than Qt::DisplayRole. +*/ +QVariant ComponentModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (section >= 0 && section < columnCount() && orientation == Qt::Horizontal && role == Qt::DisplayRole) + return m_headerData.at(section); + return QVariant(); +} + +/*! + Sets the data for the given \a role and \a section in the header with the specified \a orientation to the + \a value supplied. Returns true if the header's data was updated; otherwise returns false. The + headerDataChanged() signal is emitted if the data was successfully set. + + \note Only \bold Qt::Horizontal orientation is supported. +*/ +bool ComponentModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) +{ + if (section >= 0 && section < columnCount() && orientation == Qt::Horizontal + && (role == Qt::DisplayRole || role == Qt::EditRole)) { + m_headerData.replace(section, value); + emit headerDataChanged(orientation, section, section); + return true; + } + return false; +} + +/*! + Returns the item flags for the given \a index. + + The class implementation returns a combination of flags that enables the item (Qt::ItemIsEnabled), allows + it to be selected (Qt::ItemIsSelectable) and to be checked (Qt::ItemIsUserCheckable). +*/ +Qt::ItemFlags ComponentModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::NoItemFlags; + + if (Component *component = componentFromIndex(index)) + return component->flags(); + + return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable; +} + +/*! + Set's the passed \a rootComponents to be list of currently shown components. The model is repopulated and + the individual component checked state is used to show the check mark in front of the visual component + representation. The modelAboutToBeReset() and modelReset() signals are emitted. +*/ +void ComponentModel::setRootComponents(QList<Component*> rootComponents) +{ + beginResetModel(); + + m_indexByNameCache.clear(); + m_rootComponentList.clear(); + m_initialCheckedSet.clear(); + m_currentCheckedSet.clear(); + + m_rootIndex = 0; + m_rootComponentList = rootComponents; + + endResetModel(); +} + +/*! + Appends the passed \a rootComponents to the currently shown list of components. The model is repopulated + and the individual component checked state is used to show the check mark in front of the visual component + representation. Already changed check states on the previous model are preserved. The modelAboutToBeReset() + and modelReset() signals are emitted. +*/ +void ComponentModel::appendRootComponents(QList<Component*> rootComponents) +{ + beginResetModel(); + + m_indexByNameCache.clear(); + + m_rootIndex = m_rootComponentList.count() - 1; + m_rootComponentList += rootComponents; + + endResetModel(); +} + +/*! + Returns a pointer to the PackageManagerCore this model belongs to. +*/ +PackageManagerCore *ComponentModel::packageManagerCore() const +{ + return m_core; +} + +/*! + Returns true if no changes to the components checked state have been done, otherwise returns false. +*/ +bool ComponentModel::defaultCheckState() const +{ + return m_initialCheckedSet == m_currentCheckedSet; +} + +/*! + Returns true if this model has checked components, otherwise returns false. +*/ +bool ComponentModel::hasCheckedComponents() const +{ + return !m_currentCheckedSet.isEmpty(); +} + +/*! + Returns a list of checked components. +*/ +QList<Component*> ComponentModel::checkedComponents() const +{ + QList<Component*> list; + foreach (const QString &name, m_currentCheckedSet) + list.append(componentFromIndex(indexFromComponentName(name))); + return list; +} + +/*! + Translates between a given component \a name and it's associated QModelIndex. Returns the QModelIndex that + represents the component or an invalid QModelIndex if the component does not exist in the model. +*/ +QModelIndex ComponentModel::indexFromComponentName(const QString &name) const +{ + if (m_indexByNameCache.isEmpty()) { + for (int i = 0; i < m_rootComponentList.count(); ++i) + updateCache(index(i, 0, QModelIndex())); + } + return m_indexByNameCache.value(name, QModelIndex()); +} + +/*! + Translates between a given QModelIndex \a index and it's associated Component. Returns the Component if + the index is valid or 0 if an invalid QModelIndex is given. +*/ +Component *ComponentModel::componentFromIndex(const QModelIndex &index) const +{ + if (index.isValid()) + return static_cast<Component*>(index.internalPointer()); + return 0; +} + +// -- public slots + +/*! + Invoking this slot results in an checked state for every component the has a visual representation in the + model. Note that components are not changed if they are not checkable. The checkStateChanged() and + defaultCheckStateChanged() signal are emitted. +*/ +void ComponentModel::selectAll() +{ + m_currentCheckedSet = m_currentCheckedSet.unite(select(Qt::Checked)); + emit defaultCheckStateChanged(m_initialCheckedSet != m_currentCheckedSet); +} + +/*! + Invoking this slot results in an unchecked state for every component the has a visual representation in + the model. Note that components are not changed if they are not checkable. The checkStateChanged() and + defaultCheckStateChanged() signal are emitted. +*/ +void ComponentModel::deselectAll() +{ + m_currentCheckedSet = m_currentCheckedSet.subtract(select(Qt::Unchecked)); + emit defaultCheckStateChanged(m_initialCheckedSet != m_currentCheckedSet); +} + +/*! + Invoking this slot results in an checked state for every component the has a visual representation in the + model when the model was setup during setRootComponents() or appendRootComponents(). Note that components + are not changed it they are not checkable. The checkStateChanged() and defaultCheckStateChanged() signal + are emitted. +*/ +void ComponentModel::selectDefault() +{ + m_currentCheckedSet = m_currentCheckedSet.subtract(select(Qt::Unchecked)); + foreach (const QString &name, m_initialCheckedSet) + setData(indexFromComponentName(name), Qt::Checked, Qt::CheckStateRole); + emit defaultCheckStateChanged(m_initialCheckedSet != m_currentCheckedSet); +} + +// -- private slots + +void ComponentModel::slotModelReset() +{ + QList<QInstaller::Component*> components = m_rootComponentList; + if (m_core->runMode() == QInstaller::AllMode) { + for (int i = m_rootIndex; i < m_rootComponentList.count(); ++i) + components.append(m_rootComponentList.at(i)->childs()); + } + + foreach (Component *child, components) { + if (child->checkState() == Qt::Checked && !child->isTristate()) + m_initialCheckedSet.insert(child->name()); + } + m_currentCheckedSet += m_initialCheckedSet; + + if (m_core->runMode() == QInstaller::AllMode) + select(Qt::Unchecked); + + foreach (const QString &name, m_currentCheckedSet) + setData(indexFromComponentName(name), Qt::Checked, Qt::CheckStateRole); + +} + +static Qt::CheckState verifyPartiallyChecked(Component *component) +{ + int checked = 0; + int unchecked = 0; + int virtualChilds = 0; + + const int count = component->childCount(); + for (int i = 0; i < count; ++i) { + Component *const child = component->childAt(i); + if (!child->isVirtual()) { + switch (component->childAt(i)->checkState()) { + case Qt::Checked: { + ++checked; + } break; + case Qt::Unchecked: { + ++unchecked; + } break; + default: + break; + } + } else { + ++virtualChilds; + } + } + + if ((checked + virtualChilds) == count) + return Qt::Checked; + + if ((unchecked + virtualChilds) == count) + return Qt::Unchecked; + + return Qt::PartiallyChecked; +} + +void ComponentModel::slotCheckStateChanged(const QModelIndex &index) +{ + Component *component = componentFromIndex(index); + if (!component) + return; + + if (component->checkState() == Qt::Checked && !component->isTristate()) + m_currentCheckedSet.insert(component->name()); + else if (component->checkState() == Qt::Unchecked && !component->isTristate()) + m_currentCheckedSet.remove(component->name()); + emit defaultCheckStateChanged(m_initialCheckedSet != m_currentCheckedSet); + + if (component->isVirtual()) + return; + + const Qt::CheckState state = component->checkState(); + if (component->isTristate()) { + if (state == Qt::PartiallyChecked) { + component->setCheckState(verifyPartiallyChecked(component)); + return; + } + + QModelIndexList notCheckable; + foreach (Component *child, component->childs()) { + const QModelIndex &idx = indexFromComponentName(child->name()); + if (child->isCheckable()) { + if (child->checkState() != state && !child->isVirtual()) + setData(idx, state, Qt::CheckStateRole); + } else { + notCheckable.append(idx); + } + } + + if (state == Qt::Unchecked && !notCheckable.isEmpty()) { + foreach (const QModelIndex &idx, notCheckable) + setData(idx, idx.data(Qt::CheckStateRole), Qt::CheckStateRole); + } + } else { + QList<Component*> parents; + while (0 != component->parentComponent()) { + parents.append(component->parentComponent()); + component = parents.last(); + } + + foreach (Component *parent, parents) { + if (parent->isCheckable()) { + const QModelIndex &idx = indexFromComponentName(parent->name()); + if (parent->checkState() == Qt::PartiallyChecked) { + setData(idx, verifyPartiallyChecked(parent), Qt::CheckStateRole); + } else { + setData(idx, Qt::PartiallyChecked, Qt::CheckStateRole); + } + } + } + } +} + +// -- private + +QSet<QString> ComponentModel::select(Qt::CheckState state) +{ + QSet<QString> changed; + for (int i = 0; i < m_rootComponentList.count(); ++i) { + QSet<QString> tmp; + QList<Component*> children = m_rootComponentList.at(i)->childs(); + children.prepend(m_rootComponentList.at(i)); // we need to take the root item into account as well + foreach (Component *child, children) { + if (child->isCheckable() && !child->isTristate() && child->checkState() != state) { + tmp.insert(child->name()); + child->setCheckState(state); + } + } + if (!tmp.isEmpty()) { + changed += tmp; + setData(index(i, 0, QModelIndex()), state, Qt::CheckStateRole); + } + } + return changed; +} + +void ComponentModel::updateCache(const QModelIndex &parent) const +{ + const QModelIndexList &list = collectComponents(parent); + foreach (const QModelIndex &index, list) { + if (Component *component = componentFromIndex(index)) + m_indexByNameCache.insert(component->name(), index); + } + m_indexByNameCache.insert((static_cast<Component*> (parent.internalPointer()))->name(), parent); +} + +QModelIndexList ComponentModel::collectComponents(const QModelIndex &parent) const +{ + QModelIndexList list; + for (int i = 0; i < rowCount(parent) ; ++i) { + const QModelIndex &next = index(i, 0, parent); + if (Component *component = componentFromIndex(next)) { + if (component->childCount() > 0) + list += collectComponents(next); + } + list.append(next); + } + return list; +} + +} // namespace QInstaller diff --git a/src/libs/installer/componentmodel.h b/src/libs/installer/componentmodel.h new file mode 100644 index 000000000..6dd6b634a --- /dev/null +++ b/src/libs/installer/componentmodel.h @@ -0,0 +1,115 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef COMPONENTMODEL_H +#define COMPONENTMODEL_H + +#include "qinstallerglobal.h" + +#include <QtCore/QAbstractItemModel> +#include <QtCore/QList> +#include <QtCore/QSet> +#include <QtCore/QVector> + +namespace QInstaller { + +class Component; +class PackageManagerCore; + +class INSTALLER_EXPORT ComponentModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + explicit ComponentModel(int columns, PackageManagerCore *core = 0); + ~ComponentModel(); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + QModelIndex parent(const QModelIndex &child) const; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, + int role = Qt::EditRole); + + Qt::ItemFlags flags(const QModelIndex &index) const; + + void setRootComponents(QList<Component*> rootComponents); + void appendRootComponents(QList<Component*> rootComponents); + + PackageManagerCore *packageManagerCore() const; + + bool defaultCheckState() const; + bool hasCheckedComponents() const; + QList<Component*> checkedComponents() const; + + QModelIndex indexFromComponentName(const QString &name) const; + Component* componentFromIndex(const QModelIndex &index) const; + +public Q_SLOTS: + void selectAll(); + void deselectAll(); + void selectDefault(); + +Q_SIGNALS: + void defaultCheckStateChanged(bool changed); + void checkStateChanged(const QModelIndex &index); + +private Q_SLOTS: + void slotModelReset(); + void slotCheckStateChanged(const QModelIndex &index); + +private: + QSet<QString> select(Qt::CheckState state); + void updateCache(const QModelIndex &parent) const; + QModelIndexList collectComponents(const QModelIndex &parent) const; + +private: + PackageManagerCore *m_core; + + int m_rootIndex; + QVector<QVariant> m_headerData; + QSet<QString> m_initialCheckedSet; + QSet<QString> m_currentCheckedSet; + QList<Component*> m_rootComponentList; + + mutable QMap<QString, QPersistentModelIndex> m_indexByNameCache; +}; + +} // namespace QInstaller + +#endif // COMPONENTMODEL_H diff --git a/src/libs/installer/constants.h b/src/libs/installer/constants.h new file mode 100644 index 000000000..ea512ed76 --- /dev/null +++ b/src/libs/installer/constants.h @@ -0,0 +1,82 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef CONSTANTS_H +#define CONSTANTS_H + +#include <QtCore/QString> + +namespace QInstaller { + +// constants used throughout several classes +static const QLatin1String scTrue("true"); +static const QLatin1String scFalse("false"); + +static const QLatin1String scName("Name"); +static const QLatin1String scVersion("Version"); +static const QLatin1String scRemoteVersion("Version"); +static const QLatin1String scDisplayVersion("DisplayVersion"); +static const QLatin1String scRemoteDisplayVersion("RemoteDisplayVersion"); +static const QLatin1String scInheritVersion("inheritVersionFrom"); +static const QLatin1String scReplaces("Replaces"); +static const QLatin1String scDownloadableArchives("DownloadableArchives"); +static const QLatin1String scEssential("Essential"); +static const QLatin1String scTargetDir("TargetDir"); +static const QLatin1String scReleaseDate("ReleaseDate"); +static const QLatin1String scDescription("Description"); +static const QLatin1String scDisplayName("DisplayName"); +static const QLatin1String scDependencies("Dependencies"); +static const QLatin1String scNewComponent("NewComponent"); +static const QLatin1String scRepositories("Repositories"); +static const QLatin1String scCompressedSize("CompressedSize"); +static const QLatin1String scInstalledVersion("InstalledVersion"); +static const QLatin1String scUncompressedSize("UncompressedSize"); +static const QLatin1String scUncompressedSizeSum("UncompressedSizeSum"); +static const QLatin1String scRequiresAdminRights("RequiresAdminRights"); + +// constants used throughout the components class +static const QLatin1String scVirtual("Virtual"); +static const QLatin1String scSortingPriority("SortingPriority"); + +// constants used throughout the settings and package manager core class +static const QLatin1String scTitle("Title"); +static const QLatin1String scPublisher("Publisher"); +static const QLatin1String scRunProgram("RunProgram"); +static const QLatin1String scStartMenuDir("StartMenuDir"); +static const QLatin1String scRemoveTargetDir("RemoveTargetDir"); +static const QLatin1String scRunProgramDescription("RunProgramDescription"); +static const QLatin1String scTargetConfigurationFile("TargetConfigurationFile"); +static const QLatin1String scAllowNonAsciiCharacters("AllowNonAsciiCharacters"); + +} + +#endif // CONSTANTS_H diff --git a/src/libs/installer/copydirectoryoperation.cpp b/src/libs/installer/copydirectoryoperation.cpp new file mode 100644 index 000000000..f5e7692f5 --- /dev/null +++ b/src/libs/installer/copydirectoryoperation.cpp @@ -0,0 +1,159 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "copydirectoryoperation.h" + +#include <QtCore/QDir> +#include <QtCore/QDirIterator> +#include <QtCore/QFileInfo> + +using namespace QInstaller; + +class AutoPush +{ +public: + AutoPush(CopyDirectoryOperation *op) + : m_op(op) {} + ~AutoPush() { m_op->setValue(QLatin1String("files"), m_files); } + + QStringList m_files; + CopyDirectoryOperation *m_op; +}; + +/* +TRANSLATOR QInstaller::CopyDirectoryOperation +*/ + +CopyDirectoryOperation::CopyDirectoryOperation() +{ + setName(QLatin1String("CopyDirectory")); +} + +void CopyDirectoryOperation::backup() +{ +} + +bool CopyDirectoryOperation::performOperation() +{ + const QStringList args = arguments(); + if (args.count() != 2) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in %0: %1 arguments given, 2 expected.").arg(name()) + .arg(args.count())); + return false; + } + const QString sourcePath = args.at(0); + const QString targetPath = args.at(1); + + const QFileInfo sourceInfo(sourcePath); + const QFileInfo targetInfo(targetPath); + if (!sourceInfo.exists() || !sourceInfo.isDir() || !targetInfo.exists() || !targetInfo.isDir()) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in %0: Directories are invalid: %1 %2").arg(name()) + .arg(sourcePath).arg(targetPath)); + return false; + } + + const QDir sourceDir = sourceInfo.absoluteDir(); + const QDir targetDir = targetInfo.absoluteDir(); + + AutoPush autoPush(this); + QDirIterator it(sourceInfo.absoluteFilePath(), QDir::NoDotAndDotDot | QDir::AllEntries | QDir::Hidden, + QDirIterator::Subdirectories); + while (it.hasNext()) { + const QString itemName = it.next(); + const QFileInfo itemInfo(sourceDir.absoluteFilePath(itemName)); + const QString relativePath = sourceDir.relativeFilePath(itemName); + if (itemInfo.isSymLink()) { + // Check if symlink target is inside copied directory + const QString linkTarget = itemInfo.symLinkTarget(); + if (linkTarget.startsWith(sourceDir.absolutePath())) { + // create symlink to copied location + const QString linkTargetRelative = sourceDir.relativeFilePath(linkTarget); + QFile(targetDir.absoluteFilePath(linkTargetRelative)) + .link(targetDir.absoluteFilePath(relativePath)); + } else { + // create symlink pointing to original location + QFile(linkTarget).link(targetDir.absoluteFilePath(relativePath)); + } + // add file entry + autoPush.m_files.prepend(targetDir.absoluteFilePath(relativePath)); + emit outputTextChanged(autoPush.m_files.first()); + } else if (itemInfo.isDir()) { + if (!targetDir.mkpath(targetDir.absoluteFilePath(relativePath))) { + setError(InvalidArguments); + setErrorString(tr("Could not create %0").arg(targetDir.absoluteFilePath(relativePath))); + return false; + } + } else { + if (!QFile::copy(sourceDir.absoluteFilePath(itemName), targetDir.absoluteFilePath(relativePath))) { + setError(InvalidArguments); + setErrorString(tr("Could not copy %0 to %1").arg(sourceDir.absoluteFilePath(itemName)) + .arg(targetDir.absoluteFilePath(relativePath))); + return false; + } + autoPush.m_files.prepend(targetDir.absoluteFilePath(relativePath)); + emit outputTextChanged(autoPush.m_files.first()); + } + } + return true; +} + +bool CopyDirectoryOperation::undoOperation() +{ + Q_ASSERT(arguments().count() == 2); + + QDir dir; + const QStringList files = value(QLatin1String("files")).toStringList(); + foreach (const QString &file, files) { + if (!QFile::remove(file)) { + setError(InvalidArguments); + setErrorString(tr("Could not remove %0").arg(file)); + return false; + } + dir.rmpath(QFileInfo(file).absolutePath()); + emit outputTextChanged(file); + } + + setValue(QLatin1String("files"), QStringList()); + return true; +} + +bool CopyDirectoryOperation::testOperation() +{ + return true; +} + +Operation *CopyDirectoryOperation::clone() const +{ + return new CopyDirectoryOperation(); +} diff --git a/src/libs/installer/copydirectoryoperation.h b/src/libs/installer/copydirectoryoperation.h new file mode 100644 index 000000000..5b2067cc4 --- /dev/null +++ b/src/libs/installer/copydirectoryoperation.h @@ -0,0 +1,61 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef COPYDIRECTORYOPERATION_H +#define COPYDIRECTORYOPERATION_H + +#include "qinstallerglobal.h" + +#include <QtCore/QObject> + +namespace QInstaller { + +class INSTALLER_EXPORT CopyDirectoryOperation : public QObject, public Operation +{ + Q_OBJECT + +public: + CopyDirectoryOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation *clone() const; + +Q_SIGNALS: + void outputTextChanged(const QString &progress); +}; + +} + +#endif diff --git a/src/libs/installer/createdesktopentryoperation.cpp b/src/libs/installer/createdesktopentryoperation.cpp new file mode 100644 index 000000000..feda777cc --- /dev/null +++ b/src/libs/installer/createdesktopentryoperation.cpp @@ -0,0 +1,204 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ +#include "createdesktopentryoperation.h" + +#include "errors.h" +#include "fileutils.h" + +#include <QtCore/QDir> +#include <QtCore/QFile> +#include <QtCore/QFileInfo> +#include <QtCore/QTextStream> +#include <QtCore/QProcess> +#if QT_VERSION >= 0x040600 +# include <QtCore/QProcessEnvironment> +#endif + +using namespace QInstaller; + +QString CreateDesktopEntryOperation::absoluteFileName() +{ + const QString filename = arguments().first(); + + // give filename is already absolute + if (QFileInfo(filename).isAbsolute()) + return filename; + + // we're not searching for the first time, let's re-use the old value + if (hasValue(QLatin1String("directory"))) + return QDir(value(QLatin1String("directory")).toString()).absoluteFilePath(filename); + + const QProcessEnvironment env; + QStringList XDG_DATA_DIRS = env.value(QLatin1String("XDG_DATA_DIRS")).split(QLatin1Char(':'), + QString::SkipEmptyParts); + QStringList XDG_DATA_HOME = env.value(QLatin1String("XDG_DATA_HOME")).split(QLatin1Char(':'), + QString::SkipEmptyParts); + + XDG_DATA_DIRS.push_back(QLatin1String("/usr/share")); // default path + XDG_DATA_HOME.push_back(QDir::home().absoluteFilePath(QLatin1String(".local/share"))); // default path + + const QStringList directories = XDG_DATA_DIRS + XDG_DATA_HOME; + QString directory; + for (QStringList::const_iterator it = directories.begin(); it != directories.end(); ++it) { + if (it->isEmpty()) + continue; + + directory = QDir(*it).absoluteFilePath(QLatin1String("applications")); + QDir dir(directory); + + // let's see wheter this dir exists or we're able to create it + if (!dir.exists() && !QDir().mkpath(directory)) + continue; + + // we just try wheter we're able to open the file in ReadWrite + QFile file(QDir(directory).absoluteFilePath(filename)); + const bool existed = file.exists(); + if (!file.open(QIODevice::ReadWrite)) + continue; + file.close(); + if (!existed) + file.remove(); + break; + } + + if (!QDir(directory).exists()) + QDir().mkpath(directory); + + setValue(QLatin1String("directory"), directory); + + return QDir(directory).absoluteFilePath(filename); +} + +/* +TRANSLATOR QInstaller::CreateDesktopEntryOperation +*/ + +CreateDesktopEntryOperation::CreateDesktopEntryOperation() +{ + setName(QLatin1String("CreateDesktopEntry")); +} + +CreateDesktopEntryOperation::~CreateDesktopEntryOperation() +{ + deleteFileNowOrLater(value(QLatin1String("backupOfExistingDesktopEntry")).toString()); +} + +void CreateDesktopEntryOperation::backup() +{ + const QString filename = absoluteFileName(); + if (!QFile::exists(filename)) + return; + + try { + setValue(QLatin1String("backupOfExistingDesktopEntry"), generateTemporaryFileName(filename)); + } catch (const QInstaller::Error &e) { + setErrorString(e.message()); + return; + } + + if (!QFile::copy(filename, value(QLatin1String("backupOfExistingDesktopEntry")).toString())) + setErrorString(QObject::tr("Could not backup file %1").arg(filename)); +} + +bool CreateDesktopEntryOperation::performOperation() +{ + const QStringList args = arguments(); + if (args.count() != 2) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in %0: %1 arguments given, 2 expected.").arg(name()).arg(args + .count())); + return false; + } + + const QString filename = absoluteFileName(); + const QString &values = args[1]; + + if (QFile::exists(filename) && !deleteFileNowOrLater(filename)) { + setError(UserDefinedError); + setErrorString(tr("Failed to overwrite %1").arg(filename)); + return false; + } + + QFile file(filename); + if(!file.open(QIODevice::WriteOnly)) { + setError(UserDefinedError); + setErrorString(tr("Could not write Desktop Entry at %1").arg(filename)); + return false; + } + + QFile::setPermissions(filename, QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::ReadGroup + | QFile::ReadOther); + + QTextStream stream(&file); + stream.setCodec("UTF-8"); + stream << QLatin1String("[Desktop Entry]") << endl; + stream << QLatin1String("Encoding=UTF-8") << endl; + + // Type=Application\nExec=qtcreator\nPath=... + const QStringList pairs = values.split(QLatin1Char('\n')); + for (QStringList::const_iterator it = pairs.begin(); it != pairs.end(); ++it) + stream << *it << endl; + + return true; +} + +bool CreateDesktopEntryOperation::undoOperation() +{ + const QString filename = absoluteFileName(); + + // first remove the link + if (!deleteFileNowOrLater(filename)) { + setErrorString(QObject::tr("Could not delete file %1").arg(filename)); + return false; + } + + if (!hasValue(QLatin1String("backupOfExistingDesktopEntry"))) + return true; + + const QString backupOfExistingDesktopEntry = value(QLatin1String("backupOfExistingDesktopEntry")).toString(); + const bool success = QFile::copy(backupOfExistingDesktopEntry, filename) + && deleteFileNowOrLater(backupOfExistingDesktopEntry); + if (!success) + setErrorString(QObject::tr("Could not restore backup file into %1").arg(filename)); + + return success; +} + +bool CreateDesktopEntryOperation::testOperation() +{ + return true; +} + +Operation *CreateDesktopEntryOperation::clone() const +{ + return new CreateDesktopEntryOperation(); +} diff --git a/src/libs/installer/createdesktopentryoperation.h b/src/libs/installer/createdesktopentryoperation.h new file mode 100644 index 000000000..b87e317f7 --- /dev/null +++ b/src/libs/installer/createdesktopentryoperation.h @@ -0,0 +1,57 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef CREATEDESKTOPENTRYOPERATION_H +#define CREATEDESKTOPENTRYOPERATION_H + +#include "qinstallerglobal.h" + +namespace QInstaller { + +class INSTALLER_EXPORT CreateDesktopEntryOperation : public Operation +{ +public: + CreateDesktopEntryOperation(); + ~CreateDesktopEntryOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation* clone() const; + + QString absoluteFileName(); +}; + +} + +#endif diff --git a/src/libs/installer/createlocalrepositoryoperation.cpp b/src/libs/installer/createlocalrepositoryoperation.cpp new file mode 100644 index 000000000..a0527f432 --- /dev/null +++ b/src/libs/installer/createlocalrepositoryoperation.cpp @@ -0,0 +1,384 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ +#include "createlocalrepositoryoperation.h" + +#include "binaryformat.h" +#include "errors.h" +#include "fileutils.h" +#include "copydirectoryoperation.h" +#include "lib7z_facade.h" +#include "packagemanagercore.h" + +#include "kdupdaterupdateoperations.h" + +#include <QtCore/QDir> +#include <QtCore/QDirIterator> + +#include <cerrno> + +namespace QInstaller { + + +// -- AutoHelper + +class AutoHelper +{ +public: + AutoHelper(CreateLocalRepositoryOperation *op) + : m_op(op) + { + } + + virtual ~AutoHelper() + { + m_op->emitFullProgress(); + m_op->setValue(QLatin1String("files"), m_files); + } + + QStringList m_files; + CreateLocalRepositoryOperation *m_op; +}; + + +// statics + +namespace Static { + +static void fixPermissions(const QString &repoPath) +{ + QDirIterator it(repoPath, QDirIterator::Subdirectories); + while (it.hasNext() && !it.next().isEmpty()) { + if (!it.fileInfo().isFile()) + continue; + + if (!QFile::setPermissions(it.filePath(), QFile::ReadOwner | QFile::WriteOwner + | QFile::ReadUser | QFile::WriteUser | QFile::ReadGroup | QFile::ReadOther)) { + throw Error(CreateLocalRepositoryOperation::tr("Could not set file permissions %1!") + .arg(it.filePath())); + } + } +} + +static void removeDirectory(const QString &path, AutoHelper *const helper) +{ + QInstaller::removeDirectory(path); + QStringList files = helper->m_files.filter(path); + foreach (const QString &file, files) + helper->m_files.removeAll(file); +} + +static void removeFiles(const QString &path, AutoHelper *const helper) +{ + const QFileInfoList entries = QDir(path).entryInfoList(QDir::AllEntries | QDir::Hidden); + foreach (const QFileInfo &fi, entries) { + if (fi.isSymLink() || fi.isFile()) { + QFile f(fi.filePath()); + if (!f.remove()) + throw Error(QObject::tr("Could not remove file %1: %2").arg(f.fileName(), f.errorString())); + helper->m_files.removeAll(f.fileName()); + } + } +} + +static QString createArchive(const QString repoPath, const QString &sourceDir, const QString &version + , AutoHelper *const helper) +{ + const QString fileName = QString::fromLatin1("/%1meta.7z").arg(version); + + QFile archive(repoPath + fileName); + QInstaller::openForWrite(&archive, archive.fileName()); + Lib7z::createArchive(&archive, QStringList() << sourceDir); + removeFiles(sourceDir, helper); // cleanup the files we compressed + if (!archive.rename(sourceDir + fileName)) { + throw Error(CreateLocalRepositoryOperation::tr("Could not move file %1 to %2. Error: %3").arg(archive + .fileName(), sourceDir + fileName, archive.errorString())); + } + return archive.fileName(); +} + +} // namespace Statics + + +// -- CreateLocalRepositoryOperation + +CreateLocalRepositoryOperation::CreateLocalRepositoryOperation() +{ + setName(QLatin1String("CreateLocalRepository")); +} + +void CreateLocalRepositoryOperation::backup() +{ +} + +bool CreateLocalRepositoryOperation::performOperation() +{ + AutoHelper helper(this); + emit progressChanged(0.0); + + const QStringList args = arguments(); + + if (args.count() != 2) { + emit progressChanged(1.0); + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in %0: %1 arguments given, 2 expected.").arg(name()) + .arg(args.count())); + return false; + } + + QString repoPath; + try { + QString binaryPath = QFileInfo(args.at(0)).absoluteFilePath(); + // Note the "/" at the end, important to make copy directory operation behave well + repoPath = QFileInfo(args.at(1)).absoluteFilePath() + QLatin1String("/repository/"); + + // if we're running as installer and install into an existing target, remove possible previous repos + PackageManagerCore *core = qVariantValue<PackageManagerCore*>(value(QLatin1String("installer"))); + if (core && core->isOfflineOnly() && QFile::exists(repoPath)) { + Static::fixPermissions(repoPath); + QInstaller::removeDirectory(repoPath); + } + + // create the local repository target dir + KDUpdater::MkdirOperation mkDirOp; + mkDirOp.setArguments(QStringList() << repoPath); + mkDirOp.backup(); + if (!mkDirOp.performOperation()) { + setError(mkDirOp.error()); + setErrorString(mkDirOp.errorString()); + return false; + } + setValue(QLatin1String("createddir"), mkDirOp.value(QLatin1String("createddir"))); + + // copy the whole meta data into local repository + CopyDirectoryOperation copyDirOp; + copyDirOp.setArguments(QStringList() << QLatin1String(":/metadata/") << repoPath); + connect(©DirOp, SIGNAL(outputTextChanged(QString)), this, SIGNAL(outputTextChanged(QString))); + + const bool success = copyDirOp.performOperation(); + helper.m_files = copyDirOp.value(QLatin1String("files")).toStringList(); + if (!success) { + setError(copyDirOp.error()); + setErrorString(copyDirOp.errorString()); + return false; + } + + emit progressChanged(0.25); + + // we need to fix the folder and file permissions here, as copying from read only resource file + // systems sets all permissions to a completely bogus value... + Static::fixPermissions(repoPath); + + // open the updates xml file we previously copied + QFile updatesXml(repoPath + QLatin1String("Updates.xml")); + if (!updatesXml.exists() || !updatesXml.open(QIODevice::ReadOnly)) + throw QInstaller::Error(tr("Could not open file: %1").arg(updatesXml.fileName())); + + // read the content of the updates xml + QString error; + QDomDocument doc; + if (!doc.setContent(&updatesXml, &error)) + throw QInstaller::Error(tr("Could not read: %1. Error: %2").arg(updatesXml.fileName(), error)); + + // build for each available package a name - version mapping + QHash<QString, QString> versionMap; + const QDomElement root = doc.documentElement(); + const QDomNodeList rootChildNodes = root.childNodes(); + for (int i = 0; i < rootChildNodes.count(); ++i) { + const QDomElement element = rootChildNodes.at(i).toElement(); + if (element.isNull()) + continue; + + QString name, version; + if (element.tagName() == QLatin1String("PackageUpdate")) { + const QDomNodeList elementChildNodes = element.childNodes(); + for (int j = 0; j < elementChildNodes.count(); ++j) { + const QDomElement e = elementChildNodes.at(j).toElement(); + if (e.tagName() == QLatin1String("Name")) + name = e.text(); + else if (e.tagName() == QLatin1String("Version")) + version = e.text(); + } + versionMap.insert(name, version); + } + } + + emit progressChanged(0.50); + + QSharedPointer<QFile> file(new QFile(binaryPath)); + if (!file->open(QIODevice::ReadOnly)) { + throw QInstaller::Error(tr("Could not open file: %1. Error: %2").arg(file->fileName(), + file->errorString())); + } + + // start to read the binary layout + BinaryLayout bl = BinaryContent::readBinaryLayout(file.data(), findMagicCookie(file.data(), + QInstaller::MagicCookie)); + + // calculate the offset of the component index start inside the binary + const qint64 resourceOffsetAndLengtSize = 2 * sizeof(qint64); + const qint64 resourceSectionSize = resourceOffsetAndLengtSize * bl.resourceCount; + file->seek(bl.endOfData - bl.indexSize - resourceSectionSize - resourceOffsetAndLengtSize); + + const qint64 dataBlockStart = bl.endOfData - bl.dataBlockSize; + file->seek(retrieveInt64(file.data()) + dataBlockStart); + QInstallerCreator::ComponentIndex componentIndex = QInstallerCreator::ComponentIndex::read(file, + dataBlockStart); + + QDirIterator it(repoPath, QDirIterator::Subdirectories); + while (it.hasNext() && !it.next().isEmpty()) { + if (it.fileInfo().isDir()) { + const QString fileName = it.fileName(); + const QString absoluteTargetPath = QDir(repoPath).absoluteFilePath(fileName); + + // zip the meta files that come with the offline installer + if (versionMap.contains(fileName)) { + helper.m_files.prepend(Static::createArchive(repoPath, absoluteTargetPath, + versionMap.value(fileName), &helper)); + versionMap.remove(fileName); + emit outputTextChanged(helper.m_files.first()); + } + + // copy the 7z files that are inside the component index into the target + QInstallerCreator::Component c = componentIndex.componentByName(fileName.toUtf8()); + if (c.archives().count()) { + QVector<QSharedPointer<QInstallerCreator::Archive> > archives = c.archives(); + foreach (const QSharedPointer<QInstallerCreator::Archive> &a, archives) { + if (!a->open(QIODevice::ReadOnly)) + continue; + + QFile target(absoluteTargetPath + QDir::separator() + QString::fromUtf8(a->name())); + QInstaller::openForWrite(&target, target.fileName()); + QInstaller::blockingCopy(a.data(), &target, a->size()); + helper.m_files.prepend(target.fileName()); + emit outputTextChanged(helper.m_files.first()); + } + } + } + } + + emit progressChanged(0.75); + + QDir repo(repoPath); + if (!versionMap.isEmpty()) { + // offline installers might miss possible old components + foreach (const QString &dir, versionMap.keys()) { + const QString missingDir = repoPath + dir; + if (!repo.mkpath(missingDir)) + throw QInstaller::Error(tr("Could not create target dir: %1.").arg(missingDir)); + helper.m_files.prepend(Static::createArchive(repoPath, missingDir, versionMap.value(dir) + , &helper)); + emit outputTextChanged(helper.m_files.first()); + } + } + + try { + // remove these, if we fail it doesn't hurt + Static::removeDirectory(QDir::cleanPath(repoPath + QLatin1String("/installer-config")), + &helper); + Static::removeDirectory(QDir::cleanPath(repoPath + QLatin1String("/config")), &helper); + const QStringList files = repo.entryList(QStringList() << QLatin1String("*.qrc"), QDir::Files); + foreach (const QString &file, files) { + if (repo.remove(file)) + helper.m_files.removeAll(QDir::cleanPath(repoPath + file)); + } + } catch (...) {} + setValue(QLatin1String("local-repo"), repoPath); + } catch (const Lib7z::SevenZipException &e) { + setError(UserDefinedError); + setErrorString(e.message()); + return false; + } catch (const QInstaller::Error &e) { + setError(UserDefinedError); + setErrorString(e.message()); + return false; + } catch (...) { + setError(UserDefinedError); + setErrorString(tr("Unknown exception caught: %1.").arg(QLatin1String(Q_FUNC_INFO))); + return false; + } + return true; +} + +bool CreateLocalRepositoryOperation::undoOperation() +{ + Q_ASSERT(arguments().count() == 2); + + AutoHelper _(this); + emit progressChanged(0.0); + + QDir dir; + const QStringList files = value(QLatin1String("files")).toStringList(); + foreach (const QString &file, files) { + emit outputTextChanged(tr("Removing file: %0").arg(file)); + if (!QFile::remove(file)) { + setError(InvalidArguments); + setErrorString(tr("Could not remove %0.").arg(file)); + return false; + } + dir.rmpath(QFileInfo(file).absolutePath()); + } + setValue(QLatin1String("files"), QStringList()); + + QDir createdDir = QDir(value(QLatin1String("createddir")).toString()); + if (createdDir == QDir::root() || !createdDir.exists()) + return true; + + QFile::remove(createdDir.path() + QLatin1String("/.DS_Store")); + QFile::remove(createdDir.path() + QLatin1String("/Thumbs.db")); + + errno = 0; + const bool result = QDir::root().rmdir(createdDir.path()); + if (!result) { + setError(UserDefinedError, tr("Cannot remove directory %1: %2").arg(createdDir.path(), + QLatin1String(strerror(errno)))); + } + setValue(QLatin1String("files"), QStringList()); + + return result; +} + +bool CreateLocalRepositoryOperation::testOperation() +{ + return true; +} + +Operation *CreateLocalRepositoryOperation::clone() const +{ + return new CreateLocalRepositoryOperation(); +} + +void CreateLocalRepositoryOperation::emitFullProgress() +{ + emit progressChanged(1.0); +} + +} // namespace QInstaller diff --git a/src/libs/installer/createlocalrepositoryoperation.h b/src/libs/installer/createlocalrepositoryoperation.h new file mode 100644 index 000000000..928e2959e --- /dev/null +++ b/src/libs/installer/createlocalrepositoryoperation.h @@ -0,0 +1,68 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef CREATELOCALREPOSITORYOPERATION_H +#define CREATELOCALREPOSITORYOPERATION_H + +#include "qinstallerglobal.h" + +#include <QtCore/QObject> + +namespace QInstaller { + +class AutoHelper; + +class INSTALLER_EXPORT CreateLocalRepositoryOperation : public QObject, public Operation +{ + Q_OBJECT + friend class AutoHelper; + +public: + CreateLocalRepositoryOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation *clone() const; + +signals: + void progressChanged(double progress); + void outputTextChanged(const QString &message); + +private: + void emitFullProgress(); +}; + +} // namespace QInstaller + +#endif // CREATELOCALREPOSITORYOPERATION_H diff --git a/src/libs/installer/createshortcutoperation.cpp b/src/libs/installer/createshortcutoperation.cpp new file mode 100644 index 000000000..bd0608233 --- /dev/null +++ b/src/libs/installer/createshortcutoperation.cpp @@ -0,0 +1,219 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ +#include "createshortcutoperation.h" + +#include "errors.h" +#include "fileutils.h" + +#include "kdupdaterapplication.h" +#include "kdupdaterpackagesinfo.h" + +#include <QtCore/QDir> +#include <QtCore/QFileInfo> +#include <QtCore/QTemporaryFile> + +#include <algorithm> +#include <cerrno> + +#ifdef Q_WS_WIN +# include <windows.h> +# include <shlobj.h> +#endif + +using namespace QInstaller; + +static bool createLink(QString fileName, QString linkName, QString workingDir, QString arguments = QString()) +{ +#ifdef Q_WS_WIN + bool ret = false; + fileName = QDir::toNativeSeparators(fileName); + linkName = QDir::toNativeSeparators(linkName); + if (workingDir.isEmpty()) + workingDir = QFileInfo(fileName).absolutePath(); + workingDir = QDir::toNativeSeparators(workingDir); + + //### assume that they add .lnk + + IShellLink *psl; + bool neededCoInit = false; + + HRESULT hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void **)&psl); + + if (hres == CO_E_NOTINITIALIZED) { // COM was not initialized + neededCoInit = true; + CoInitialize(NULL); + hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void **)&psl); + } + + if (SUCCEEDED(hres)) { + hres = psl->SetPath((wchar_t *)fileName.utf16()); + if (SUCCEEDED(hres) && !arguments.isNull()) + hres = psl->SetArguments((wchar_t*)arguments.utf16()); + if (SUCCEEDED(hres)) { + hres = psl->SetWorkingDirectory((wchar_t *)workingDir.utf16()); + if (SUCCEEDED(hres)) { + IPersistFile *ppf; + hres = psl->QueryInterface(IID_IPersistFile, (void **)&ppf); + if (SUCCEEDED(hres)) { + hres = ppf->Save((wchar_t*)linkName.utf16(), TRUE); + if (SUCCEEDED(hres)) + ret = true; + ppf->Release(); + } + } + } + psl->Release(); + } + + if (neededCoInit) + CoUninitialize(); + + return ret; +#else + Q_UNUSED(workingDir) + Q_UNUSED(arguments) + return QFile::link(fileName, linkName); +#endif +} + +/* +TRANSLATOR QInstaller::CreateShortcutOperation +*/ + +CreateShortcutOperation::CreateShortcutOperation() +{ + setName(QLatin1String("CreateShortcut")); +} + +static bool isWorkingDirOption(const QString &s) +{ + return s.startsWith(QLatin1String("workingDirectory=")); +} + +static QString takeWorkingDirArgument(QStringList &args) +{ + QString workingDir; + // if the args contain an option in the form "workingDirectory=...", find it and consume it + QStringList::iterator wdiropt = std::find_if(args.begin(), args.end(), isWorkingDirOption); + if (wdiropt != args.end()) { + workingDir = wdiropt->mid(QString::fromLatin1("workingDirectory=").size()); + args.erase(wdiropt); + } + return workingDir; +} + +void CreateShortcutOperation::backup() +{ +} + +bool CreateShortcutOperation::performOperation() +{ + QStringList args = arguments(); + const QString workingDir = takeWorkingDirArgument(args); + + if (args.count() != 2 && args.count() != 3) { + setError(InvalidArguments); + setErrorString(QObject::tr("Invalid arguments: %1 arguments given, 2 or 3 expected (optional: " + "\"workingDirectory=...\").").arg(args.count())); + return false; + } + + const QString& linkTarget = args.at(0); + const QString& linkLocation = args.at(1); + const QString targetArguments = args.value(2); //used value because it could be not existing + + const QString linkPath = QFileInfo(linkLocation).absolutePath(); + + const bool linkPathAlreadyExists = QDir(linkPath).exists(); + const bool created = linkPathAlreadyExists || QDir::root().mkpath(linkPath); + + if (!created) { + setError(UserDefinedError); + setErrorString(tr("Could not create folder %1: %2.").arg(QDir::toNativeSeparators(linkPath), + QLatin1String(strerror(errno)))); + return false; + } + + + //remove a possible existing older one + QString errorString; + if (QFile::exists(linkLocation) && !deleteFileNowOrLater(linkLocation, &errorString)) { + setError(UserDefinedError); + setErrorString(QObject::tr("Failed to overwrite %1: %2").arg(QDir::toNativeSeparators(linkLocation), + errorString)); + return false; + } + + const bool linked = createLink(linkTarget, linkLocation, workingDir, targetArguments); + if (!linked) { + setError(UserDefinedError); + setErrorString(tr("Could not create link %1: %2").arg(QDir::toNativeSeparators(linkLocation), + qt_error_string())); + return false; + } + return true; +} + +bool CreateShortcutOperation::undoOperation() +{ + const QStringList args = arguments(); + + const QString& linkLocation = args.at(1); + + // first remove the link + if (!deleteFileNowOrLater(linkLocation)) + qDebug() << "Can't delete:" << linkLocation; + + const QString linkPath = QFileInfo(linkLocation).absolutePath(); + + QStringList pathParts = QString(linkPath).remove(QDir::homePath()).split(QLatin1String("/")); + for (int i = pathParts.count(); i > 0; --i) { + QString possibleToDeleteDir = QDir::homePath() + QStringList(pathParts.mid(0, i)).join(QLatin1String("/")); + removeSystemGeneratedFiles(possibleToDeleteDir); + if (!possibleToDeleteDir.isEmpty() && QDir().rmdir(possibleToDeleteDir)) + qDebug() << "Deleted directory:" << possibleToDeleteDir; + else + break; + } + + return true; +} + +bool CreateShortcutOperation::testOperation() +{ + return true; +} + +Operation *CreateShortcutOperation::clone() const +{ + return new CreateShortcutOperation(); +} diff --git a/src/libs/installer/createshortcutoperation.h b/src/libs/installer/createshortcutoperation.h new file mode 100644 index 000000000..ab8a6b096 --- /dev/null +++ b/src/libs/installer/createshortcutoperation.h @@ -0,0 +1,54 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef CREATESHORTCUTOPERATION_H +#define CREATESHORTCUTOPERATION_H + +#include "qinstallerglobal.h" + +namespace QInstaller { + +class INSTALLER_EXPORT CreateShortcutOperation : public Operation +{ +public: + CreateShortcutOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation *clone() const; +}; + +} + +#endif diff --git a/src/libs/installer/downloadarchivesjob.cpp b/src/libs/installer/downloadarchivesjob.cpp new file mode 100644 index 000000000..f33108442 --- /dev/null +++ b/src/libs/installer/downloadarchivesjob.cpp @@ -0,0 +1,340 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ +#include "downloadarchivesjob.h" + +#include "binaryformatenginehandler.h" +#include "component.h" +#include "messageboxhandler.h" +#include "packagemanagercore.h" + +#include "kdupdaterfiledownloader.h" +#include "kdupdaterfiledownloaderfactory.h" + +#include <QtCore/QFile> +#include <QtCore/QTimerEvent> + +using namespace QInstaller; +using namespace KDUpdater; + + +/*! + Creates a new DownloadArchivesJob with \a parent. +*/ +DownloadArchivesJob::DownloadArchivesJob(PackageManagerCore *core) + : KDJob(core), + m_core(core), + m_downloader(0), + m_archivesDownloaded(0), + m_archivesToDownloadCount(0), + m_canceled(false), + m_lastFileProgress(0), + m_progressChangedTimerId(0) +{ + setCapabilities(Cancelable); +} + +/*! + Destroys the DownloadArchivesJob. + All temporary files get deleted. +*/ +DownloadArchivesJob::~DownloadArchivesJob() +{ + foreach (const QString &fileName, m_temporaryFiles) { + QFile file(fileName); + if (file.exists() && !file.remove()) + qWarning("Could not delete file %s: %s", qPrintable(fileName), qPrintable(file.errorString())); + } + + if (m_downloader) + m_downloader->deleteLater(); +} + +/*! + Sets the archives to download. The first value of each pair contains the file name to register + the file in the installer's internal file system, the second one the source url. +*/ +void DownloadArchivesJob::setArchivesToDownload(const QList<QPair<QString, QString> > &archives) +{ + m_archivesToDownload = archives; + m_archivesToDownloadCount = archives.count(); +} + +/*! + \reimp +*/ +void DownloadArchivesJob::doStart() +{ + m_archivesDownloaded = 0; + fetchNextArchiveHash(); +} + +/*! + \reimp +*/ +void DownloadArchivesJob::doCancel() +{ + m_canceled = true; + if (m_downloader != 0) + m_downloader->cancelDownload(); +} + +void DownloadArchivesJob::fetchNextArchiveHash() +{ + if (m_core->testChecksum()) { + if (m_canceled) { + finishWithError(tr("Canceled")); + return; + } + + if (m_archivesToDownload.isEmpty()) { + emitFinished(); + return; + } + + if (m_downloader) + m_downloader->deleteLater(); + + m_downloader = setupDownloader(QLatin1String(".sha1")); + if (!m_downloader) { + m_archivesToDownload.removeFirst(); + QMetaObject::invokeMethod(this, "fetchNextArchiveHash", Qt::QueuedConnection); + return; + } + + connect(m_downloader, SIGNAL(downloadCompleted()), this, SLOT(finishedHashDownload()), + Qt::QueuedConnection); + m_downloader->download(); + } else { + QMetaObject::invokeMethod(this, "fetchNextArchive", Qt::QueuedConnection); + } +} + +void DownloadArchivesJob::finishedHashDownload() +{ + Q_ASSERT(m_downloader != 0); + + const QString tempFile = m_downloader->downloadedFileName(); + QFile sha1HashFile(tempFile); + if (sha1HashFile.open(QFile::ReadOnly)) + m_currentHash = sha1HashFile.readAll(); + else + finishWithError(tr("Downloading hash signature failed.")); + + m_temporaryFiles.insert(tempFile); + + fetchNextArchive(); +} + +/*! + Fetches the next archive and registers it in the installer. +*/ +void DownloadArchivesJob::fetchNextArchive() +{ + if (m_canceled) { + finishWithError(tr("Canceled")); + return; + } + + if (m_archivesToDownload.isEmpty()) { + emitFinished(); + return; + } + + if (m_downloader != 0) + m_downloader->deleteLater(); + + m_downloader = setupDownloader(); + if (!m_downloader) { + m_archivesToDownload.removeFirst(); + QMetaObject::invokeMethod(this, "fetchNextArchive", Qt::QueuedConnection); + return; + } + + emit progressChanged(double(m_archivesDownloaded) / m_archivesToDownloadCount); + connect(m_downloader, SIGNAL(downloadProgress(double)), this, SLOT(emitDownloadProgress(double))); + connect(m_downloader, SIGNAL(downloadCompleted()), this, SLOT(registerFile()), Qt::QueuedConnection); + + m_downloader->download(); +} + +/*! + Emits the global download progress during a single download in a lazy way (uses a timer to reduce to + much processChanged). +*/ +void DownloadArchivesJob::emitDownloadProgress(double progress) +{ + m_lastFileProgress = progress; + if (!m_progressChangedTimerId) + m_progressChangedTimerId = startTimer(5); +} + +/*! + This is used to reduce the progressChanged signals. +*/ +void DownloadArchivesJob::timerEvent(QTimerEvent *event) +{ + if (event->timerId() == m_progressChangedTimerId) { + killTimer(m_progressChangedTimerId); + m_progressChangedTimerId = 0; + emit progressChanged((double(m_archivesDownloaded) + m_lastFileProgress) / m_archivesToDownloadCount); + } +} + +/*! + Registers the just downloaded file in the intaller's file system. +*/ +void DownloadArchivesJob::registerFile() +{ + Q_ASSERT(m_downloader != 0); + + ++m_archivesDownloaded; + if (m_progressChangedTimerId) { + killTimer(m_progressChangedTimerId); + m_progressChangedTimerId = 0; + emit progressChanged(double(m_archivesDownloaded) / m_archivesToDownloadCount); + } + + const QString tempFile = m_downloader->downloadedFileName(); + if (m_core->testChecksum()) { + QFile archiveFile(tempFile); + if (archiveFile.open(QFile::ReadOnly)) { + static QByteArray buffer(1024 * 1024, '\0'); + QCryptographicHash hash(QCryptographicHash::Sha1); + while (true) { + const qint64 numRead = archiveFile.read(buffer.data(), buffer.size()); + if (numRead <= 0) + break; + hash.addData(buffer.constData(), numRead); + } + + const QByteArray archiveHash = hash.result().toHex(); + if ((archiveHash != m_currentHash) && (!m_canceled)) { + //TODO: Maybe we should try to download the file again automatically + const QMessageBox::Button res = + MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), + QLatin1String("DownloadError"), tr("Download Error"), tr("Hash verification while " + "downloading failed. This is a temporary error, please retry."), + QMessageBox::Retry | QMessageBox::Cancel, QMessageBox::Cancel); + + if (res == QMessageBox::Cancel) { + finishWithError(tr("Could not verify Hash")); + return; + } + + fetchNextArchiveHash(); + return; + } + } else { + finishWithError(tr("Could not open %1").arg(tempFile)); + } + } + + m_temporaryFiles.insert(tempFile); + const QPair<QString, QString> pair = m_archivesToDownload.takeFirst(); + QInstallerCreator::BinaryFormatEngineHandler::instance()->registerArchive(pair.first, tempFile); + + fetchNextArchiveHash(); +} + +void DownloadArchivesJob::downloadCanceled() +{ + emitFinishedWithError(KDJob::Canceled, m_downloader->errorString()); +} + +void DownloadArchivesJob::downloadFailed(const QString &error) +{ + if (m_canceled) + return; + + const QMessageBox::StandardButton b = + MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), + QLatin1String("archiveDownloadError"), tr("Download Error"), tr("Could not download archive: %1 : %2") + .arg(m_archivesToDownload.first().second, error), QMessageBox::Retry | QMessageBox::Cancel); + + if (b == QMessageBox::Retry) + QMetaObject::invokeMethod(this, "fetchNextArchiveHash", Qt::QueuedConnection); + else + downloadCanceled(); +} + +void DownloadArchivesJob::finishWithError(const QString &error) +{ + const FileDownloader *const dl = dynamic_cast<const FileDownloader*> (sender()); + const QString msg = tr("Could not fetch archives: %1\nError while loading %2"); + if (dl != 0) + emitFinishedWithError(QInstaller::DownloadError, msg.arg(error, dl->url().toString())); + else + emitFinishedWithError(QInstaller::DownloadError, msg.arg(error, m_downloader->url().toString())); +} + +KDUpdater::FileDownloader *DownloadArchivesJob::setupDownloader(const QString &prefix) +{ + KDUpdater::FileDownloader *downloader = 0; + const QFileInfo fi = QFileInfo(m_archivesToDownload.first().first); + const Component *const component = m_core->componentByName(QFileInfo(fi.path()).fileName()); + if (component) { + const QUrl url(m_archivesToDownload.first().second + prefix); + const QString &scheme = url.scheme(); + downloader = FileDownloaderFactory::instance().create(scheme, this); + + if (downloader) { + downloader->setUrl(url); + downloader->setAutoRemoveDownloadedFile(false); + + QAuthenticator auth; + auth.setUser(component->value(QLatin1String("username"))); + auth.setPassword(component->value(QLatin1String("password"))); + downloader->setAuthenticator(auth); + + connect(downloader, SIGNAL(downloadCanceled()), this, SLOT(downloadCanceled())); + connect(downloader, SIGNAL(downloadAborted(QString)), this, SLOT(downloadFailed(QString)), + Qt::QueuedConnection); + connect(downloader, SIGNAL(downloadStatus(QString)), this, SIGNAL(downloadStatusChanged(QString))); + + if (scheme == QLatin1String("http") || scheme == QLatin1String("ftp") || + scheme == QLatin1String("file")) { + downloader->setDownloadedFileName(component->localTempPath() + QLatin1String("/") + + component->name() + QLatin1String("/") + fi.fileName() + prefix); + } + + QString message = tr("Downloading archive hash for component: %1"); + if (prefix.isEmpty()) + message = tr("Downloading archive for component: %1"); + emit outputTextChanged(message.arg(component->displayName())); + } else { + emit outputTextChanged(tr("Scheme not supported: %1 (%2)").arg(scheme, url.toString())); + } + } else { + emit outputTextChanged(tr("Could not find component for: %1.").arg(QFileInfo(fi.path()).fileName())); + } + return downloader; +} diff --git a/src/libs/installer/downloadarchivesjob.h b/src/libs/installer/downloadarchivesjob.h new file mode 100644 index 000000000..1c0c9b272 --- /dev/null +++ b/src/libs/installer/downloadarchivesjob.h @@ -0,0 +1,104 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef DOWNLOADARCHIVESJOB_H +#define DOWNLOADARCHIVESJOB_H + +#include <kdjob.h> + +#include <QtCore/QPair> +#include <QtCore/QSet> + +QT_BEGIN_NAMESPACE +class QTimerEvent; +QT_END_NAMESPACE + +namespace KDUpdater { + class FileDownloader; +} + +namespace QInstaller { + +class MessageBoxHandler; +class PackageManagerCore; + +class DownloadArchivesJob : public KDJob +{ + Q_OBJECT + +public: + explicit DownloadArchivesJob(PackageManagerCore *core = 0); + ~DownloadArchivesJob(); + + void setArchivesToDownload(const QList<QPair<QString, QString> > &archives); + +Q_SIGNALS: + void progressChanged(double progress); + void outputTextChanged(const QString &progress); + void downloadStatusChanged(const QString &status); + +protected: + void doStart(); + void doCancel(); + void timerEvent(QTimerEvent *event); + +protected Q_SLOTS: + void registerFile(); + void downloadCanceled(); + void downloadFailed(const QString &error); + void finishWithError(const QString &error); + void fetchNextArchive(); + void fetchNextArchiveHash(); + void finishedHashDownload(); + void emitDownloadProgress(double progress); + +private: + KDUpdater::FileDownloader *setupDownloader(const QString &prefix = QString()); + +private: + PackageManagerCore *m_core; + KDUpdater::FileDownloader *m_downloader; + + int m_archivesDownloaded; + int m_archivesToDownloadCount; + QList<QPair<QString, QString> > m_archivesToDownload; + + bool m_canceled; + QSet<QString> m_temporaryFiles; + QByteArray m_currentHash; + double m_lastFileProgress; + int m_progressChangedTimerId; +}; + +} // namespace QInstaller + +#endif // DOWNLOADARCHIVESJOB_H diff --git a/src/libs/installer/elevatedexecuteoperation.cpp b/src/libs/installer/elevatedexecuteoperation.cpp new file mode 100644 index 000000000..1e1539222 --- /dev/null +++ b/src/libs/installer/elevatedexecuteoperation.cpp @@ -0,0 +1,276 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "elevatedexecuteoperation.h" + +#include "environment.h" +#include "qprocesswrapper.h" + +#include <QtCore/QThread> +#include <QtCore/QProcessEnvironment> +#include <QtCore/QDebug> + +using namespace QInstaller; + +class ElevatedExecuteOperation::Private +{ +public: + explicit Private(ElevatedExecuteOperation *qq) + : q(qq), process(0), showStandardError(false) + { + } + +private: + ElevatedExecuteOperation *const q; + +public: + void readProcessOutput(); + bool run(const QStringList &arguments); + + QProcessWrapper *process; + bool showStandardError; +}; + +ElevatedExecuteOperation::ElevatedExecuteOperation() + : d(new Private(this)) +{ + // this operation has to "overwrite" the Execute operation from KDUpdater + setName(QLatin1String("Execute")); +} + +ElevatedExecuteOperation::~ElevatedExecuteOperation() +{ + delete d; +} + +bool ElevatedExecuteOperation::performOperation() +{ + // This operation receives only one argument. It is the complete + // command line of the external program to execute. + if (arguments().isEmpty()) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in %1: %2 arguments given, at least 1 expected.").arg(name(), + QString::number(arguments().count()))); + return false; + } + QStringList args; + foreach (const QString &argument, arguments()) { + if (argument!=QLatin1String("UNDOEXECUTE")) + args.append(argument); + else + break; //we don't need the UNDOEXECUTE args here + } + + return d->run(args); +} + +bool ElevatedExecuteOperation::Private::run(const QStringList &arguments) +{ + QStringList args = arguments; + QString workingDirectory; + QStringList filteredWorkingDirectoryArgs = args.filter(QLatin1String("workingdirectory="), + Qt::CaseInsensitive); + if (!filteredWorkingDirectoryArgs.isEmpty()) { + QString workingDirectoryArgument = filteredWorkingDirectoryArgs.at(0); + workingDirectory = workingDirectoryArgument; + workingDirectory.replace(QLatin1String("workingdirectory="), QString(), Qt::CaseInsensitive); + args.removeAll(workingDirectoryArgument); + } + + + if (args.last().endsWith(QLatin1String("showStandardError"))) { + showStandardError = true; + args.pop_back(); + } + + 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.constBegin(); it != numbers.constEnd(); ++it) + allowedExitCodes.push_back(it->toInt()); + args.pop_front(); + } else { + allowedExitCodes.push_back(0); + } + + const QString callstr = args.join(QLatin1String(" ")); + + // unix style: when there's an ampersand after the command, it's started detached + if (args.count() >= 2 && args.last() == QLatin1String("&")) { + args.pop_back(); + const bool success = QProcessWrapper::startDetached(args.front(), args.mid(1)); + if (!success) { + q->setError(UserDefinedError); + q->setErrorString(tr("Execution failed: Could not start detached: \"%1\"").arg(callstr)); + } + return success; + } + + process = new QProcessWrapper(); + if (!workingDirectory.isEmpty()) { + process->setWorkingDirectory(workingDirectory); + qDebug() << "ElevatedExecuteOperation setWorkingDirectory:" << workingDirectory; + } + + QProcessEnvironment penv; + // there is no way to serialize a QProcessEnvironment properly other than per mangled QStringList: + // (i.e. no other way to list all keys) + process->setEnvironment(KDUpdater::Environment::instance().applyTo(penv).toStringList()); + + if (showStandardError) + process->setProcessChannelMode(QProcessWrapper::MergedChannels); + + connect(q, SIGNAL(cancelProcess()), process, SLOT(cancel())); + + //we still like the none blocking possibility to perform this operation without threads + QEventLoop loop; + if (QThread::currentThread() == qApp->thread()) { + QObject::connect(process, SIGNAL(finished(int, QProcess::ExitStatus)), &loop, SLOT(quit())); + } + //readProcessOutput should only called from this current Thread -> Qt::DirectConnection + QObject::connect(process, SIGNAL(readyRead()), q, SLOT(readProcessOutput()), Qt::DirectConnection); +#ifdef Q_OS_WIN + if (args.count() == 1) { + process->setNativeArguments(args.front()); + qDebug() << "ElevatedExecuteOperation setNativeArguments to start:" << args.front(); + process->start(QString(), QStringList()); + } else +#endif + { + process->start(args.front(), args.mid(1)); + } + qDebug() << args.front() << "started, arguments:" << QStringList(args.mid(1)).join(QLatin1String(" ")); + + bool success = false; + //we still like the none blocking possibility to perform this operation without threads + if (QThread::currentThread() == qApp->thread()) { + success = process->waitForStarted(); + } else { + success = process->waitForFinished(-1); + } + + bool returnValue = true; + if (!success) { + q->setError(UserDefinedError); + //TODO: pass errorString() through the wrapper */ + q->setErrorString(tr("Execution failed: Could not start: \"%1\"").arg(callstr)); + returnValue = false; + } + + if (QThread::currentThread() == qApp->thread()) { + if (process->state() != QProcessWrapper::NotRunning) { + loop.exec(); + } + readProcessOutput(); + } + + q->setValue(QLatin1String("ExitCode"), process->exitCode()); + + if (process->exitStatus() == QProcessWrapper::CrashExit) { + q->setError(UserDefinedError); + q->setErrorString(tr("Execution failed(Crash): \"%1\"").arg(callstr)); + returnValue = false; + } + + if (!allowedExitCodes.contains(process->exitCode())) { + q->setError(UserDefinedError); + q->setErrorString(tr("Execution failed(Unexpected exit code: %1): \"%2\"") + .arg(QString::number(process->exitCode()), callstr)); + returnValue = false; + } + + Q_ASSERT(process); + process->deleteLater(); + process = 0; + + return returnValue; +} + +/*! + Cancels the ElevatedExecuteOperation. This methods tries to terminate the process + gracefully by calling QProcessWrapper::terminate. After 10 seconds, the process gets killed. + */ +void ElevatedExecuteOperation::cancelOperation() +{ + emit cancelProcess(); +} + +void ElevatedExecuteOperation::Private::readProcessOutput() +{ + Q_ASSERT(process); + Q_ASSERT(QThread::currentThread() == process->thread()); + if (QThread::currentThread() != process->thread()) { + qDebug() << Q_FUNC_INFO << "can only be called from the same thread as the process is."; + } + const QByteArray output = process->readAll(); + if (!output.isEmpty()) { + qDebug() << output; + emit q->outputTextChanged(QString::fromLocal8Bit(output)); + } +} + + +bool ElevatedExecuteOperation::undoOperation() +{ + QStringList args; + bool found = false; + foreach (const QString &argument, arguments()) { + if (found) + args.append(argument); + else + found = argument == QLatin1String("UNDOEXECUTE"); + } + if (args.isEmpty()) + return true; + + return d->run(args); +} + +bool ElevatedExecuteOperation::testOperation() +{ + // TODO + return true; +} + +Operation *ElevatedExecuteOperation::clone() const +{ + return new ElevatedExecuteOperation; +} + +void ElevatedExecuteOperation::backup() +{ +} + + +#include "moc_elevatedexecuteoperation.cpp" diff --git a/src/libs/installer/elevatedexecuteoperation.h b/src/libs/installer/elevatedexecuteoperation.h new file mode 100644 index 000000000..db9575380 --- /dev/null +++ b/src/libs/installer/elevatedexecuteoperation.h @@ -0,0 +1,70 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef ELEVATEDEXECUTEOPERATION_H +#define ELEVATEDEXECUTEOPERATION_H + +#include "qinstallerglobal.h" + +namespace QInstaller { + +class INSTALLER_EXPORT ElevatedExecuteOperation : public QObject, public Operation +{ + Q_OBJECT + +public: + ElevatedExecuteOperation(); + ~ElevatedExecuteOperation(); + + virtual void backup(); + virtual bool performOperation(); + virtual bool undoOperation(); + virtual bool testOperation(); + virtual Operation *clone() const; + +Q_SIGNALS: + void cancelProcess(); + void outputTextChanged(const QString &text); + +public Q_SLOTS: + void cancelOperation(); + +private: + Q_PRIVATE_SLOT(d, void readProcessOutput()) + + class Private; + Private *d; +}; + +} // namespace + +#endif diff --git a/src/libs/installer/environmentvariablesoperation.cpp b/src/libs/installer/environmentvariablesoperation.cpp new file mode 100644 index 000000000..c71a250c9 --- /dev/null +++ b/src/libs/installer/environmentvariablesoperation.cpp @@ -0,0 +1,230 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "environmentvariablesoperation.h" +#include "qsettingswrapper.h" + +#include <stdlib.h> + +#include "environment.h" + +#ifdef Q_WS_WIN +# include <windows.h> +#endif + +using namespace QInstaller; +using namespace KDUpdater; + +/* +TRANSLATOR QInstaller::EnvironmentVariablesOperation +*/ + +EnvironmentVariableOperation::EnvironmentVariableOperation() +{ + setName(QLatin1String("EnvironmentVariable")); +} + +void EnvironmentVariableOperation::backup() +{ +} + +#ifdef Q_WS_WIN +static bool broadcastChange() { + // Use SendMessageTimeout to Broadcast a message to the whole system to update settings of all + // running applications. This is needed to activate the changes done above without logout+login. + // Note that cmd.exe does not respond to any WM_SETTINGCHANGE messages... + DWORD aResult = 0; + LRESULT sendresult = SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, + 0, (LPARAM) "Environment", SMTO_BLOCK | SMTO_ABORTIFHUNG, 5000, &aResult); + if (sendresult == 0 || aResult != 0) { + qWarning("Failed to broadcast a WM_SETTINGCHANGE message\n"); + return false; + } + + return true; +} +#endif + +namespace { + +template <typename SettingsType> +UpdateOperation::Error writeSetting(const QString ®Path, + const QString &name, + const QString &value, + QString *errorString, + QString *oldValue) +{ + oldValue->clear(); + SettingsType registry(regPath, QSettingsWrapper::NativeFormat); + if (!registry.isWritable()) { + *errorString = QObject::tr("Registry path %1 is not writable").arg(regPath); + return UpdateOperation::UserDefinedError; + } + + // remember old value for undo + *oldValue = registry.value(name).toString(); + + // set the new value + registry.setValue(name, value); + registry.sync(); + + if (registry.status() != QSettingsWrapper::NoError) { + *errorString = QObject::tr("Could not write to registry path %1").arg(regPath); + return UpdateOperation::UserDefinedError; + } + + return UpdateOperation::NoError; +} + +template <typename SettingsType> +UpdateOperation::Error undoSetting(const QString ®Path, + const QString &name, + const QString &value, + const QString &oldValue, + QString *errorString) +{ + QString actual; + { + SettingsType registry(regPath, QSettingsWrapper::NativeFormat); + actual = registry.value(name).toString(); + } + if (actual != value) //key changed, don't undo + return UpdateOperation::UserDefinedError; + QString dontcare; + return writeSetting<SettingsType>(regPath, name, oldValue, errorString, &dontcare); +} + +} // namespace + +bool EnvironmentVariableOperation::performOperation() +{ + if (arguments().count() < 2 || arguments().count() > 4) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in %0: %1 arguments given, 2-3 expected.") + .arg(name()).arg(arguments().count())); + return false; + } + + const QString name = arguments().at(0); + const QString value = arguments().at(1); + bool isPersistent = false; + +#ifdef Q_WS_WIN + isPersistent = arguments().count() >= 3 ? arguments().at(2) == QLatin1String("true") : true; + const bool isSystemWide = arguments().count() >= 4 ? arguments().at(3) == QLatin1String("true") : false; + QString oldvalue; + if (isPersistent) { + const QString regPath = isSystemWide ? QLatin1String("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet" + "\\Control\\Session Manager\\Environment") : QLatin1String("HKEY_CURRENT_USER\\Environment"); + + // write the name=value pair to the global environment + QString errorString; + + Error err = NoError; + + err = isSystemWide + ? writeSetting<QSettingsWrapper>(regPath, name, value, &errorString, &oldvalue) + : writeSetting<QSettingsWrapper>(regPath, name, value, &errorString, &oldvalue); + if (err != NoError) { + setError(err); + setErrorString(errorString); + return false; + } + const bool bret = broadcastChange(); + Q_UNUSED(bret); // this is not critical, so fall-through + setValue(QLatin1String("oldvalue"), oldvalue); + return true; + } +#endif + Q_ASSERT(!isPersistent); + Q_UNUSED(isPersistent) + + setValue(QLatin1String("oldvalue"), Environment::instance().value(name)); + Environment::instance().setTemporaryValue(name, value); + + return true; +} + +bool EnvironmentVariableOperation::undoOperation() +{ + if (arguments().count() < 2 || arguments().count() > 4) + return false; + + const QString name = arguments().at(0); + const QString value = arguments().at(1); + const QString oldvalue = this->value(QLatin1String("oldvalue")).toString(); + +#ifdef Q_WS_WIN + const bool isPersistent = arguments().count() >= 3 ? arguments().at(2) == QLatin1String("true") : true; +#else + const bool isPersistent = false; +#endif + + if (!isPersistent) { + const QString actual = Environment::instance().value(name); + const bool doUndo = actual == value; + if (doUndo) + Environment::instance().setTemporaryValue(name, oldvalue); + return doUndo; + } + +#ifdef Q_WS_WIN + const bool isSystemWide = arguments().count() >= 4 ? arguments().at(3) == QLatin1String("true") : false; + + const QString regPath = isSystemWide ? QLatin1String("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\" + "Control\\Session Manager\\Environment") : QLatin1String("HKEY_CURRENT_USER\\Environment"); + + QString errorString; + + const Error err = isSystemWide + ? undoSetting<QSettingsWrapper>(regPath, name, value, oldvalue, &errorString) + : undoSetting<QSettingsWrapper>(regPath, name, value, oldvalue, &errorString); + + if (err != NoError) { + setError(err); + setErrorString(errorString); + return false; + } +#endif + + return true; +} + +bool EnvironmentVariableOperation::testOperation() +{ + return true; +} + +Operation *EnvironmentVariableOperation::clone() const +{ + return new EnvironmentVariableOperation(); +} diff --git a/src/libs/installer/environmentvariablesoperation.h b/src/libs/installer/environmentvariablesoperation.h new file mode 100644 index 000000000..c8bc98c07 --- /dev/null +++ b/src/libs/installer/environmentvariablesoperation.h @@ -0,0 +1,54 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef ENVIRONMENTVARIABLESOPERATION_H +#define ENVIRONMENTVARIABLESOPERATION_H + +#include "qinstallerglobal.h" + +namespace QInstaller { + +class INSTALLER_EXPORT EnvironmentVariableOperation : public Operation +{ +public: + EnvironmentVariableOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation *clone() const; +}; + +} + +#endif diff --git a/src/libs/installer/errors.h b/src/libs/installer/errors.h new file mode 100644 index 000000000..09ad52b14 --- /dev/null +++ b/src/libs/installer/errors.h @@ -0,0 +1,59 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef ERRORS_H +#define ERRORS_H + +#include <QtCore/QDebug> +#include <QtCore/QString> + +#include <stdexcept> + +namespace QInstaller { + +class Error : public std::runtime_error +{ +public: + explicit Error(const QString &message) + : std::runtime_error(message.toStdString()) + , m_message (message) { qWarning() << "Error-Exception:" << message; } + virtual ~Error() throw() {} + + QString message() const { return m_message; } + +private: + QString m_message; +}; + +} + +#endif // ERRORS_H diff --git a/src/libs/installer/extractarchiveoperation.cpp b/src/libs/installer/extractarchiveoperation.cpp new file mode 100644 index 000000000..f07a91d5a --- /dev/null +++ b/src/libs/installer/extractarchiveoperation.cpp @@ -0,0 +1,149 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "extractarchiveoperation.h" +#include "extractarchiveoperation_p.h" + +#include <QtCore/QEventLoop> +#include <QtCore/QThread> +#include <QtCore/QThreadPool> + +using namespace QInstaller; + + +ExtractArchiveOperation::ExtractArchiveOperation() +{ + setName(QLatin1String("Extract")); +} + +void ExtractArchiveOperation::backup() +{ + // we need to backup on the fly... +} + +bool ExtractArchiveOperation::performOperation() +{ + const QStringList args = arguments(); + if (args.count() != 2) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in %0: %1 arguments given, 2 expected.").arg(name()).arg(args + .count())); + return false; + } + + const QString archivePath = args.first(); + const QString targetDir = args.at(1); + + Receiver receiver; + Callback callback; + + // usually we have to connect it as queued connection but then some blocking work is in the main thread + connect(&callback, SIGNAL(progressChanged(QString)), this, SLOT(slotProgressChanged(QString)), + Qt::DirectConnection); + + if (PackageManagerCore *core = this->value(QLatin1String("installer")).value<PackageManagerCore*>()) { + connect(core, SIGNAL(statusChanged(QInstaller::PackageManagerCore::Status)), &callback, + SLOT(statusChanged(QInstaller::PackageManagerCore::Status)), Qt::QueuedConnection); + } + + //Runnable is derived from QRunable which will be deleted by the ThreadPool -> no parent is needed + Runnable *runnable = new Runnable(archivePath, targetDir, &callback); + connect(runnable, SIGNAL(finished(bool,QString)), &receiver, SLOT(runnableFinished(bool,QString)), + Qt::QueuedConnection); + + QEventLoop loop; + connect(&receiver, SIGNAL(finished()), &loop, SLOT(quit())); + if (QThreadPool::globalInstance()->tryStart(runnable)) { + loop.exec(); + } else { + // in case there is no availabe thread we should call it directly this is more a hack + runnable->run(); + receiver.runnableFinished(true, QString()); + } + + typedef QPair<QString, QString> StringPair; + QVector<StringPair> backupFiles = callback.backupFiles; + + //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 StringPair &i, backupFiles) + deleteFileNowOrLater(i.second); + + if (!receiver.success) { + setError(UserDefinedError); + setErrorString(receiver.errorString); + return false; + } + return true; +} + +bool ExtractArchiveOperation::undoOperation() +{ + Q_ASSERT(arguments().count() == 2); + //const QString archivePath = arguments().first(); + //const QString targetDir = arguments().last(); + + const QStringList files = value(QLatin1String("files")).toStringList(); + + WorkerThread *const thread = new WorkerThread(this, files); + connect(thread, SIGNAL(outputTextChanged(QString)), this, SIGNAL(outputTextChanged(QString)), + Qt::QueuedConnection); + + QEventLoop loop; + connect(thread, SIGNAL(finished()), &loop, SLOT(quit()), Qt::QueuedConnection); + thread->start(); + loop.exec(); + thread->deleteLater(); + return true; +} + +bool ExtractArchiveOperation::testOperation() +{ + return true; +} + +Operation *ExtractArchiveOperation::clone() const +{ + return new ExtractArchiveOperation(); +} + +/*! + This slot is direct connected to the caller so please don't call it from another thread in the same time. +*/ +void ExtractArchiveOperation::slotProgressChanged(const QString &filename) +{ + QStringList files = value(QLatin1String("files")).toStringList(); + files.prepend(filename); + setValue(QLatin1String("files"), files); + emit outputTextChanged(filename); +} diff --git a/src/libs/installer/extractarchiveoperation.h b/src/libs/installer/extractarchiveoperation.h new file mode 100644 index 000000000..822a43510 --- /dev/null +++ b/src/libs/installer/extractarchiveoperation.h @@ -0,0 +1,70 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef EXTRACTARCHIVEOPERATION_H +#define EXTRACTARCHIVEOPERATION_H + +#include "qinstallerglobal.h" + +#include <QtCore/QObject> + +namespace QInstaller { + +class INSTALLER_EXPORT ExtractArchiveOperation : public QObject, public Operation +{ + Q_OBJECT + friend class WorkerThread; + +public: + ExtractArchiveOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation *clone() const; + +Q_SIGNALS: + void outputTextChanged(const QString &progress); + +private Q_SLOTS: + void slotProgressChanged(const QString &progress); + +private: + class Callback; + class Runnable; + class Receiver; +}; + +} + +#endif diff --git a/src/libs/installer/extractarchiveoperation_p.h b/src/libs/installer/extractarchiveoperation_p.h new file mode 100644 index 000000000..25c38e61e --- /dev/null +++ b/src/libs/installer/extractarchiveoperation_p.h @@ -0,0 +1,231 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ +#ifndef EXTRACTARCHIVEOPERATION_P_H +#define EXTRACTARCHIVEOPERATION_P_H + +#include "extractarchiveoperation.h" + +#include "fileutils.h" +#include "lib7z_facade.h" +#include "packagemanagercore.h" + +#include <QtCore/QDir> +#include <QtCore/QFile> +#include <QtCore/QPair> +#include <QtCore/QThread> +#include <QtCore/QVector> + +namespace QInstaller { + +class WorkerThread : public QThread +{ + Q_OBJECT +public: + WorkerThread(ExtractArchiveOperation *op, const QStringList &files, QObject *parent = 0) + : QThread(parent) + , m_files(files) + , m_op(op) + { + } + + void run() + { + ExtractArchiveOperation *const op = m_op;//dynamic_cast< ExtractArchiveOperation* >(parent()); + Q_ASSERT(op != 0); + + foreach (const QString &file, m_files) { + const QFileInfo fi(file); + emit outputTextChanged(file); + if (fi.isFile() || fi.isSymLink()) { + op->deleteFileNowOrLater(fi.absoluteFilePath()); + } else if (fi.isDir()) { + const QDir d = fi.dir(); + removeSystemGeneratedFiles(file); + d.rmdir(file); // directory may not exist + } + } + } + +Q_SIGNALS: + void outputTextChanged(const QString &filename); + +private: + QStringList m_files; + ExtractArchiveOperation *m_op; +}; + + +class ExtractArchiveOperation::Callback : public QObject, public Lib7z::ExtractCallback +{ + Q_OBJECT + +public: + HRESULT state; + bool createBackups; + QVector<QPair<QString, QString> > backupFiles; + + Callback() : state(S_OK), createBackups(true) {} + +Q_SIGNALS: + void progressChanged(const QString &filename); + +public Q_SLOTS: + void statusChanged(QInstaller::PackageManagerCore::Status status) + { + switch(status) { + case PackageManagerCore::Canceled: + state = E_ABORT; + break; + case PackageManagerCore::Failure: + state = E_FAIL; + break; + case PackageManagerCore::Unfinished: // fall through + case PackageManagerCore::Success: + case PackageManagerCore::Running: + //state = S_OK; + break; + } + } + +protected: + void setCurrentFile(const QString &filename) + { + emit progressChanged(QDir::toNativeSeparators(filename)); + } + + static QString 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 prepareForFile(const QString &filename) + { + if (!createBackups) + return true; + if (!QFile::exists(filename)) + return true; + const QString backup = generateBackupName(filename); + QFile f(filename); + const bool renamed = f.rename(backup); + if (f.exists() && !renamed) { + qCritical("Could not rename %s to %s: %s", qPrintable(filename), qPrintable(backup), + qPrintable(f.errorString())); + return false; + } + backupFiles.push_back(qMakePair(filename, backup)); + return true; + } + + HRESULT setCompleted(quint64 /*completed*/, quint64 /*total*/) + { + return state; + } +}; + +class ExtractArchiveOperation::Runnable : public QObject, public QRunnable +{ + Q_OBJECT + +public: + Runnable(const QString &archivePath_, const QString &targetDir_, ExtractArchiveOperation::Callback *callback_) + : QObject() + , QRunnable() + , archivePath(archivePath_) + , targetDir(targetDir_) + , callback(callback_) {} + + void run() + { + QFile archive(archivePath); + if (!archive.open(QIODevice::ReadOnly)) { + + emit finished(false, tr("Could not open %1 for reading: %2.").arg(archivePath, archive.errorString())); + return; + } + + try { + Lib7z::extractArchive(&archive, targetDir, callback); + emit finished(true, QString()); + } catch (const Lib7z::SevenZipException& e) { +#ifdef Q_WS_WIN + emit finished(false, tr("Error while extracting %1: %2. (Maybe the target dir(%3) is blocked by " + "another process.)").arg(archivePath, e.message(), targetDir)); +#else + emit finished(false, tr("Error while extracting %1: %2.").arg(archivePath, e.message())); +#endif + } catch (...) { + emit finished(false, tr("Unknown exception caught while extracting %1.").arg(archivePath)); + } + } + +Q_SIGNALS: + void finished(bool success, const QString &errorString); + +private: + const QString archivePath; + const QString targetDir; + ExtractArchiveOperation::Callback *const callback; +}; + + +class ExtractArchiveOperation::Receiver : public QObject +{ + Q_OBJECT +public: + explicit Receiver(QObject *parent = 0) + : QObject(parent) + , success(false) {} + +public Q_SLOTS: + void runnableFinished(bool ok, const QString &msg) + { + success = ok; + errorString = msg; + emit finished(); + } + +Q_SIGNALS: + void finished(); + +public: + bool success; + QString errorString; +}; + +} + +#endif // EXTRACTARCHIVEOPERATION_P_H diff --git a/src/libs/installer/fakestopprocessforupdateoperation.cpp b/src/libs/installer/fakestopprocessforupdateoperation.cpp new file mode 100644 index 000000000..9bfd6f35e --- /dev/null +++ b/src/libs/installer/fakestopprocessforupdateoperation.cpp @@ -0,0 +1,129 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "fakestopprocessforupdateoperation.h" + +#include <kdsysinfo.h> +#include <QtCore/QDir> + +#include <algorithm> + +using namespace KDUpdater; + +/*! + Copied from QInstaller with some adjustments + Return true, if a process with \a name is running. On Windows, the comparision is case-insensitive. +*/ +static bool isProcessRunning(const QString &name, const QList<ProcessInfo> &processes) +{ + for (QList<ProcessInfo>::const_iterator it = processes.constBegin(); it != processes.constEnd(); ++it) { + if (it->name.isEmpty()) + continue; + +#ifndef Q_WS_WIN + if (it->name == name) + return true; + const QFileInfo fi(it->name); + if (fi.fileName() == name || fi.baseName() == name) + return true; +#else + if (it->name.toLower() == name.toLower()) + return true; + if (it->name.toLower() == QDir::toNativeSeparators(name.toLower())) + return true; + const QFileInfo fi(it->name); + if (fi.fileName().toLower() == name.toLower() || fi.baseName().toLower() == name.toLower()) + return true; +#endif + } + return false; +} + +static QStringList checkRunningProcessesFromList(const QStringList &processList) +{ + const QList<ProcessInfo> allProcesses = runningProcesses(); + QStringList stillRunningProcesses; + foreach (const QString &process, processList) { + if (!process.isEmpty() && isProcessRunning(process, allProcesses)) + stillRunningProcesses.append(process); + } + return stillRunningProcesses; +} + +using namespace QInstaller; + +FakeStopProcessForUpdateOperation::FakeStopProcessForUpdateOperation() +{ + setName(QLatin1String("FakeStopProcessForUpdate")); +} + +void FakeStopProcessForUpdateOperation::backup() +{ +} + +bool FakeStopProcessForUpdateOperation::performOperation() +{ + return true; +} + +bool FakeStopProcessForUpdateOperation::undoOperation() +{ + setError(KDUpdater::UpdateOperation::NoError); + if (arguments().size() != 1) { + setError(KDUpdater::UpdateOperation::InvalidArguments, QObject::tr("Number of arguments does not " + "match : one is required")); + return false; + } + + QStringList processList = arguments()[0].split(QLatin1String(","), QString::SkipEmptyParts); + qSort(processList); + processList.erase(std::unique(processList.begin(), processList.end()), processList.end()); + if (!processList.isEmpty()) { + const QStringList processes = checkRunningProcessesFromList(processList); + if (!processes.isEmpty()) { + setError(KDUpdater::UpdateOperation::UserDefinedError, tr("These processes should be stopped to " + "continue:\n\n%1").arg(QDir::toNativeSeparators(processes.join(QLatin1String("\n"))))); + } + return false; + } + return true; +} + +bool FakeStopProcessForUpdateOperation::testOperation() +{ + return true; +} + +Operation *FakeStopProcessForUpdateOperation::clone() const +{ + return new FakeStopProcessForUpdateOperation(); +} diff --git a/src/libs/installer/fakestopprocessforupdateoperation.h b/src/libs/installer/fakestopprocessforupdateoperation.h new file mode 100644 index 000000000..ab380aa3f --- /dev/null +++ b/src/libs/installer/fakestopprocessforupdateoperation.h @@ -0,0 +1,54 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef FAKESTOPPROCESSFORUPDATEOPERATION_H +#define FAKESTOPPROCESSFORUPDATEOPERATION_H + +#include "qinstallerglobal.h" + +namespace QInstaller { + +class FakeStopProcessForUpdateOperation : public Operation +{ +public: + FakeStopProcessForUpdateOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation *clone() const; +}; + +} + +#endif // FAKESTOPPROCESSFORUPDATEOPERATION_H diff --git a/src/libs/installer/fileutils.cpp b/src/libs/installer/fileutils.cpp new file mode 100644 index 000000000..b07b5b08f --- /dev/null +++ b/src/libs/installer/fileutils.cpp @@ -0,0 +1,507 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ +#include "fileutils.h" + +#include <errors.h> + +#include <QtCore/QDateTime> +#include <QtCore/QDir> +#include <QtCore/QDirIterator> +#include <QtCore/QEventLoop> +#include <QtCore/QTemporaryFile> +#include <QtCore/QThread> +#include <QtCore/QUrl> + +#include <errno.h> + +using namespace QInstaller; + + +// -- TempDirDeleter + +TempDirDeleter::TempDirDeleter(const QString &path) +{ + m_paths.insert(path); +} + +TempDirDeleter::TempDirDeleter(const QStringList &paths) + : m_paths(paths.toSet()) +{ +} + +TempDirDeleter::~TempDirDeleter() +{ + releaseAndDeleteAll(); +} + +QStringList TempDirDeleter::paths() const +{ + return m_paths.toList(); +} + +void TempDirDeleter::add(const QString &path) +{ + m_paths.insert(path); +} + +void TempDirDeleter::add(const QStringList &paths) +{ + m_paths += paths.toSet(); +} + +void TempDirDeleter::releaseAll() +{ + m_paths.clear(); +} + +void TempDirDeleter::release(const QString &path) +{ + m_paths.remove(path); +} + +void TempDirDeleter::passAndReleaseAll(TempDirDeleter &tdd) +{ + tdd.m_paths = m_paths; + releaseAll(); +} + +void TempDirDeleter::passAndRelease(TempDirDeleter &tdd, const QString &path) +{ + tdd.add(path); + release(path); +} + +void TempDirDeleter::releaseAndDeleteAll() +{ + foreach (const QString &path, m_paths) + releaseAndDelete(path); +} + +void TempDirDeleter::releaseAndDelete(const QString &path) +{ + if (m_paths.contains(path)) { + try { + m_paths.remove(path); + removeDirectory(path); + } catch (const Error &e) { + qCritical() << Q_FUNC_INFO << "Exception caught:" << e.message(); + } catch (...) { + qCritical() << Q_FUNC_INFO << "Unknown exception caught."; + } + } +} + + +// -- read, write operations + +bool QInstaller::isLocalUrl(const QUrl &url) +{ + return url.scheme().isEmpty() || url.scheme().toLower() == QLatin1String("file"); +} + +QString QInstaller::pathFromUrl(const QUrl &url) +{ + if (isLocalUrl(url)) + return url.toLocalFile(); + const QString str = url.toString(); + if (url.scheme() == QLatin1String("resource")) + return str.mid(QString::fromLatin1("resource").length()); + return str; +} + +void QInstaller::openForRead(QIODevice *dev, const QString &name) +{ + Q_ASSERT(dev); + if (!dev->open(QIODevice::ReadOnly)) + throw Error(QObject::tr("Cannot open file %1 for reading: %2").arg(name, dev->errorString())); +} + +void QInstaller::openForWrite(QIODevice *dev, const QString &name) +{ + Q_ASSERT(dev); + if (!dev->open(QIODevice::WriteOnly)) + throw Error(QObject::tr("Cannot open file %1 for writing: %2").arg(name, dev->errorString())); +} + +void QInstaller::openForAppend(QIODevice *dev, const QString &name) +{ + Q_ASSERT(dev); + if (!dev->open(QIODevice::ReadWrite | QIODevice::Append)) + throw Error(QObject::tr("Cannot open file %1 for writing: %2").arg(name, dev->errorString())); +} + +qint64 QInstaller::blockingWrite(QIODevice *out, const char *buffer, qint64 size) +{ + qint64 left = size; + while (left > 0) { + const qint64 n = out->write(buffer, left); + if (n < 0) { + throw Error(QObject::tr("Write failed after %1 bytes: %2").arg(QString::number(size-left), + out->errorString())); + } + left -= n; + } + return size; +} + +qint64 QInstaller::blockingWrite(QIODevice *out, const QByteArray &ba) +{ + return blockingWrite(out, ba.constData(), ba.size()); +} + +qint64 QInstaller::blockingRead(QIODevice *in, char *buffer, qint64 size) +{ + if (in->atEnd()) + return 0; + qint64 left = size; + while (left > 0) { + const qint64 n = in->read(buffer, left); + if (n < 0) { + throw Error(QObject::tr("Read failed after %1 bytes: %2").arg(QString::number(size-left), + in->errorString())); + } + left -= n; + buffer += n; + } + return size; +} + +void QInstaller::blockingCopy(QIODevice *in, QIODevice *out, qint64 size) +{ + static const qint64 blockSize = 4096; + QByteArray ba(blockSize, '\0'); + qint64 actual = qMin(blockSize, size); + while (actual > 0) { + blockingRead(in, ba.data(), actual); + blockingWrite(out, ba.constData(), actual); + size -= actual; + actual = qMin(blockSize, size); + } +} + +void QInstaller::removeFiles(const QString &path, bool ignoreErrors) +{ + const QFileInfoList entries = QDir(path).entryInfoList(QDir::AllEntries | QDir::Hidden); + foreach (const QFileInfo &fi, entries) { + if (fi.isSymLink() || fi.isFile()) { + QFile f(fi.filePath()); + if (!f.remove() && !ignoreErrors) + throw Error(QObject::tr("Could not remove file %1: %2").arg(f.fileName(), f.errorString())); + } + } +} + +void QInstaller::removeDirectory(const QString &path, bool ignoreErrors) +{ + if (path.isEmpty()) // QDir("") points to the working directory! We never want to remove that one. + return; + + QStringList dirs; + QDirIterator it(path, QDir::NoDotAndDotDot | QDir::Dirs | QDir::NoSymLinks | QDir::Hidden, + QDirIterator::Subdirectories); + while (it.hasNext()) { + it.next(); + dirs.prepend(it.filePath()); + removeFiles(dirs.at(0), ignoreErrors); + } + + QDir d; + dirs.append(path); + removeFiles(path, ignoreErrors); + foreach (const QString &dir, dirs) { + errno = 0; + if (d.exists(path) && !d.rmdir(dir) && !ignoreErrors) + throw Error(QObject::tr("Could not remove folder %1: %2").arg(dir, QLatin1String(strerror(errno)))); + } +} + +/*! + \internal + */ +class RemoveDirectoryThread : public QThread +{ +public: + explicit RemoveDirectoryThread(const QString &path, bool ignoreErrors = false, QObject *parent = 0) + : QThread(parent), + p(path), + ignore(ignoreErrors) + { + } + + const QString &error() const + { + return err; + } + +protected: + /*! + \reimp + */ + void run() + { + try { + removeDirectory(p, ignore); + } catch (const Error &e) { + err = e.message(); + } + } + +private: + QString err; + const QString p; + const bool ignore; +}; + +void QInstaller::removeDirectoryThreaded(const QString &path, bool ignoreErrors) +{ + RemoveDirectoryThread thread(path, ignoreErrors); + QEventLoop loop; + QObject::connect(&thread, SIGNAL(finished()), &loop, SLOT(quit())); + thread.start(); + loop.exec(); + if (!thread.error().isEmpty()) + throw Error(thread.error()); +} + +void QInstaller::removeSystemGeneratedFiles(const QString &path) +{ + if (path.isEmpty()) + return; +#if defined Q_WS_MAC + QFile::remove(path + QLatin1String("/.DS_Store")); +#elif defined Q_WS_WIN + QFile::remove(path + QLatin1String("/Thumbs.db")); +#endif +} + +void QInstaller::copyDirectoryContents(const QString &sourceDir, const QString &targetDir) +{ + qDebug() << "Copying" << sourceDir << "to" << targetDir; + Q_ASSERT(QFileInfo(sourceDir).isDir()); + Q_ASSERT(!QFileInfo(targetDir).exists() || QFileInfo(targetDir).isDir()); + if (!QDir().mkpath(targetDir)) + throw Error(QObject::tr("Could not create folder %1").arg(targetDir)); + + QDirIterator it(sourceDir, QDir::NoDotAndDotDot | QDir::AllEntries); + while (it.hasNext()) { + const QFileInfo i(it.next()); + if (i.isDir()) { + copyDirectoryContents(QDir(sourceDir).absoluteFilePath(i.fileName()), + QDir(targetDir).absoluteFilePath(i.fileName())); + } else { + QFile f(i.filePath()); + const QString target = QDir(targetDir).absoluteFilePath(i.fileName()); + if (!f.copy(target)) { + throw Error(QObject::tr("Could not copy file from %1 to %2: %3").arg(f.fileName(), target, + f.errorString())); + } + } + } +} + +void QInstaller::moveDirectoryContents(const QString &sourceDir, const QString &targetDir) +{ + qDebug() << "Moving" << sourceDir << "to" << targetDir; + Q_ASSERT(QFileInfo(sourceDir).isDir()); + Q_ASSERT(!QFileInfo(targetDir).exists() || QFileInfo(targetDir).isDir()); + if (!QDir().mkpath(targetDir)) + throw Error(QObject::tr("Could not create folder %1").arg(targetDir)); + + QDirIterator it(sourceDir, QDir::NoDotAndDotDot | QDir::AllEntries); + while (it.hasNext()) { + const QFileInfo i(it.next()); + if (i.isDir()) { + moveDirectoryContents(QDir(sourceDir).absoluteFilePath(i.fileName()), + QDir(targetDir).absoluteFilePath(i.fileName())); + } else { + QFile f(i.filePath()); + const QString target = QDir(targetDir).absoluteFilePath(i.fileName()); + if (!f.rename(target)) { + throw Error(QObject::tr("Could not move file from %1 to %2: %3").arg(f.fileName(), target, + f.errorString())); + } + } + } +} + +void QInstaller::mkdir(const QString &path) +{ + errno = 0; + if (!QDir().mkdir(QFileInfo(path).absoluteFilePath())) { + throw Error(QObject::tr("Could not create folder %1: %2").arg(path, + QString::fromLocal8Bit(strerror(errno)))); + } +} + +void QInstaller::mkpath(const QString &path) +{ + errno = 0; + if (!QDir().mkpath(QFileInfo(path).absoluteFilePath())) { + throw Error(QObject::tr("Could not create folder %1: %2").arg(path, + QString::fromLocal8Bit(strerror(errno)))); + } +} + +QString QInstaller::generateTemporaryFileName(const QString &templ) +{ + if (templ.isEmpty()) { + QTemporaryFile f; + if (!f.open()) + throw Error(QObject::tr("Could not open temporary file: %1").arg(f.errorString())); + return f.fileName(); + } + + static const QString characters = QLatin1String("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); + QString suffix; + qsrand(qrand() * QDateTime::currentDateTime().toTime_t()); + for (int i = 0; i < 5; ++i) + suffix += characters[qrand() % characters.length()]; + + const QString tmp = QLatin1String("%1.tmp.%2.%3"); + int count = 1; + while (QFile::exists(tmp.arg(templ, suffix).arg(count))) + ++count; + + QFile f(tmp.arg(templ, suffix).arg(count)); + if (!f.open(QIODevice::WriteOnly)) + throw Error(QObject::tr("Could not open temporary file for template %1: %2").arg(templ, f.errorString())); + f.remove(); + return f.fileName(); +} + +QString QInstaller::createTemporaryDirectory(const QString &templ) +{ + const QString t = QDir::tempPath() + QLatin1String("/") + templ + QLatin1String("XXXXXX"); + QTemporaryFile f(t); + if (!f.open()) + throw Error(QObject::tr("Could not create temporary folder for template %1: %2").arg(t, f.errorString())); + const QString path = f.fileName() + QLatin1String("meta"); + qDebug() << "Creating meta data directory at" << path; + + QInstaller::mkpath(path); + return path; +} + +#ifdef Q_WS_WIN +#include <windows.h> + +#pragma pack(push) +#pragma pack(2) + +typedef struct { + BYTE bWidth; // Width, in pixels, of the image + BYTE bHeight; // Height, in pixels, of the image + BYTE bColorCount; // Number of colors in image (0 if >=8bpp) + BYTE bReserved; // Reserved + WORD wPlanes; // Color Planes + WORD wBitCount; // Bits per pixel + DWORD dwBytesInRes; // how many bytes in this resource? + DWORD dwImageOffset; // the ID +} ICONDIRENTRY; + +typedef struct { + WORD idReserved; // Reserved (must be 0) + WORD idType; // Resource type (1 for icons) + WORD idCount; // How many images? + ICONDIRENTRY idEntries[1]; // The entries for each image +} ICONDIR; + +typedef struct { + BYTE bWidth; // Width, in pixels, of the image + BYTE bHeight; // Height, in pixels, of the image + BYTE bColorCount; // Number of colors in image (0 if >=8bpp) + BYTE bReserved; // Reserved + WORD wPlanes; // Color Planes + WORD wBitCount; // Bits per pixel + DWORD dwBytesInRes; // how many bytes in this resource? + WORD nID; // the ID +} GRPICONDIRENTRY, *LPGRPICONDIRENTRY; + +typedef struct { + WORD idReserved; // Reserved (must be 0) + WORD idType; // Resource type (1 for icons) + WORD idCount; // How many images? + GRPICONDIRENTRY idEntries[1]; // The entries for each image +} GRPICONDIR, *LPGRPICONDIR; + + +#pragma pack(pop) + +void QInstaller::setApplicationIcon(const QString &application, const QString &icon) +{ + wchar_t* const path = new wchar_t[application.length() + 1]; + QDir::toNativeSeparators(application).toWCharArray(path); + path[application.length()] = 0; + + HANDLE updateRes = BeginUpdateResource(path, false); + delete[] path; + + QFile iconFile(icon); + if (!iconFile.open(QIODevice::ReadOnly)) + return; + + QByteArray temp = iconFile.readAll(); + + ICONDIR* ig = reinterpret_cast< ICONDIR* >(temp.data()); + + DWORD newSize = sizeof(GRPICONDIR) + sizeof(GRPICONDIRENTRY) * (ig->idCount - 1); + GRPICONDIR* newDir = reinterpret_cast< GRPICONDIR* >(new char[newSize]); + newDir->idReserved = ig->idReserved; + newDir->idType = ig->idType; + newDir->idCount = ig->idCount; + + for (int i = 0; i < ig->idCount; ++i) { + char* temp1 = temp.data() + ig->idEntries[i].dwImageOffset; + DWORD size1 = ig->idEntries[i].dwBytesInRes; + + newDir->idEntries[i].bWidth = ig->idEntries[i].bWidth; + newDir->idEntries[i].bHeight = ig->idEntries[i].bHeight; + newDir->idEntries[i].bColorCount = ig->idEntries[i].bColorCount; + newDir->idEntries[i].bReserved = ig->idEntries[i].bReserved; + newDir->idEntries[i].wPlanes = ig->idEntries[i].wPlanes; + newDir->idEntries[i].wBitCount = ig->idEntries[i].wBitCount; + newDir->idEntries[i].dwBytesInRes = ig->idEntries[i].dwBytesInRes; + newDir->idEntries[i].nID = i + 1; + + UpdateResource(updateRes, RT_ICON, MAKEINTRESOURCE(i + 1), + MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), temp1, size1); + } + + UpdateResource(updateRes, RT_GROUP_ICON, L"IDI_ICON1", MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), newDir + , newSize); + + delete [] newDir; + + EndUpdateResource(updateRes, false); +} + +#endif diff --git a/src/libs/installer/fileutils.h b/src/libs/installer/fileutils.h new file mode 100644 index 000000000..4e634a210 --- /dev/null +++ b/src/libs/installer/fileutils.h @@ -0,0 +1,114 @@ +/************************************************************************** +** +** This file is part of Installer Framework** +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).* +** +** Contact: Nokia Corporation qt-info@nokia.com** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception version +** 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you are unsure which license is appropriate for your use, please contact +** (qt-info@nokia.com). +** +**************************************************************************/ +#ifndef QINSTALLER_FILEUTILS_H +#define QINSTALLER_FILEUTILS_H + +#include "installer_global.h" + +#include <QtCore/QSet> +#include <QtCore/QString> +#include <QtCore/QStringList> + +QT_BEGIN_NAMESPACE +class QByteArray; +class QIODevice; +class QUrl; +QT_END_NAMESPACE + +namespace QInstaller { +class INSTALLER_EXPORT TempDirDeleter +{ +public: + explicit TempDirDeleter(const QString &path); + explicit TempDirDeleter(const QStringList &paths = QStringList()); + ~TempDirDeleter(); + + QStringList paths() const; + + void add(const QString &path); + void add(const QStringList &paths); + + void releaseAll(); + void release(const QString &path); + void passAndReleaseAll(TempDirDeleter &tdd); + void passAndRelease(TempDirDeleter &tdd, const QString &path); + + void releaseAndDeleteAll(); + void releaseAndDelete(const QString &path); + +private: + Q_DISABLE_COPY(TempDirDeleter) + QSet<QString> m_paths; +}; + + void INSTALLER_EXPORT openForRead(QIODevice *dev, const QString &name); + void INSTALLER_EXPORT openForWrite(QIODevice *dev, const QString &name); + void INSTALLER_EXPORT openForAppend(QIODevice *dev, const QString &name); + + qint64 INSTALLER_EXPORT blockingRead(QIODevice *in, char *buffer, qint64 size); + void INSTALLER_EXPORT blockingCopy(QIODevice *in, QIODevice *out, qint64 size); + qint64 INSTALLER_EXPORT blockingWrite(QIODevice *out, const char *buffer, qint64 size); + qint64 INSTALLER_EXPORT blockingWrite(QIODevice *out, const QByteArray& ba); + + /*! + Removes the directory at \a path recursively. + @param path The directory to remove + @param ignoreErrors if @p true, errors will be silently ignored. Otherwise an exception will be thrown + if removing fails. + + @throws QInstaller::Error if the directory cannot be removed and ignoreErrors is @p false + */ + void INSTALLER_EXPORT removeFiles(const QString &path, bool ignoreErrors = false); + void INSTALLER_EXPORT removeDirectory(const QString &path, bool ignoreErrors = false); + void INSTALLER_EXPORT removeDirectoryThreaded(const QString &path, bool ignoreErrors = false); + void INSTALLER_EXPORT removeSystemGeneratedFiles(const QString &path); + + /*! + Creates a temporary directory + @throws QInstaller::Error if creating the temporary directory fails + */ + QString INSTALLER_EXPORT createTemporaryDirectory(const QString &templ=QString()); + + QString INSTALLER_EXPORT generateTemporaryFileName(const QString &templ=QString()); + + void INSTALLER_EXPORT moveDirectoryContents(const QString &sourceDir, const QString &targetDir); + void INSTALLER_EXPORT copyDirectoryContents(const QString &sourceDir, const QString &targetDir); + + bool INSTALLER_EXPORT isLocalUrl(const QUrl &url); + QString INSTALLER_EXPORT pathFromUrl(const QUrl &url); + + void INSTALLER_EXPORT mkdir(const QString &path); + void INSTALLER_EXPORT mkpath(const QString &path); + +#ifdef Q_WS_WIN + /*! + Sets the .ico file at \a icon as application icon for \a application. + */ + void INSTALLER_EXPORT setApplicationIcon(const QString &application, const QString &icon); +#endif +} + +#endif // QINSTALLER_FILEUTILS_H diff --git a/src/libs/installer/fsengineclient.cpp b/src/libs/installer/fsengineclient.cpp new file mode 100644 index 000000000..1858d7408 --- /dev/null +++ b/src/libs/installer/fsengineclient.cpp @@ -0,0 +1,818 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "fsengineclient.h" + +#include "adminauthorization.h" + +#include <QtCore/QCoreApplication> +#include <QtCore/QMutex> +#include <QtCore/QProcess> +#include <QtCore/QThread> +#include <QtCore/QTimer> +#include <QtCore/QUuid> + +#include <QtNetwork/QHostAddress> +#include <QtNetwork/QTcpSocket> + + +// -- StillAliveThread + +/*! + This thread convinces the watchdog in the running server that the client has not crashed yet. +*/ +class StillAliveThread : public QThread +{ + Q_OBJECT +public: + void run() + { + QTimer stillAliveTimer; + connect(&stillAliveTimer, SIGNAL(timeout()), this, SLOT(stillAlive())); + stillAliveTimer.start(1000); + exec(); + } + +public Q_SLOTS: + void stillAlive() + { + if (!FSEngineClientHandler::instance().isServerRunning()) + return; + + // in case of the server not running, this will simply fail + QTcpSocket socket; + FSEngineClientHandler::instance().connect(&socket); + } +}; + + +// -- FSEngineClient + +class FSEngineClient : public QAbstractFileEngine +{ +public: + FSEngineClient(); + ~FSEngineClient(); + + bool atEnd() const; + Iterator *beginEntryList(QDir::Filters filters, const QStringList &filterNames); + bool caseSensitive() const; + bool close(); + bool copy(const QString &newName); + QStringList entryList(QDir::Filters filters, const QStringList &filterNames) const; + QFile::FileError error() const; + QString errorString() const; + bool extension(Extension extension, const ExtensionOption *option = 0, ExtensionReturn *output = 0); + FileFlags fileFlags(FileFlags type = FileInfoAll) const; + QString fileName(FileName file = DefaultName) const; + bool flush(); + int handle() const; + bool isRelativePath() const; + bool isSequential() const; + bool link(const QString &newName); + bool mkdir(const QString &dirName, bool createParentDirectories) const; + bool open(QIODevice::OpenMode mode); + QString owner(FileOwner owner) const; + uint ownerId(FileOwner owner) const; + qint64 pos() const; + qint64 read(char *data, qint64 maxlen); + qint64 readLine(char *data, qint64 maxlen); + bool remove(); + bool rename(const QString &newName); + bool rmdir(const QString &dirName, bool recurseParentDirectories) const; + bool seek(qint64 offset); + void setFileName(const QString &fileName); + bool setPermissions(uint perms); + bool setSize(qint64 size); + qint64 size() const; + bool supportsExtension(Extension extension) const; + qint64 write(const char *data, qint64 len); + +private: + template<typename T> T returnWithType() const; + template<typename T> T returnWithCastedType() const; + +private: + friend class FSEngineClientHandler; + + mutable QTcpSocket *socket; + mutable QDataStream stream; +}; + +template<typename T> T FSEngineClient::returnWithType() const +{ + socket->flush(); + if (!socket->bytesAvailable()) + socket->waitForReadyRead(); + quint32 test; + stream >> test; + + T result; + stream >> result; + return result; +} + +template<typename T> T FSEngineClient::returnWithCastedType() const +{ + socket->flush(); + if (!socket->bytesAvailable()) + socket->waitForReadyRead(); + quint32 test; + stream >> test; + + int result; + stream >> result; + return static_cast<T>(result); +} + +/*! + \internal +*/ +class FSEngineClientIterator : public QAbstractFileEngineIterator +{ +public: + FSEngineClientIterator(QDir::Filters filters, const QStringList &nameFilters, const QStringList &files) + : QAbstractFileEngineIterator(filters, nameFilters), + entries(files), + index(-1) + { + } + + /*! + \reimp + */ + bool hasNext() const + { + return index < entries.size() - 1; + } + + /*! + \reimp + */ + QString next() + { + if (!hasNext()) + return QString(); + ++index; + return currentFilePath(); + } + + /*! + \reimp + */ + QString currentFileName() const + { + return entries.at(index); + } + +private: + const QStringList entries; + int index; +}; + +FSEngineClient::FSEngineClient() + : socket(new QTcpSocket) +{ + FSEngineClientHandler::instance().connect(socket); + stream.setDevice(socket); + stream.setVersion(QDataStream::Qt_4_2); +} + +FSEngineClient::~FSEngineClient() +{ + if (QThread::currentThread() == socket->thread()) { + socket->close(); + delete socket; + } else { + socket->deleteLater(); + } +} + +/*! + \reimp +*/ +bool FSEngineClient::atEnd() const +{ + stream << QString::fromLatin1("QFSFileEngine::atEnd"); + return returnWithType<bool>(); +} + +/*! + \reimp +*/ +QAbstractFileEngine::Iterator* FSEngineClient::beginEntryList(QDir::Filters filters, + const QStringList &filterNames) +{ + QStringList entries = entryList(filters, filterNames); + entries.removeAll(QString()); + return new FSEngineClientIterator(filters, filterNames, entries); +} + +/*! + \reimp +*/ +bool FSEngineClient::caseSensitive() const +{ + stream << QString::fromLatin1("QFSFileEngine::caseSensitive"); + return returnWithType<bool>(); +} + +/*! + \reimp +*/ +bool FSEngineClient::close() +{ + stream << QString::fromLatin1("QFSFileEngine::close"); + return returnWithType<bool>(); +} + +/*! + \reimp +*/ +bool FSEngineClient::copy(const QString &newName) +{ + stream << QString::fromLatin1("QFSFileEngine::copy"); + stream << newName; + return returnWithType<bool>(); +} + +/*! + \reimp +*/ +QStringList FSEngineClient::entryList(QDir::Filters filters, const QStringList &filterNames) const +{ + stream << QString::fromLatin1("QFSFileEngine::entryList"); + stream << static_cast<int>(filters); + stream << filterNames; + return returnWithType<QStringList>(); +} + +/*! + \reimp +*/ +QFile::FileError FSEngineClient::error() const +{ + stream << QString::fromLatin1("QFSFileEngine::error"); + return returnWithCastedType<QFile::FileError>(); +} + +/*! + \reimp +*/ +QString FSEngineClient::errorString() const +{ + stream << QString::fromLatin1("QFSFileEngine::errorString"); + return returnWithType<QString>(); +} + +/*! + \reimp +*/ +bool FSEngineClient::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output) +{ + Q_UNUSED(extension) + Q_UNUSED(option) + Q_UNUSED(output) + return false; +} + +/*! + \reimp +*/ +QAbstractFileEngine::FileFlags FSEngineClient::fileFlags(FileFlags type) const +{ + stream << QString::fromLatin1("QFSFileEngine::fileFlags"); + stream << static_cast<int>(type); + return returnWithCastedType<QAbstractFileEngine::FileFlags>(); +} + +/*! + \reimp +*/ +QString FSEngineClient::fileName(FileName file) const +{ + stream << QString::fromLatin1("QFSFileEngine::fileName"); + stream << static_cast<int>(file); + return returnWithType<QString>(); +} + +/*! + \reimp +*/ +bool FSEngineClient::flush() +{ + stream << QString::fromLatin1("QFSFileEngine::flush"); + return returnWithType<bool>(); +} + +/*! + \reimp +*/ +int FSEngineClient::handle() const +{ + stream << QString::fromLatin1("QFSFileEngine::handle"); + return returnWithType<int>(); +} + +/*! + \reimp +*/ +bool FSEngineClient::isRelativePath() const +{ + stream << QString::fromLatin1("QFSFileEngine::isRelativePath"); + return returnWithType<bool>(); +} + +/*! + \reimp +*/ +bool FSEngineClient::isSequential() const +{ + stream << QString::fromLatin1("QFSFileEngine::isSequential"); + return returnWithType<bool>(); +} + +/*! + \reimp +*/ +bool FSEngineClient::link(const QString &newName) +{ + stream << QString::fromLatin1("QFSFileEngine::link"); + stream << newName; + return returnWithType<bool>(); +} + +/*! + \reimp +*/ +bool FSEngineClient::mkdir(const QString &dirName, bool createParentDirectories) const +{ + stream << QString::fromLatin1("QFSFileEngine::mkdir"); + stream << dirName; + stream << createParentDirectories; + return returnWithType<bool>(); +} + +/*! + \reimp +*/ +bool FSEngineClient::open(QIODevice::OpenMode mode) +{ + stream << QString::fromLatin1("QFSFileEngine::open"); + stream << static_cast<int>(mode); + return returnWithType<bool>(); +} + +/*! + \reimp +*/ +QString FSEngineClient::owner(FileOwner owner) const +{ + stream << QString::fromLatin1("QFSFileEngine::owner"); + stream << static_cast<int>(owner); + return returnWithType<QString>(); +} + +/*! + \reimp +*/ +uint FSEngineClient::ownerId(FileOwner owner) const +{ + stream << QString::fromLatin1("QFSFileEngine::ownerId"); + stream << static_cast<int>(owner); + return returnWithType<uint>(); +} + +/*! + \reimp +*/ +qint64 FSEngineClient::pos() const +{ + stream << QString::fromLatin1("QFSFileEngine::pos"); + return returnWithType<qint64>(); +} + +/*! + \reimp +*/ +qint64 FSEngineClient::read(char *data, qint64 maxlen) +{ + stream << QString::fromLatin1("QFSFileEngine::read"); + stream << maxlen; + socket->flush(); + if (!socket->bytesAvailable()) + socket->waitForReadyRead(); + quint32 size; + stream >> size; + qint64 result; + stream >> result; + qint64 read = 0; + while (read < result) { + if (!socket->bytesAvailable()) + socket->waitForReadyRead(); + read += socket->read(data + read, result - read); + } + return result; +} + +/*! + \reimp +*/ +qint64 FSEngineClient::readLine(char *data, qint64 maxlen) +{ + stream << QString::fromLatin1("QFSFileEngine::readLine"); + stream << maxlen; + socket->flush(); + if (!socket->bytesAvailable()) + socket->waitForReadyRead(); + quint32 size; + stream >> size; + qint64 result; + stream >> result; + qint64 read = 0; + while (read < result) { + if (!socket->bytesAvailable()) + socket->waitForReadyRead(); + read += socket->read(data + read, result - read); + } + return result; +} + +/*! + \reimp +*/ +bool FSEngineClient::remove() +{ + stream << QString::fromLatin1("QFSFileEngine::remove"); + return returnWithType<bool>(); +} + +/*! + \reimp +*/ +bool FSEngineClient::rename(const QString &newName) +{ + stream << QString::fromLatin1("QFSFileEngine::rename"); + stream << newName; + return returnWithType<bool>(); +} + +/*! + \reimp +*/ +bool FSEngineClient::rmdir(const QString &dirName, bool recurseParentDirectories) const +{ + stream << QString::fromLatin1("QFSFileEngine::rmdir"); + stream << dirName; + stream << recurseParentDirectories; + return returnWithType<bool>(); +} + +/*! + \reimp +*/ +bool FSEngineClient::seek(qint64 offset) +{ + stream << QString::fromLatin1("QFSFileEngine::seek"); + stream << offset; + return returnWithType<bool>(); +} + +/*! + \reimp +*/ +void FSEngineClient::setFileName(const QString &fileName) +{ + stream << QString::fromLatin1("QFSFileEngine::setFileName"); + stream << fileName; + + socket->flush(); + if (!socket->bytesAvailable()) + socket->waitForReadyRead(); + quint32 test; + stream >> test; +} + +/*! + \reimp +*/ +bool FSEngineClient::setPermissions(uint perms) +{ + stream << QString::fromLatin1("QFSFileEngine::setPermissions"); + stream << perms; + return returnWithType<bool>(); +} + +/*! + \reimp +*/ +bool FSEngineClient::setSize(qint64 size) +{ + stream << QString::fromLatin1("QFSFileEngine::setSize"); + stream << size; + return returnWithType<bool>(); +} + +/*! + \reimp +*/ +qint64 FSEngineClient::size() const +{ + stream << QString::fromLatin1("QFSFileEngine::size"); + return returnWithType<qint64>(); +} + +/*! + \reimp +*/ +bool FSEngineClient::supportsExtension(Extension extension) const +{ + stream << QString::fromLatin1("QFSFileEngine::supportsExtension"); + stream << static_cast<int>(extension); + return returnWithType<bool>(); +} + +/*! + \reimp +*/ +qint64 FSEngineClient::write(const char *data, qint64 len) +{ + stream << QString::fromLatin1("QFSFileEngine::write"); + stream << len; + qint64 written = 0; + while (written < len) { + written += socket->write(data, len - written); + socket->waitForBytesWritten(); + } + return returnWithType<qint64>(); +} + +class FSEngineClientHandler::Private +{ +public: + Private() + : mutex(QMutex::Recursive), + port(0), + startServerAsAdmin(false), + serverStarted(false), + serverStarting(false), + active(false), + thread(new StillAliveThread) + { + thread->moveToThread(thread); + } + + void maybeStartServer(); + void maybeStopServer(); + + QMutex mutex; + QHostAddress address; + quint16 port; + QString socket; + bool startServerAsAdmin; + bool serverStarted; + bool serverStarting; + bool active; + QString serverCommand; + QStringList serverArguments; + QString key; + + StillAliveThread *const thread; +}; + +/*! + Creates a new FSEngineClientHandler with no connection. +*/ +FSEngineClientHandler::FSEngineClientHandler() + : d(new Private) +{ + //don't do this in the Private ctor as createUuid() accesses QFileEngine, which accesses this + // half-constructed handler -> Crash (KDNDK-248) + d->key = QUuid::createUuid().toString(); +} + +void FSEngineClientHandler::enableTestMode() +{ + d->key = QLatin1String("testAuthorizationKey"); + d->serverStarted = true; +} + +void FSEngineClientHandler::init(quint16 port, const QHostAddress &a) +{ + d->address = a; + d->port = port; + d->thread->start(); +} + +bool FSEngineClientHandler::connect(QTcpSocket *socket) +{ + int tries = 3; + while (tries > 0) { + socket->connectToHost(d->address, d->port); + if (!socket->waitForConnected(10000)) { + if (static_cast<QAbstractSocket::SocketError>(socket->error()) != QAbstractSocket::UnknownSocketError) + --tries; + qApp->processEvents(); + continue; + } + + QDataStream stream(socket); + stream << QString::fromLatin1("authorize"); + stream << d->key; + socket->flush(); + return true; + } + return false; +} + +/*! + Destroys the FSEngineClientHandler. If the handler started a server instance, it gets shut down. +*/ +FSEngineClientHandler::~FSEngineClientHandler() +{ + QMetaObject::invokeMethod(d->thread, "quit"); + //d->maybeStopServer(); + delete d; +} + +/*! + Returns a previously created FSEngineClientHandler instance. +*/ +FSEngineClientHandler &FSEngineClientHandler::instance() +{ + static FSEngineClientHandler instance; + return instance; +} + +/*! + Returns a created authorization key which is sent to the server when connecting via the "authorize" + command after the server was started. +*/ +QString FSEngineClientHandler::authorizationKey() const +{ + return d->key; +} + +/*! + Sets \a command as the command to be executed to startup the server. If \a startAsAdmin is set, + it is executed with admin privilegies. +*/ +void FSEngineClientHandler::setStartServerCommand(const QString &command, bool startAsAdmin) +{ + setStartServerCommand(command, QStringList(), startAsAdmin); +} + +/*! + Sets \a command as the command to be executed to startup the server. If \a startAsAdmin is set, it is + executed with admin privilegies. A list of \a arguments is passed to the process. +*/ +void FSEngineClientHandler::setStartServerCommand(const QString &command, const QStringList &arguments, + bool startAsAdmin) +{ + d->maybeStopServer(); + + d->startServerAsAdmin = startAsAdmin; + d->serverCommand = command; + d->serverArguments = arguments; +} + +/*! + \reimp +*/ +QAbstractFileEngine* FSEngineClientHandler::create(const QString &fileName) const +{ + if (d->serverStarting || !d->active) + return 0; + + d->maybeStartServer(); + + static QRegExp re(QLatin1String("^[a-z0-9]*://.*$")); + if (re.exactMatch(fileName)) // stuff like installer:// 7z:// and so on + return 0; + + if (fileName.isEmpty() || fileName.startsWith(QLatin1String(":"))) + return 0; // empty filename or Qt resource + + FSEngineClient *const client = new FSEngineClient; + // authorize + client->stream << QString::fromLatin1("authorize"); + client->stream << d->key; + client->socket->flush(); + + client->setFileName(fileName); + return client; +} + +/*! + Sets the FSEngineClientHandler to \a active. I.e. to actually return FSEngineClients if asked for. +*/ +void FSEngineClientHandler::setActive(bool active) +{ + d->active = active; + if (active) { + d->maybeStartServer(); + d->active = d->serverStarted; + } +} + +/*! + Returns, wheter this FSEngineClientHandler is active or not. +*/ +bool FSEngineClientHandler::isActive() const +{ + return d->active; +} + +/*! + Returns true, when the server already has been started. +*/ +bool FSEngineClientHandler::isServerRunning() const +{ + return d->serverStarted; +} + +/*! + \internal + Starts the server if a command was set and it isn't already started. +*/ +void FSEngineClientHandler::Private::maybeStartServer() +{ + if (serverStarted || serverCommand.isEmpty()) + return; + + const QMutexLocker ml(&mutex); + if (serverStarted) + return; + + serverStarting = true; + + if (startServerAsAdmin) { + AdminAuthorization auth; + serverStarted = auth.authorize() && auth.execute(0, serverCommand, serverArguments); + } else { + serverStarted = QProcess::startDetached(serverCommand, serverArguments); + } + + // now wait for the socket to arrive + QTcpSocket s; + while (serverStarting && serverStarted) { + if (FSEngineClientHandler::instance().connect(&s)) + serverStarting = false; + } + serverStarting = false; +} + +/*! + Stops the server if it was started before. +*/ +void FSEngineClientHandler::Private::maybeStopServer() +{ + if (!serverStarted) + return; + + const QMutexLocker ml(&mutex); + if (!serverStarted) + return; + + QTcpSocket s; + if (FSEngineClientHandler::instance().connect(&s)) { + QDataStream stream(&s); + stream.setVersion(QDataStream::Qt_4_2); + stream << QString::fromLatin1("authorize"); + stream << key; + stream << QString::fromLatin1("shutdown"); + s.flush(); + } + serverStarted = false; +} + +#include "fsengineclient.moc" diff --git a/src/libs/installer/fsengineclient.h b/src/libs/installer/fsengineclient.h new file mode 100644 index 000000000..b974234ec --- /dev/null +++ b/src/libs/installer/fsengineclient.h @@ -0,0 +1,75 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef FSENGINECLIENT_H +#define FSENGINECLIENT_H + +#include "installer_global.h" + +#include <QtCore/QAbstractFileEngineHandler> + +#include <QtNetwork/QHostAddress> + +QT_BEGIN_NAMESPACE +class QTcpSocket; +QT_END_NAMESPACE + +class INSTALLER_EXPORT FSEngineClientHandler : public QAbstractFileEngineHandler +{ +public: + static FSEngineClientHandler& instance(); + + QAbstractFileEngine* create(const QString &fileName) const; + void init(quint16 port, const QHostAddress &a = QHostAddress::LocalHost); + + bool connect(QTcpSocket *socket); + + bool isActive() const; + void setActive(bool active); + + void enableTestMode(); + bool isServerRunning() const; + QString authorizationKey() const; + + void setStartServerCommand(const QString &command, bool startAsAdmin = false); + void setStartServerCommand(const QString &command, const QStringList &arguments, bool startAsAdmin = false); + +protected: + FSEngineClientHandler(); + ~FSEngineClientHandler(); + +private: + class Private; + Private *d; +}; + +#endif diff --git a/src/libs/installer/fsengineserver.cpp b/src/libs/installer/fsengineserver.cpp new file mode 100644 index 000000000..19539334c --- /dev/null +++ b/src/libs/installer/fsengineserver.cpp @@ -0,0 +1,595 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ +#include "fsengineserver.h" + +#include "utils.h" + +#include <QtCore/QCoreApplication> +#include <QtCore/QFSFileEngine> +#include <QtCore/QProcess> +#include <QtCore/QSettings> +#include <QtCore/QStringList> +#include <QtCore/QThread> + +#include <QtNetwork/QTcpSocket> + +typedef int descriptor_t; + +#ifdef Q_WS_WIN +# include <windows.h> +#endif + +bool startDetached(const QString &program, const QStringList &args, const QString &workingDirectory, + qint64 *pid) +{ +#ifdef Q_WS_WIN + 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 + }; + + const QString arguments = QInstaller::createCommandline(program, args); + const bool success = CreateProcess(0, const_cast<wchar_t *>(static_cast<const wchar_t *>(arguments.utf16())), + 0, 0, FALSE, CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE, + 0, (wchar_t*)workingDirectory.utf16(), + &startupInfo, &pinfo); + + if (success) { + CloseHandle(pinfo.hThread); + CloseHandle(pinfo.hProcess); + if (pid) + *pid = pinfo.dwProcessId; + } + + return success; +#else + return QProcess::startDetached(program, args, workingDirectory, pid); +#endif +} + + +class QProcessSignalReceiver : public QObject +{ + Q_OBJECT + +public: + QProcessSignalReceiver(QObject *parent = 0) + : QObject(parent) + { + connect(parent, SIGNAL(finished(int, QProcess::ExitStatus)), this, + SLOT(processFinished(int, QProcess::ExitStatus))); + connect(parent, SIGNAL(error(QProcess::ProcessError)), this, + SLOT(processError(QProcess::ProcessError))); + connect(parent, SIGNAL(readyRead()), this, SLOT(processReadyRead())); + connect(parent, SIGNAL(started()), this, SLOT(processStarted())); + connect(parent, SIGNAL(stateChanged(QProcess::ProcessState)), this, + SLOT(processStateChanged(QProcess::ProcessState))); + } + + QList<QVariant> receivedSignals; + +private Q_SLOTS: + void processError(QProcess::ProcessError error); + void processFinished(int exitCode, QProcess::ExitStatus exitStatus); + void processReadyRead(); + void processStarted(); + void processStateChanged(QProcess::ProcessState newState); +}; + +/*! + \internal +*/ +class FSEngineConnectionThread : public QThread +{ + Q_OBJECT +public: + FSEngineConnectionThread(descriptor_t socketDescriptor, QObject *parent) + : QThread(parent), + descriptor(socketDescriptor), + settings(0), + process(0), + signalReceiver(0) + {} + +protected: + void run(); + +private: + QByteArray handleCommand(const QString &command); + + QFSFileEngine engine; + const descriptor_t descriptor; + QDataStream receivedStream; + QSettings *settings; + + QProcess *process; + QProcessSignalReceiver *signalReceiver; +}; + + +FSEngineServer::FSEngineServer(quint16 port, QObject *parent) + : QTcpServer(parent) +{ + listen(QHostAddress::LocalHost, port); + connect(&watchdog, SIGNAL(timeout()), qApp, SLOT(quit())); + watchdog.setSingleShot(true); + watchdog.setInterval(30000); + watchdog.start(); +} + +FSEngineServer::FSEngineServer(const QHostAddress &address, quint16 port, QObject *parent) + : QTcpServer(parent) +{ + listen(address, port); + connect(&watchdog, SIGNAL(timeout()), qApp, SLOT(quit())); + watchdog.setSingleShot(true); + watchdog.setInterval(30000); + watchdog.start(); +} + +/*! + Destroys the FSEngineServer. +*/ +FSEngineServer::~FSEngineServer() +{ + const QList<QThread *> threads = findChildren<QThread *>(); + foreach (QThread *thread, threads) + thread->wait(); +} + +/*! + \reimp +*/ +void FSEngineServer::incomingConnection(int socketDescriptor) +{ + qApp->processEvents(); + QThread *const thread = new FSEngineConnectionThread(socketDescriptor, this); + connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); + thread->start(); + watchdog.start(); +} + +void FSEngineServer::enableTestMode() +{ + setAuthorizationKey(QLatin1String("testAuthorizationKey")); + //we don't want to kill the server, + //maybe we should introduce a call where the client can kill the server + watchdog.disconnect(); +} + + +/*! + Sets the authorization key this server is asking the clients for to \a authorizationKey. +*/ +void FSEngineServer::setAuthorizationKey(const QString &authorizationKey) +{ + key = authorizationKey; +} + +QString FSEngineServer::authorizationKey() const +{ + return key; +} + +void QProcessSignalReceiver::processError(QProcess::ProcessError error) +{ + receivedSignals.push_back(QLatin1String("error")); + receivedSignals.push_back(static_cast<int> (error)); +} + +void QProcessSignalReceiver::processFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + receivedSignals.push_back(QLatin1String("finished")); + receivedSignals.push_back(exitCode); + receivedSignals.push_back(static_cast<int> (exitStatus)); +} + +void QProcessSignalReceiver::processStarted() +{ + receivedSignals.push_back(QLatin1String("started")); +} + +void QProcessSignalReceiver::processReadyRead() +{ + receivedSignals.push_back(QLatin1String("readyRead")); +} + +void QProcessSignalReceiver::processStateChanged(QProcess::ProcessState newState) +{ + receivedSignals.push_back(QLatin1String("stateChanged")); + receivedSignals.push_back(static_cast<int>(newState)); +} + +/*! + \reimp +*/ +void FSEngineConnectionThread::run() +{ + QTcpSocket socket; + socket.setSocketDescriptor(descriptor); + + receivedStream.setDevice(&socket); + receivedStream.setVersion(QDataStream::Qt_4_2); + + bool authorized = false; + + while (static_cast<QAbstractSocket::SocketState>(socket.state()) == QAbstractSocket::ConnectedState) { + if (!socket.bytesAvailable() && !socket.waitForReadyRead(250)) + continue; + + QString command; + receivedStream >> command; + + if (authorized && command == QLatin1String("shutdown")) { + // this is a graceful shutdown + socket.close(); + parent()->deleteLater(); + return; + } else if (command == QLatin1String("authorize")) { + QString k; + receivedStream >> k; + if (k != dynamic_cast<FSEngineServer*> (parent())->authorizationKey()) { + // this is closing the connection... auth failed + socket.close(); + return; + } + authorized = true; + } else if (authorized) { + if (command.isEmpty()) + continue; + const QByteArray result = handleCommand(command); + receivedStream << static_cast<quint32> (result.size()); + if (!result.isEmpty()) + receivedStream.writeRawData(result.data(), result.size()); + } else { + // authorization failed, connection not wanted + socket.close(); + return; + } + } +} + +static QDataStream &operator<<(QDataStream &stream, const QSettings::Status &status) +{ + return stream << static_cast<int>(status); +} + +/*! + Handles \a command and returns a QByteArray which has the result streamed into it. +*/ +QByteArray FSEngineConnectionThread::handleCommand(const QString &command) +{ + QByteArray block; + QDataStream returnStream(&block, QIODevice::WriteOnly); + returnStream.setVersion(QDataStream::Qt_4_2); + + // first, QSettings handling + if (command == QLatin1String("createQSettings")) { + QString fileName; + receivedStream >> fileName; + settings = new QSettings(fileName, QSettings::NativeFormat); + } else if (command == QLatin1String("destroyQSettings")) { + delete settings; + settings = 0; + } else if (command == QLatin1String("QSettings::allKeys")) { + returnStream << settings->allKeys(); + } else if (command == QLatin1String("QSettings::beginGroup")) { + QString prefix; + receivedStream >> prefix; + settings->beginGroup(prefix); + } else if (command == QLatin1String("QSettings::beginReadArray")) { + QString prefix; + int size; + receivedStream >> prefix; + receivedStream >> size; + settings->beginWriteArray(prefix, size); + } else if (command == QLatin1String("QSettings::beginWriteArray")) { + QString prefix; + receivedStream >> prefix; + returnStream << settings->beginReadArray(prefix); + } else if (command == QLatin1String("QSettings::childGroups")) { + returnStream << settings->childGroups(); + } else if (command == QLatin1String("QSettings::childKeys")) { + returnStream << settings->childKeys(); + } else if (command == QLatin1String("QSettings::clear")) { + settings->clear(); + } else if (command == QLatin1String("QSettings::contains")) { + QString key; + receivedStream >> key; + returnStream << settings->contains(key); + } else if (command == QLatin1String("QSettings::endArray")) { + settings->endArray(); + } else if (command == QLatin1String("QSettings::endGroup")) { + settings->endGroup(); + } else if (command == QLatin1String("QSettings::fallbacksEnabled")) { + returnStream << settings->fallbacksEnabled(); + } else if (command == QLatin1String("QSettings::fileName")) { + returnStream << settings->fileName(); + } else if (command == QLatin1String("QSettings::group")) { + returnStream << settings->group(); + } else if (command == QLatin1String("QSettings::isWritable")) { + returnStream << settings->isWritable(); + } else if (command == QLatin1String("QSettings::remove")) { + QString key; + receivedStream >> key; + settings->remove(key); + } else if (command == QLatin1String("QSettings::setArrayIndex")) { + int i; + receivedStream >> i; + settings->setArrayIndex(i); + } else if (command == QLatin1String("QSettings::setFallbacksEnabled")) { + bool b; + receivedStream >> b; + settings->setFallbacksEnabled(b); + } else if (command == QLatin1String("QSettings::status")) { + returnStream << settings->status(); + } else if (command == QLatin1String("QSettings::sync")) { + settings->sync(); + } else if (command == QLatin1String("QSettings::setValue")) { + QString key; + QVariant value; + receivedStream >> key; + receivedStream >> value; + settings->setValue(key, value); + } else if (command == QLatin1String("QSettings::value")) { + QString key; + QVariant defaultValue; + receivedStream >> key; + receivedStream >> defaultValue; + returnStream << settings->value(key, defaultValue); + } + + // from here, QProcess handling + else if (command == QLatin1String("createQProcess")) { + process = new QProcess; + signalReceiver = new QProcessSignalReceiver(process); + } else if (command == QLatin1String("destroyQProcess")) { + signalReceiver->receivedSignals.clear(); + process->deleteLater(); + process = 0; + } else if (command == QLatin1String("getQProcessSignals")) { + returnStream << signalReceiver->receivedSignals; + signalReceiver->receivedSignals.clear(); + qApp->processEvents(); + } else if (command == QLatin1String("QProcess::closeWriteChannel")) { + process->closeWriteChannel(); + } else if (command == QLatin1String("QProcess::exitCode")) { + returnStream << process->exitCode(); + } else if (command == QLatin1String("QProcess::exitStatus")) { + returnStream << static_cast<int> (process->exitStatus()); + } else if (command == QLatin1String("QProcess::kill")) { + process->kill(); + } else if (command == QLatin1String("QProcess::readAll")) { + returnStream << process->readAll(); + } else if (command == QLatin1String("QProcess::readAllStandardOutput")) { + returnStream << process->readAllStandardOutput(); + } else if (command == QLatin1String("QProcess::startDetached")) { + QString program; + QStringList arguments; + QString workingDirectory; + receivedStream >> program; + receivedStream >> arguments; + receivedStream >> workingDirectory; + qint64 pid; + const bool result = startDetached(program, arguments, workingDirectory, &pid); + returnStream << qMakePair< bool, qint64> (result, pid); + } else if (command == QLatin1String("QProcess::setWorkingDirectory")) { + QString dir; + receivedStream >> dir; + process->setWorkingDirectory(dir); + } else if (command == QLatin1String("QProcess::setEnvironment")) { + QStringList env; + receivedStream >> env; + process->setEnvironment(env); + } else if (command == QLatin1String("QProcess::start")) { + QString program; + QStringList arguments; + int mode; + receivedStream >> program; + receivedStream >> arguments; + receivedStream >> mode; + process->start(program, arguments, static_cast<QIODevice::OpenMode> (mode)); + } else if (command == QLatin1String("QProcess::state")) { + returnStream << static_cast<int> (process->state()); + } else if (command == QLatin1String("QProcess::terminate")) { + process->terminate(); + } else if (command == QLatin1String("QProcess::waitForFinished")) { + int msecs; + receivedStream >> msecs; + returnStream << process->waitForFinished(msecs); + } else if (command == QLatin1String("QProcess::waitForStarted")) { + int msecs; + receivedStream >> msecs; + returnStream << process->waitForStarted(msecs); + } else if (command == QLatin1String("QProcess::workingDirectory")) { + returnStream << process->workingDirectory(); + } else if (command == QLatin1String("QProcess::write")) { + QByteArray byteArray; + receivedStream >> byteArray; + returnStream << process->write(byteArray); + } else if (command == QLatin1String("QProcess::readChannel")) { + returnStream << static_cast<int> (process->readChannel()); + } else if (command == QLatin1String("QProcess::setReadChannel")) { + int processChannel; + receivedStream >> processChannel; + process->setReadChannel(static_cast<QProcess::ProcessChannel>(processChannel)); + } else if (command == QLatin1String("QProcess::write")) { + QByteArray byteArray; + receivedStream >> byteArray; + returnStream << process->write(byteArray); + } + + // from here, QFSEngine handling + else if (command == QLatin1String("QFSFileEngine::atEnd")) { + returnStream << engine.atEnd(); + } else if (command == QLatin1String("QFSFileEngine::caseSensitive")) { + returnStream << engine.caseSensitive(); + } else if (command == QLatin1String("QFSFileEngine::close")) { + returnStream << engine.close(); + } else if (command == QLatin1String("QFSFileEngine::copy")) { + QString newName; + receivedStream >> newName; + returnStream << engine.copy(newName); + } else if (command == QLatin1String("QFSFileEngine::entryList")) { + int filters; + QStringList filterNames; + receivedStream >> filters; + receivedStream >> filterNames; + returnStream << engine.entryList(static_cast<QDir::Filters> (filters), filterNames); + } else if (command == QLatin1String("QFSFileEngine::error")) { + returnStream << static_cast<int> (engine.error()); + } else if (command == QLatin1String("QFSFileEngine::errorString")) { + returnStream << engine.errorString(); + } + // extension + else if (command == QLatin1String("QFSFileEngine::fileFlags")) { + int flags; + receivedStream >> flags; + returnStream << static_cast<int>(engine.fileFlags(static_cast<QAbstractFileEngine::FileFlags>(flags))); + } else if (command == QLatin1String("QFSFileEngine::fileName")) { + int file; + receivedStream >> file; + returnStream << engine.fileName(static_cast<QAbstractFileEngine::FileName> (file)); + } else if (command == QLatin1String("QFSFileEngine::flush")) { + returnStream << engine.flush(); + } else if (command == QLatin1String("QFSFileEngine::handle")) { + returnStream << engine.handle(); + } else if (command == QLatin1String("QFSFileEngine::isRelativePath")) { + returnStream << engine.isRelativePath(); + } else if (command == QLatin1String("QFSFileEngine::isSequential")) { + returnStream << engine.isSequential(); + } else if (command == QLatin1String("QFSFileEngine::link")) { + QString newName; + receivedStream >> newName; + returnStream << engine.link(newName); + } else if (command == QLatin1String("QFSFileEngine::mkdir")) { + QString dirName; + bool createParentDirectories; + receivedStream >> dirName; + receivedStream >> createParentDirectories; + returnStream << engine.mkdir(dirName, createParentDirectories); + } else if (command == QLatin1String("QFSFileEngine::open")) { + int openMode; + receivedStream >> openMode; + returnStream << engine.open(static_cast<QIODevice::OpenMode> (openMode)); + } else if (command == QLatin1String("QFSFileEngine::owner")) { + int owner; + receivedStream >> owner; + returnStream << engine.owner(static_cast<QAbstractFileEngine::FileOwner> (owner)); + } else if (command == QLatin1String("QFSFileEngine::ownerId")) { + int owner; + receivedStream >> owner; + returnStream << engine.ownerId(static_cast<QAbstractFileEngine::FileOwner> (owner)); + } else if (command == QLatin1String("QFSFileEngine::pos")) { + returnStream << engine.pos(); + } else if (command == QLatin1String("QFSFileEngine::read")) { + qint64 maxlen; + receivedStream >> maxlen; + QByteArray ba(maxlen, '\0'); + const qint64 result = engine.read(ba.data(), maxlen); + returnStream << result; + int written = 0; + while (written < result) + written += returnStream.writeRawData(ba.data() + written, result - written); + } else if (command == QLatin1String("QFSFileEngine::readLine")) { + qint64 maxlen; + receivedStream >> maxlen; + QByteArray ba(maxlen, '\0'); + const qint64 result = engine.readLine(ba.data(), maxlen); + returnStream << result; + int written = 0; + while (written < result) + written += returnStream.writeRawData(ba.data() + written, result - written); + } else if (command == QLatin1String("QFSFileEngine::remove")) { + returnStream << engine.remove(); + } else if (command == QLatin1String("QFSFileEngine::rename")) { + QString newName; + receivedStream >> newName; + returnStream << engine.rename(newName); + } else if (command == QLatin1String("QFSFileEngine::rmdir")) { + QString dirName; + bool recurseParentDirectories; + receivedStream >> dirName; + receivedStream >> recurseParentDirectories; + returnStream << engine.rmdir(dirName, recurseParentDirectories); + } else if (command == QLatin1String("QFSFileEngine::seek")) { + quint64 offset; + receivedStream >> offset; + returnStream << engine.seek(offset); + } else if (command == QLatin1String("QFSFileEngine::setFileName")) { + QString fileName; + receivedStream >> fileName; + engine.setFileName(fileName); + } else if (command == QLatin1String("QFSFileEngine::setPermissions")) { + uint perms; + receivedStream >> perms; + returnStream << engine.setPermissions(perms); + } else if (command == QLatin1String("QFSFileEngine::setSize")) { + qint64 size; + receivedStream >> size; + returnStream << engine.setSize(size); + } else if (command == QLatin1String("QFSFileEngine::size")) { + returnStream << engine.size(); + } else if (command == QLatin1String("QFSFileEngine::supportsExtension")) { + int extension; + receivedStream >> extension; + //returnStream << engine.supportsExtension(static_cast<QAbstractFileEngine::Extension> (extension)); + returnStream << false; + } else if (command == QLatin1String("QFSFileEngine::write")) { + qint64 length; + receivedStream >> length; + qint64 read = 0; + qint64 written = 0; + QByteArray buffer(65536, '\0'); + while (read < length) { + if (!receivedStream.device()->bytesAvailable()) + receivedStream.device()->waitForReadyRead(-1); + const qint64 r = receivedStream.readRawData(buffer.data(), qMin(length - read, + static_cast<qint64> (buffer.length()))); + read += r; + qint64 w = 0; + while (w < r) + w += engine.write(buffer.data(), r); + written += r; + } + returnStream << written; + } else if (!command.isEmpty()) { + qDebug() << "unknown command:" << command; + } + + return block; +} + +#include "fsengineserver.moc" +#include "moc_fsengineserver.cpp" diff --git a/src/libs/installer/fsengineserver.h b/src/libs/installer/fsengineserver.h new file mode 100644 index 000000000..8ba72a85d --- /dev/null +++ b/src/libs/installer/fsengineserver.h @@ -0,0 +1,62 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef FSENGINESERVER_H +#define FSENGINESERVER_H + +#include "installer_global.h" + +#include <QtCore/QTimer> +#include <QtNetwork/QTcpServer> + +class INSTALLER_EXPORT FSEngineServer : public QTcpServer +{ + Q_OBJECT + +public: + explicit FSEngineServer(quint16 port, QObject *parent = 0); + FSEngineServer(const QHostAddress &address, quint16 port, QObject *parent = 0); + ~FSEngineServer(); + + void enableTestMode(); + void setAuthorizationKey(const QString &key); + QString authorizationKey() const; + +protected: + void incomingConnection(int socketDescriptor); + +private: + QString key; + QTimer watchdog; +}; + +#endif diff --git a/src/libs/installer/getrepositoriesmetainfojob.cpp b/src/libs/installer/getrepositoriesmetainfojob.cpp new file mode 100644 index 000000000..1e7092129 --- /dev/null +++ b/src/libs/installer/getrepositoriesmetainfojob.cpp @@ -0,0 +1,195 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "getrepositoriesmetainfojob.h" + +#include "getrepositorymetainfojob.h" +#include "packagemanagercore_p.h" +#include "qinstallerglobal.h" + +#include <QtCore/QDebug> + +using namespace KDUpdater; +using namespace QInstaller; + + +// -- GetRepositoriesMetaInfoJob + +GetRepositoriesMetaInfoJob::GetRepositoriesMetaInfoJob(PackageManagerCorePrivate *corePrivate) + : KDJob(corePrivate), + m_canceled(false), + m_silentRetries(3), + m_haveIgnoredError(false), + m_corePrivate(corePrivate) +{ + setCapabilities(Cancelable); +} + +QStringList GetRepositoriesMetaInfoJob::temporaryDirectories() const +{ + return m_repositoryByTemporaryDirectory.keys(); +} + +QStringList GetRepositoriesMetaInfoJob::releaseTemporaryDirectories() const +{ + m_tempDirDeleter.releaseAll(); + return m_repositoryByTemporaryDirectory.keys(); +} + +Repository GetRepositoriesMetaInfoJob::repositoryForTemporaryDirectory(const QString &tmpDir) const +{ + return m_repositoryByTemporaryDirectory.value(tmpDir); +} + +int GetRepositoriesMetaInfoJob::numberOfRetrievedRepositories() const +{ + return m_repositoryByTemporaryDirectory.size(); +} + +int GetRepositoriesMetaInfoJob::silentRetries() const +{ + return m_silentRetries; +} + +void GetRepositoriesMetaInfoJob::setSilentRetries(int retries) +{ + m_silentRetries = retries; +} + +void GetRepositoriesMetaInfoJob::reset() +{ + m_canceled = false; + m_silentRetries = 3; + m_errorString.clear(); + m_haveIgnoredError = false; + + m_repositories.clear(); + m_tempDirDeleter.releaseAndDeleteAll(); + m_repositoryByTemporaryDirectory.clear(); + + setError(KDJob::NoError); + setErrorString(QString()); + setCapabilities(Cancelable); +} + +bool GetRepositoriesMetaInfoJob::isCanceled() const +{ + return m_canceled; +} + +// -- private Q_SLOTS + +void GetRepositoriesMetaInfoJob::doStart() +{ + if ((m_corePrivate->isInstaller() && !m_corePrivate->isOfflineOnly()) + || (m_corePrivate->isUpdater() || m_corePrivate->isPackageManager())) { + foreach (const Repository &repo, m_corePrivate->m_settings.repositories()) { + if (repo.isEnabled()) + m_repositories += repo; + } + } + + fetchNextRepo(); +} + +void GetRepositoriesMetaInfoJob::doCancel() +{ + m_canceled = true; + if (m_job) + m_job->cancel(); +} + +void GetRepositoriesMetaInfoJob::fetchNextRepo() +{ + if (m_job) { + m_job->deleteLater(); + m_job = 0; + } + + if (m_canceled) { + emitFinishedWithError(KDJob::Canceled, m_errorString); + return; + } + + if (m_repositories.isEmpty()) { + if (m_haveIgnoredError) + emitFinishedWithError(QInstaller::UserIgnoreError, m_errorString); + else + emitFinished(); + return; + } + + m_job = new GetRepositoryMetaInfoJob(m_corePrivate, this); + connect(m_job, SIGNAL(finished(KDJob*)), this, SLOT(jobFinished(KDJob*))); + connect(m_job, SIGNAL(infoMessage(KDJob*, QString)), this, SIGNAL(infoMessage(KDJob*, QString))); + + m_job->setSilentRetries(silentRetries()); + m_job->setRepository(m_repositories.takeLast()); + m_job->start(); +} + +void GetRepositoriesMetaInfoJob::jobFinished(KDJob *j) +{ + const GetRepositoryMetaInfoJob *const job = qobject_cast<const GetRepositoryMetaInfoJob *>(j); + Q_ASSERT(job); + + if (job->error() != KDJob::NoError && !job->temporaryDirectory().isEmpty()) { + try { + removeDirectory(job->temporaryDirectory()); + } catch (...) { + } + } + + if (job->error() == KDJob::Canceled + || (job->error() >= KDJob::UserDefinedError && job->error() < QInstaller::UserIgnoreError)) { + emit infoMessage(j, job->errorString()); + qDebug() << job->errorString(); + emitFinishedWithError(job->error(), job->errorString()); + return; + } + + if (job->error() == QInstaller::UserIgnoreError) { + m_haveIgnoredError = true; + m_errorString = job->errorString(); + } else { + const QString &tmpdir = job->releaseTemporaryDirectory(); + job->m_tempDirDeleter.passAndRelease(m_tempDirDeleter, tmpdir); + m_repositoryByTemporaryDirectory.insert(tmpdir, job->repository()); + } + + if (job->error() == QInstaller::RepositoryUpdatesReceived) { + reset(); + QMetaObject::invokeMethod(this, "doStart", Qt::QueuedConnection); + } else { + QMetaObject::invokeMethod(this, "fetchNextRepo", Qt::QueuedConnection); + } +} diff --git a/src/libs/installer/getrepositoriesmetainfojob.h b/src/libs/installer/getrepositoriesmetainfojob.h new file mode 100644 index 000000000..e44032b48 --- /dev/null +++ b/src/libs/installer/getrepositoriesmetainfojob.h @@ -0,0 +1,96 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ +#ifndef GETREPOSITORIESMETAINFOJOB_H +#define GETREPOSITORIESMETAINFOJOB_H + +#include "fileutils.h" +#include "installer_global.h" +#include "repository.h" + +#include "kdjob.h" + +#include <QtCore/QList> +#include <QtCore/QPointer> +#include <QtCore/QString> +#include <QtCore/QStringList> + +namespace KDUpdater { + class FileDownloader; +} + +namespace QInstaller { + +class GetRepositoryMetaInfoJob; +class PackageManagerCorePrivate; + +class INSTALLER_EXPORT GetRepositoriesMetaInfoJob : public KDJob +{ + Q_OBJECT + +public: + explicit GetRepositoriesMetaInfoJob(PackageManagerCorePrivate *corePrivate); + + QStringList temporaryDirectories() const; + QStringList releaseTemporaryDirectories() const; + Repository repositoryForTemporaryDirectory(const QString &tmpDir) const; + + int numberOfRetrievedRepositories() const; + + int silentRetries() const; + void setSilentRetries(int retries); + + void reset(); + bool isCanceled() const; + +private Q_SLOTS: + /* reimp */ void doStart(); + /* reimp */ void doCancel(); + + void fetchNextRepo(); + void jobFinished(KDJob*); + +private: + bool m_canceled; + int m_silentRetries; + bool m_haveIgnoredError; + PackageManagerCorePrivate *m_corePrivate; + + QString m_errorString; + QList<Repository> m_repositories; + mutable TempDirDeleter m_tempDirDeleter; + QPointer<GetRepositoryMetaInfoJob> m_job; + QHash<QString, Repository> m_repositoryByTemporaryDirectory; +}; + +} // namespace QInstaller + +#endif // GETREPOSITORIESMETAINFOJOB_H diff --git a/src/libs/installer/getrepositorymetainfojob.cpp b/src/libs/installer/getrepositorymetainfojob.cpp new file mode 100644 index 000000000..a72a861a0 --- /dev/null +++ b/src/libs/installer/getrepositorymetainfojob.cpp @@ -0,0 +1,512 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ +#include "getrepositorymetainfojob.h" + +#include "constants.h" +#include "errors.h" +#include "lib7z_facade.h" +#include "messageboxhandler.h" +#include "packagemanagercore_p.h" +#include "qinstallerglobal.h" + +#include "kdupdaterfiledownloader.h" +#include "kdupdaterfiledownloaderfactory.h" + +#include <QtCore/QFile> +#include <QtCore/QTimer> +#include <QtCore/QUrl> + +#include <QtGui/QMessageBox> + +#include <QtNetwork/QAuthenticator> + +#include <QtXml/QDomDocument> +#include <QtXml/QDomElement> + +using namespace KDUpdater; +using namespace QInstaller; + + +// -- GetRepositoryMetaInfoJob::ZipRunnable + +class GetRepositoryMetaInfoJob::ZipRunnable : public QObject, public QRunnable +{ + Q_OBJECT + +public: + ZipRunnable(const QString &archive, const QString &targetDir, QPointer<FileDownloader> downloader) + : QObject() + , QRunnable() + , m_archive(archive) + , m_targetDir(targetDir) + , m_downloader(downloader) + {} + + ~ZipRunnable() + { + if (m_downloader) + m_downloader->deleteLater(); + } + + void run() + { + QFile archive(m_archive); + if (archive.open(QIODevice::ReadOnly)) { + try { + Lib7z::extractArchive(&archive, m_targetDir); + if (!archive.remove()) { + qWarning("Could not delete file %s: %s", qPrintable(m_archive), + qPrintable(archive.errorString())); + } + emit finished(true, QString()); + } catch (const Lib7z::SevenZipException& e) { + emit finished(false, tr("Error while extracting %1. Error: %2").arg(m_archive, e.message())); + } catch (...) { + emit finished(false, tr("Unknown exception caught while extracting %1.").arg(m_archive)); + } + } else { + emit finished(false, tr("Could not open %1 for reading. Error: %2").arg(m_archive, + archive.errorString())); + } + } + +Q_SIGNALS: + void finished(bool success, const QString &errorString); + +private: + const QString m_archive; + const QString m_targetDir; + QPointer<FileDownloader> m_downloader; +}; + + +// -- GetRepositoryMetaInfoJob + +GetRepositoryMetaInfoJob::GetRepositoryMetaInfoJob(PackageManagerCorePrivate *corePrivate, QObject *parent) + : KDJob(parent), + m_canceled(false), + m_silentRetries(3), + m_retriesLeft(m_silentRetries), + m_downloader(0), + m_waitForDone(false), + m_corePrivate(corePrivate) +{ + setCapabilities(Cancelable); +} + +GetRepositoryMetaInfoJob::~GetRepositoryMetaInfoJob() +{ + if (m_downloader) + m_downloader->deleteLater(); +} + +Repository GetRepositoryMetaInfoJob::repository() const +{ + return m_repository; +} + +void GetRepositoryMetaInfoJob::setRepository(const Repository &r) +{ + m_repository = r; + qDebug() << "Setting repository with URL:" << r.url().toString(); +} + +int GetRepositoryMetaInfoJob::silentRetries() const +{ + return m_silentRetries; +} + +void GetRepositoryMetaInfoJob::setSilentRetries(int retries) +{ + m_silentRetries = retries; +} + +void GetRepositoryMetaInfoJob::doStart() +{ + m_retriesLeft = m_silentRetries; + startUpdatesXmlDownload(); +} + +void GetRepositoryMetaInfoJob::doCancel() +{ + m_canceled = true; + if (m_downloader) + m_downloader->cancelDownload(); +} + +void GetRepositoryMetaInfoJob::finished(int error, const QString &errorString) +{ + m_waitForDone = true; + m_threadPool.waitForDone(); + (error > KDJob::NoError) ? emitFinishedWithError(error, errorString) : emitFinished(); +} + +QString GetRepositoryMetaInfoJob::temporaryDirectory() const +{ + return m_temporaryDirectory; +} + +QString GetRepositoryMetaInfoJob::releaseTemporaryDirectory() const +{ + m_tempDirDeleter.releaseAll(); + return m_temporaryDirectory; +} + +// Updates.xml download + +void GetRepositoryMetaInfoJob::startUpdatesXmlDownload() +{ + if (m_downloader) { + m_downloader->deleteLater(); + m_downloader = 0; + } + + const QUrl url = m_repository.url(); + if (url.isEmpty()) { + finished(QInstaller::InvalidUrl, tr("Empty repository URL.")); + return; + } + + if (!url.isValid()) { + finished(QInstaller::InvalidUrl, tr("Invalid repository URL: %1").arg(url.toString())); + return; + } + + m_downloader = FileDownloaderFactory::instance().create(url.scheme(), this); + if (!m_downloader) { + finished(QInstaller::InvalidUrl, tr("URL scheme not supported: %1 (%2)").arg(url.scheme(), + url.toString())); + return; + } + + // append a random string to avoid proxy caches + m_downloader->setUrl(QUrl(url.toString() + QString::fromLatin1("/Updates.xml?") + .append(QString::number(qrand() * qrand())))); + + QAuthenticator auth; + auth.setUser(m_repository.username()); + auth.setPassword(m_repository.password()); + m_downloader->setAuthenticator(auth); + + m_downloader->setAutoRemoveDownloadedFile(false); + connect(m_downloader, SIGNAL(downloadCompleted()), this, SLOT(updatesXmlDownloadFinished())); + connect(m_downloader, SIGNAL(downloadCanceled()), this, SLOT(updatesXmlDownloadCanceled())); + connect(m_downloader, SIGNAL(downloadAborted(QString)), this, + SLOT(updatesXmlDownloadError(QString)), Qt::QueuedConnection); + m_downloader->download(); +} + +void GetRepositoryMetaInfoJob::updatesXmlDownloadCanceled() +{ + finished(KDJob::Canceled, m_downloader->errorString()); +} + +void GetRepositoryMetaInfoJob::updatesXmlDownloadFinished() +{ + emit infoMessage(this, tr("Retrieving component meta information...")); + + const QString fn = m_downloader->downloadedFileName(); + Q_ASSERT(!fn.isEmpty()); + Q_ASSERT(QFile::exists(fn)); + + try { + m_temporaryDirectory = createTemporaryDirectory(QLatin1String("remoterepo")); + m_tempDirDeleter.add(m_temporaryDirectory); + } catch (const QInstaller::Error& e) { + finished(QInstaller::ExtractionError, e.message()); + return; + } + + QFile updatesFile(fn); + if (!updatesFile.rename(m_temporaryDirectory + QLatin1String("/Updates.xml"))) { + finished(QInstaller::DownloadError, tr("Could not move Updates.xml to target location. Error: %1") + .arg(updatesFile.errorString())); + return; + } + + if (!updatesFile.open(QIODevice::ReadOnly)) { + finished(QInstaller::DownloadError, tr("Could not open Updates.xml for reading. Error: %1") + .arg(updatesFile.errorString())); + return; + } + + QString err; + QDomDocument doc; + if (!doc.setContent(&updatesFile, &err)) { + const QString msg = tr("Could not fetch a valid version of Updates.xml from repository: %1. " + "Error: %2").arg(m_repository.url().toString(), err); + + const QMessageBox::StandardButton b = + MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), + QLatin1String("updatesXmlDownloadError"), tr("Download Error"), msg, QMessageBox::Cancel); + + if (b == QMessageBox::Cancel || b == QMessageBox::NoButton) { + finished(KDJob::Canceled, msg); + return; + } + } + + emit infoMessage(this, tr("Parsing component meta information...")); + + const QDomElement root = doc.documentElement(); + // search for additional repositories that we might need to check + const QDomNode repositoryUpdate = root.firstChildElement(QLatin1String("RepositoryUpdate")); + if (!repositoryUpdate.isNull()) { + QHash<QString, QPair<Repository, Repository> > repositoryUpdates; + const QDomNodeList children = repositoryUpdate.toElement().childNodes(); + for (int i = 0; i < children.count(); ++i) { + const QDomElement el = children.at(i).toElement(); + if (!el.isNull() && el.tagName() == QLatin1String("Repository")) { + const QString action = el.attribute(QLatin1String("action")); + if (action == QLatin1String("add")) { + // add a new repository to the defaults list + Repository repository(el.attribute(QLatin1String("url")), true); + repository.setUsername(el.attribute(QLatin1String("username"))); + repository.setPassword(el.attribute(QLatin1String("password"))); + repositoryUpdates.insertMulti(action, qMakePair(repository, Repository())); + + qDebug() << "Repository to add:" << repository.url().toString(); + } else if (action == QLatin1String("remove")) { + // remove possible default repositories using the given server url + Repository repository(el.attribute(QLatin1String("url")), true); + repositoryUpdates.insertMulti(action, qMakePair(repository, Repository())); + + qDebug() << "Repository to remove:" << repository.url().toString(); + } else if (action == QLatin1String("replace")) { + // replace possible default repositories using the given server url + Repository oldRepository(el.attribute(QLatin1String("oldUrl")), true); + Repository newRepository(el.attribute(QLatin1String("newUrl")), true); + newRepository.setUsername(el.attribute(QLatin1String("username"))); + newRepository.setPassword(el.attribute(QLatin1String("password"))); + + // store the new repository and the one old it replaces + repositoryUpdates.insertMulti(action, qMakePair(newRepository, oldRepository)); + qDebug() << "Replace repository:" << oldRepository.url().toString() << "with:" + << newRepository.url().toString(); + } else { + qDebug() << "Invalid additional repositories action set in Updates.xml fetched from:" + << m_repository.url().toString() << "Line:" << el.lineNumber(); + } + } + } + + if (!repositoryUpdates.isEmpty()) { + if (m_corePrivate->m_settings.updateDefaultRepositories(repositoryUpdates) + == Settings::UpdatesApplied) { + if (m_corePrivate->isUpdater() || m_corePrivate->isPackageManager()) + m_corePrivate->writeMaintenanceConfigFiles(); + finished(QInstaller::RepositoryUpdatesReceived, tr("Repository updates received.")); + return; + } + } + } + + const QDomNodeList children = root.childNodes(); + for (int i = 0; i < children.count(); ++i) { + const QDomElement el = children.at(i).toElement(); + if (el.isNull()) + continue; + if (el.tagName() == QLatin1String("PackageUpdate")) { + const QDomNodeList c2 = el.childNodes(); + for (int j = 0; j < c2.count(); ++j) { + if (c2.at(j).toElement().tagName() == scName) + m_packageNames << c2.at(j).toElement().text(); + else if (c2.at(j).toElement().tagName() == scRemoteVersion) + m_packageVersions << c2.at(j).toElement().text(); + else if (c2.at(j).toElement().tagName() == QLatin1String("SHA1")) + m_packageHash << c2.at(j).toElement().text(); + } + } + } + + setTotalAmount(m_packageNames.count() + 1); + setProcessedAmount(1); + emit infoMessage(this, tr("Finished updating component meta information...")); + + if (m_packageNames.isEmpty()) + finished(KDJob::NoError); + else + fetchNextMetaInfo(); +} + +void GetRepositoryMetaInfoJob::updatesXmlDownloadError(const QString &err) +{ + if (m_retriesLeft <= 0) { + const QString msg = tr("Could not fetch Updates.xml from repository: %1. Error: %2") + .arg(m_repository.url().toString(), err); + + QMessageBox::StandardButtons buttons = QMessageBox::Retry | QMessageBox::Cancel; + const QMessageBox::StandardButton b = + MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), + QLatin1String("updatesXmlDownloadError"), tr("Download Error"), msg, buttons); + + if (b == QMessageBox::Cancel || b == QMessageBox::NoButton) { + finished(KDJob::Canceled, msg); + return; + } + } + + m_retriesLeft--; + QTimer::singleShot(1500, this, SLOT(startUpdatesXmlDownload())); +} + +// meta data download + +void GetRepositoryMetaInfoJob::fetchNextMetaInfo() +{ + emit infoMessage(this, tr("Retrieving component information from remote repository...")); + + if (m_canceled) { + finished(KDJob::Canceled, m_downloader->errorString()); + return; + } + + if (m_packageNames.isEmpty() && m_currentPackageName.isEmpty()) { + finished(KDJob::NoError); + return; + } + + QString next = m_currentPackageName; + QString nextVersion = m_currentPackageVersion; + if (next.isEmpty()) { + m_retriesLeft = m_silentRetries; + next = m_packageNames.takeLast(); + nextVersion = m_packageVersions.takeLast(); + } + + qDebug() << "fetching metadata of" << next << "in version" << nextVersion; + + bool online = true; + if (m_repository.url().scheme().isEmpty()) + online = false; + + const QString repoUrl = m_repository.url().toString(); + const QUrl url = QString::fromLatin1("%1/%2/%3meta.7z").arg(repoUrl, next, + online ? nextVersion : QString()); + m_downloader = FileDownloaderFactory::instance().create(url.scheme(), this); + + if (!m_downloader) { + m_currentPackageName.clear(); + m_currentPackageVersion.clear(); + qWarning() << "Scheme not supported: " << url.toString(); + QMetaObject::invokeMethod(this, "fetchNextMetaInfo", Qt::QueuedConnection); + return; + } + + m_currentPackageName = next; + m_currentPackageVersion = nextVersion; + m_downloader->setUrl(url); + m_downloader->setAutoRemoveDownloadedFile(true); + + QAuthenticator auth; + auth.setUser(m_repository.username()); + auth.setPassword(m_repository.password()); + m_downloader->setAuthenticator(auth); + + connect(m_downloader, SIGNAL(downloadCanceled()), this, SLOT(metaDownloadCanceled())); + connect(m_downloader, SIGNAL(downloadCompleted()), this, SLOT(metaDownloadFinished())); + connect(m_downloader, SIGNAL(downloadAborted(QString)), this, SLOT(metaDownloadError(QString)), + Qt::QueuedConnection); + + m_downloader->download(); +} + +void GetRepositoryMetaInfoJob::metaDownloadCanceled() +{ + finished(KDJob::Canceled, m_downloader->errorString()); +} + +void GetRepositoryMetaInfoJob::metaDownloadFinished() +{ + const QString fn = m_downloader->downloadedFileName(); + Q_ASSERT(!fn.isEmpty()); + + QFile arch(fn); + if (!arch.open(QIODevice::ReadOnly)) { + finished(QInstaller::ExtractionError, tr("Could not open meta info archive: %1. Error: %2").arg(fn, + arch.errorString())); + return; + } + + if (!m_packageHash.isEmpty()) { + // verify file hash + QByteArray expectedFileHash = m_packageHash.back().toLatin1(); + QByteArray archContent = arch.readAll(); + QByteArray realFileHash = QString::fromLatin1(QCryptographicHash::hash(archContent, + QCryptographicHash::Sha1).toHex()).toLatin1(); + if (expectedFileHash != realFileHash) { + emit infoMessage(this, tr("The hash of one component does not match the expected one.")); + metaDownloadError(tr("Bad hash.")); + return; + } + m_packageHash.removeLast(); + } + arch.close(); + m_currentPackageName.clear(); + + ZipRunnable *runnable = new ZipRunnable(fn, m_temporaryDirectory, m_downloader); + connect(runnable, SIGNAL(finished(bool,QString)), this, SLOT(unzipFinished(bool,QString))); + m_threadPool.start(runnable); + + if (!m_waitForDone) + fetchNextMetaInfo(); +} + +void GetRepositoryMetaInfoJob::metaDownloadError(const QString &err) +{ + if (m_retriesLeft <= 0) { + const QString msg = tr("Could not download meta information for component: %1. Error: %2") + .arg(m_currentPackageName, err); + + QMessageBox::StandardButtons buttons = QMessageBox::Retry | QMessageBox::Cancel; + const QMessageBox::StandardButton b = + MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), + QLatin1String("updatesXmlDownloadError"), tr("Download Error"), msg, buttons); + + if (b == QMessageBox::Cancel || b == QMessageBox::NoButton) { + finished(KDJob::Canceled, msg); + return; + } + } + + m_retriesLeft--; + QTimer::singleShot(1500, this, SLOT(fetchNextMetaInfo())); +} + +void GetRepositoryMetaInfoJob::unzipFinished(bool ok, const QString &error) +{ + if (!ok) + finished(QInstaller::ExtractionError, error); +} + +#include "getrepositorymetainfojob.moc" +#include "moc_getrepositorymetainfojob.cpp" diff --git a/src/libs/installer/getrepositorymetainfojob.h b/src/libs/installer/getrepositorymetainfojob.h new file mode 100644 index 000000000..aec94603d --- /dev/null +++ b/src/libs/installer/getrepositorymetainfojob.h @@ -0,0 +1,113 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ +#ifndef GETREPOSITORYMETAINFOJOB_H +#define GETREPOSITORYMETAINFOJOB_H + +#include "fileutils.h" +#include "installer_global.h" +#include "repository.h" + +#include "kdjob.h" + +#include <QtCore/QPointer> +#include <QtCore/QString> +#include <QtCore/QStringList> +#include <QtCore/QThreadPool> + +namespace KDUpdater { + class FileDownloader; +} + +namespace QInstaller { + +class GetRepositoriesMetaInfoJob; +class PackageManagerCorePrivate; + +class INSTALLER_EXPORT GetRepositoryMetaInfoJob : public KDJob +{ + Q_OBJECT + class ZipRunnable; + friend class QInstaller::GetRepositoriesMetaInfoJob; + +public: + explicit GetRepositoryMetaInfoJob(PackageManagerCorePrivate *corePrivate, QObject *parent = 0); + ~GetRepositoryMetaInfoJob(); + + Repository repository() const; + void setRepository(const Repository &r); + + int silentRetries() const; + void setSilentRetries(int retries); + + QString temporaryDirectory() const; + QString releaseTemporaryDirectory() const; + +private: + /* reimp */ void doStart(); + /* reimp */ void doCancel(); + void finished(int error, const QString &errorString = QString()); + +private Q_SLOTS: + void startUpdatesXmlDownload(); + void updatesXmlDownloadCanceled(); + void updatesXmlDownloadFinished(); + void updatesXmlDownloadError(const QString &error); + + void fetchNextMetaInfo(); + void metaDownloadCanceled(); + void metaDownloadFinished(); + void metaDownloadError(const QString &error); + + void unzipFinished(bool status, const QString &error); + +private: + bool m_canceled; + int m_silentRetries; + int m_retriesLeft; + Repository m_repository; + QStringList m_packageNames; + QStringList m_packageVersions; + QStringList m_packageHash; + QPointer<KDUpdater::FileDownloader> m_downloader; + QString m_currentPackageName; + QString m_currentPackageVersion; + QString m_temporaryDirectory; + mutable TempDirDeleter m_tempDirDeleter; + + bool m_waitForDone; + QThreadPool m_threadPool; + PackageManagerCorePrivate *m_corePrivate; +}; + +} // namespace QInstaller + +#endif // GETREPOSITORYMETAINFOJOB_H diff --git a/src/libs/installer/globalsettingsoperation.cpp b/src/libs/installer/globalsettingsoperation.cpp new file mode 100644 index 000000000..4fe68abae --- /dev/null +++ b/src/libs/installer/globalsettingsoperation.cpp @@ -0,0 +1,123 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "globalsettingsoperation.h" +#include "qsettingswrapper.h" + +using namespace QInstaller; + +GlobalSettingsOperation::GlobalSettingsOperation() +{ + setName(QLatin1String("GlobalConfig")); +} + +void GlobalSettingsOperation::backup() +{ +} + +bool GlobalSettingsOperation::performOperation() +{ + QString key, value; + QScopedPointer<QSettingsWrapper> settings(setup(&key, &value, arguments())); + if (settings.isNull()) + return false; + + if (!settings->isWritable()) { + setError(UserDefinedError); + setErrorString(tr("Settings are not writable")); + return false; + } + + const QVariant oldValue = settings->value(key); + settings->setValue(key, value); + settings->sync(); + + if (settings->status() != QSettingsWrapper::NoError) { + setError(UserDefinedError); + setErrorString(tr("Failed to write settings")); + return false; + } + + setValue(QLatin1String("oldvalue"), oldValue); + return true; +} + +bool GlobalSettingsOperation::undoOperation() +{ + QString key, val; + QScopedPointer<QSettingsWrapper> settings(setup(&key, &val, arguments())); + if (settings.isNull()) + return false; + + // be sure it's still our value and nobody changed it in between + const QVariant oldValue = value(QLatin1String("oldvalue")); + if (settings->value(key) == val) { + // restore the previous state + if (oldValue.isNull()) + settings->remove(key); + else + settings->setValue(key, oldValue); + } + + return true; +} + +bool GlobalSettingsOperation::testOperation() +{ + return true; +} + +Operation *GlobalSettingsOperation::clone() const +{ + return new GlobalSettingsOperation(); +} + +QSettingsWrapper *GlobalSettingsOperation::setup(QString *key, QString *value, const QStringList &arguments) +{ + if (arguments.count() == 4) { + const QString &company = arguments.at(0); + const QString &application = arguments.at(1); + *key = arguments.at(2); + *value = arguments.at(3); + return new QSettingsWrapper(company, application); + } else if (arguments.count() == 3) { + const QString &filename = arguments.at(0); + *key = arguments.at(1); + *value = arguments.at(2); + return new QSettingsWrapper(filename, QSettingsWrapper::NativeFormat); + } + + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in 0%: %1 arguments given, at least 3 expected.") + .arg(name()).arg(arguments.count())); + return 0; +} diff --git a/src/libs/installer/globalsettingsoperation.h b/src/libs/installer/globalsettingsoperation.h new file mode 100644 index 000000000..f222bdff5 --- /dev/null +++ b/src/libs/installer/globalsettingsoperation.h @@ -0,0 +1,59 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef GLOBALSETTINGSOPERATION_H +#define GLOBALSETTINGSOPERATION_H + +#include "qinstallerglobal.h" + +class QSettingsWrapper; + +namespace QInstaller { + +class INSTALLER_EXPORT GlobalSettingsOperation : public Operation +{ +public: + GlobalSettingsOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation *clone() const; + +private: + QSettingsWrapper *setup(QString *key, QString *value, const QStringList &args); +}; + +} // namespace QInstaller + +#endif // GLOBALSETTINGSOPERATION_H diff --git a/src/libs/installer/init.cpp b/src/libs/installer/init.cpp new file mode 100644 index 000000000..5a464684a --- /dev/null +++ b/src/libs/installer/init.cpp @@ -0,0 +1,244 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ +#include "init.h" + +#include "createshortcutoperation.h" +#include "createdesktopentryoperation.h" +#include "createlocalrepositoryoperation.h" +#include "extractarchiveoperation.h" +#include "globalsettingsoperation.h" +#include "environmentvariablesoperation.h" +#include "registerfiletypeoperation.h" +#include "selfrestartoperation.h" +#include "installiconsoperation.h" +#include "elevatedexecuteoperation.h" +#include "fakestopprocessforupdateoperation.h" + +//added for NDK +#include "copydirectoryoperation.h" +#include "qtpatchoperation.h" +#include "setdemospathonqtoperation.h" +#include "setexamplespathonqtoperation.h" +#include "setpluginpathonqtcoreoperation.h" +#include "setimportspathonqtcoreoperation.h" +#include "setpathonqtcoreoperation.h" +#include "replaceoperation.h" +#include "licenseoperation.h" +#include "linereplaceoperation.h" +#include "registerdocumentationoperation.h" +#include "registerqtoperation.h" +#include "registerqtv2operation.h" +#include "registerqtv23operation.h" +#include "setqtcreatorvalueoperation.h" +#include "addqtcreatorarrayvalueoperation.h" +#include "simplemovefileoperation.h" +#include "registertoolchainoperation.h" +#include "registerdefaultdebuggeroperation.h" +#include "updatecreatorsettingsfrom21to22operation.h" + +#include "minimumprogressoperation.h" + +#ifdef Q_OS_MAC +# include "macreplaceinstallnamesoperation.h" +#endif // Q_OS_MAC + +#include "utils.h" + +#include "kdupdaterupdateoperation.h" +#include "kdupdaterupdateoperationfactory.h" +#include "kdupdaterfiledownloader.h" +#include "kdupdaterfiledownloaderfactory.h" + +#include <QtPlugin> +#include <QNetworkProxyFactory> + +#include <unix/C/7zCrc.h> + +namespace NArchive { +namespace NBz2 { void registerArcBZip2(); } +namespace NGz { void registerArcGZip(); } +namespace NLzma { namespace NLzmaAr { void registerArcLzma(); } } +namespace NLzma { namespace NLzma86Ar { void registerArcLzma86(); } } +namespace NSplit { void registerArcSplit(); } +namespace NXz { void registerArcxz(); } +namespace NZ { void registerArcZ(); } +} + +void registerArc7z(); +void registerArcCab(); +void registerArcTar(); +void registerArcZip(); + +void registerCodecBCJ2(); +void registerCodecBCJ(); +void registerCodecBCJ(); +void registerCodecByteSwap(); +void registerCodecBZip2(); +void registerCodecCopy(); +void registerCodecDeflate64(); +void registerCodecDeflate(); +void registerCodecDelta(); +void registerCodecLZMA2(); +void registerCodecLZMA(); +void registerCodecPPMD(); +void registerCodec7zAES(); + +using namespace NArchive; +using namespace KDUpdater; +using namespace QInstaller; + +static void initArchives() +{ + NBz2::registerArcBZip2(); + NGz::registerArcGZip(); + NLzma::NLzmaAr::registerArcLzma(); + NLzma::NLzma86Ar::registerArcLzma86(); + NSplit::registerArcSplit(); + NXz::registerArcxz(); + NZ::registerArcZ(); + registerArc7z(); + registerArcCab(); + registerArcTar(); + registerArcZip(); + + registerCodecBCJ2(); + registerCodecBCJ(); + registerCodecBCJ(); + registerCodecByteSwap(); + registerCodecBZip2(); + registerCodecCopy(); + registerCodecDeflate64(); + registerCodecDeflate(); + registerCodecDelta(); + registerCodecLZMA2(); + registerCodecLZMA(); + registerCodecPPMD(); + registerCodec7zAES(); + + CrcGenerateTable(); +} + +static void initResources() +{ + Q_INIT_RESOURCE(patch_file_lists); +#if defined(USE_STATIC_SQLITE_PLUGIN) + Q_IMPORT_PLUGIN(qsqlite); // RegisterDocumentationOperation needs this +#endif +} + +static void messageHandler(QtMsgType type, const char *msg) +{ + QByteArray ba(msg); + // last character is a space from qDebug + if (ba.endsWith(' ')) + ba.chop(1); + + // remove quotes if the whole message is surrounded with them + if (ba.startsWith('"') && ba.endsWith('"')) + ba = ba.mid(1, ba.length()-2); + + // prepend the message type, skip QtDebugMsg + switch (type) { + case QtWarningMsg: { + ba.prepend("Warning: "); + } break; + case QtCriticalMsg: { + ba.prepend("Critical: "); + } break; + case QtFatalMsg: { + ba.prepend("Fatal: "); + } break; + default: + break; + } + + verbose() << ba.constData() << std::endl; + if (type == QtFatalMsg) { + QtMsgHandler oldMsgHandler = qInstallMsgHandler(0); + qt_message_output(type, msg); + qInstallMsgHandler(oldMsgHandler); + } +} + +void QInstaller::init() +{ + ::initResources(); + + UpdateOperationFactory &factory = UpdateOperationFactory::instance(); + factory.registerUpdateOperation<CreateShortcutOperation>(QLatin1String("CreateShortcut")); + factory.registerUpdateOperation<CreateDesktopEntryOperation>(QLatin1String("CreateDesktopEntry")); + factory.registerUpdateOperation<CreateLocalRepositoryOperation>(QLatin1String("CreateLocalRepository")); + factory.registerUpdateOperation<ExtractArchiveOperation>(QLatin1String("Extract")); + factory.registerUpdateOperation<GlobalSettingsOperation>(QLatin1String("GlobalConfig")); + factory.registerUpdateOperation<EnvironmentVariableOperation>(QLatin1String( "EnvironmentVariable")); + factory.registerUpdateOperation<RegisterFileTypeOperation>(QLatin1String("RegisterFileType")); + factory.registerUpdateOperation<SelfRestartOperation>(QLatin1String("SelfRestart")); + factory.registerUpdateOperation<InstallIconsOperation>(QLatin1String("InstallIcons")); + factory.registerUpdateOperation<ElevatedExecuteOperation>(QLatin1String("Execute")); + factory.registerUpdateOperation<FakeStopProcessForUpdateOperation>(QLatin1String("FakeStopProcessForUpdate")); + + // added for NDK + factory.registerUpdateOperation<SimpleMoveFileOperation>(QLatin1String("SimpleMoveFile")); + factory.registerUpdateOperation<CopyDirectoryOperation>(QLatin1String("CopyDirectory")); + factory.registerUpdateOperation<RegisterDocumentationOperation>(QLatin1String("RegisterDocumentation")); + factory.registerUpdateOperation<RegisterQtInCreatorOperation>(QLatin1String("RegisterQtInCreator")); + factory.registerUpdateOperation<RegisterQtInCreatorV2Operation>(QLatin1String("RegisterQtInCreatorV2")); + factory.registerUpdateOperation<RegisterQtInCreatorV23Operation>(QLatin1String("RegisterQtInCreatorV23")); + factory.registerUpdateOperation<RegisterToolChainOperation>(QLatin1String("RegisterToolChain") ); + factory.registerUpdateOperation<RegisterDefaultDebuggerOperation>(QLatin1String( "RegisterDefaultDebugger")); + factory.registerUpdateOperation<SetDemosPathOnQtOperation>(QLatin1String("SetDemosPathOnQt")); + factory.registerUpdateOperation<SetExamplesPathOnQtOperation>(QLatin1String("SetExamplesPathOnQt")); + factory.registerUpdateOperation<SetPluginPathOnQtCoreOperation>(QLatin1String("SetPluginPathOnQtCore")); + factory.registerUpdateOperation<SetImportsPathOnQtCoreOperation>(QLatin1String("SetImportsPathOnQtCore")); + factory.registerUpdateOperation<SetPathOnQtCoreOperation>(QLatin1String("SetPathOnQtCore")); + factory.registerUpdateOperation<SetQtCreatorValueOperation>(QLatin1String("SetQtCreatorValue")); + factory.registerUpdateOperation<AddQtCreatorArrayValueOperation>(QLatin1String("AddQtCreatorArrayValue")); + factory.registerUpdateOperation<QtPatchOperation>(QLatin1String("QtPatch")); + factory.registerUpdateOperation<ReplaceOperation>(QLatin1String("Replace")); + factory.registerUpdateOperation<LineReplaceOperation>(QLatin1String( "LineReplace" ) ); + factory.registerUpdateOperation<UpdateCreatorSettingsFrom21To22Operation>(QLatin1String("UpdateCreatorSettingsFrom21To22")); + + factory.registerUpdateOperation<MinimumProgressOperation>(QLatin1String("MinimumProgress")); + factory.registerUpdateOperation<LicenseOperation>(QLatin1String("License")); + + FileDownloaderFactory::setFollowRedirects(true); + +#ifdef Q_OS_MAC + factory.registerUpdateOperation<MacReplaceInstallNamesOperation>(QLatin1String("ReplaceInstallNames")); +#endif // Q_OS_MAC + + // load 7z stuff, if we're a static lib + ::initArchives(); + + // qDebug -> verbose() + qInstallMsgHandler(messageHandler); +} diff --git a/src/libs/installer/init.h b/src/libs/installer/init.h new file mode 100644 index 000000000..ae15f90dd --- /dev/null +++ b/src/libs/installer/init.h @@ -0,0 +1,44 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef QINSTALLER_INIT_H +#define QINSTALLER_INIT_H + +#include "installer_global.h" + +namespace QInstaller { + +void INSTALLER_EXPORT init(); + +} + +#endif // QINSTALLER_INIT_H diff --git a/src/libs/installer/installer.pro b/src/libs/installer/installer.pro new file mode 100644 index 000000000..5ec75d589 --- /dev/null +++ b/src/libs/installer/installer.pro @@ -0,0 +1,179 @@ +TEMPLATE = lib +TARGET = installer +DEPENDPATH += . .. +INCLUDEPATH += . .. + +include(../7zip/7zip.pri) +include(../kdtools/kdtools.pri) +include(../../../installerfw.pri) + +DESTDIR = $$IFW_LIB_PATH +DLLDESTDIR = $$IFW_APP_PATH + +DEFINES += BUILD_LIB_INSTALLER + +QT += script \ + network \ + xml + +HEADERS += packagemanagercore.h \ + packagemanagercore_p.h \ + packagemanagergui.h \ + binaryformat.h \ + binaryformatengine.h \ + binaryformatenginehandler.h \ + repository.h \ + zipjob.h \ + utils.h \ + errors.h \ + component.h \ + componentmodel.h \ + qinstallerglobal.h \ + qtpatch.h \ + persistentsettings.h \ + projectexplorer_export.h \ + qtpatchoperation.h \ + setpathonqtcoreoperation.h \ + setdemospathonqtoperation.h \ + setexamplespathonqtoperation.h \ + setpluginpathonqtcoreoperation.h \ + setimportspathonqtcoreoperation.h \ + replaceoperation.h \ + linereplaceoperation.h \ + registerdocumentationoperation.h \ + registerqtoperation.h \ + registertoolchainoperation.h \ + registerqtv2operation.h \ + registerqtv23operation.h \ + setqtcreatorvalueoperation.h \ + addqtcreatorarrayvalueoperation.h \ + copydirectoryoperation.h \ + simplemovefileoperation.h \ + extractarchiveoperation.h \ + extractarchiveoperation_p.h \ + globalsettingsoperation.h \ + createshortcutoperation.h \ + createdesktopentryoperation.h \ + registerfiletypeoperation.h \ + environmentvariablesoperation.h \ + installiconsoperation.h \ + selfrestartoperation.h \ + settings.h \ + getrepositorymetainfojob.h \ + downloadarchivesjob.h \ + init.h \ + updater.h \ + operationrunner.h \ + updatesettings.h \ + adminauthorization.h \ + fsengineclient.h \ + fsengineserver.h \ + elevatedexecuteoperation.h \ + fakestopprocessforupdateoperation.h \ + lazyplaintextedit.h \ + progresscoordinator.h \ + minimumprogressoperation.h \ + performinstallationform.h \ + messageboxhandler.h \ + getrepositoriesmetainfojob.h \ + licenseoperation.h \ + component_p.h \ + qtcreator_constants.h \ + qtcreatorpersistentsettings.h \ + registerdefaultdebuggeroperation.h \ + updatecreatorsettingsfrom21to22operation.h \ + qprocesswrapper.h \ + qsettingswrapper.h \ + constants.h \ + packagemanagerproxyfactory.h \ + createlocalrepositoryoperation.h \ + lib7z_facade.h + +SOURCES += packagemanagercore.cpp \ + packagemanagercore_p.cpp \ + packagemanagergui.cpp \ + binaryformat.cpp \ + binaryformatengine.cpp \ + binaryformatenginehandler.cpp \ + repository.cpp \ + zipjob.cpp \ + fileutils.cpp \ + utils.cpp \ + component.cpp \ + componentmodel.cpp \ + qtpatch.cpp \ + persistentsettings.cpp \ + qtpatchoperation.cpp \ + setpathonqtcoreoperation.cpp \ + setdemospathonqtoperation.cpp \ + setexamplespathonqtoperation.cpp \ + setpluginpathonqtcoreoperation.cpp \ + setimportspathonqtcoreoperation.cpp \ + replaceoperation.cpp \ + linereplaceoperation.cpp \ + registerdocumentationoperation.cpp \ + registerqtoperation.cpp \ + registertoolchainoperation.cpp \ + registerqtv2operation.cpp \ + registerqtv23operation.cpp \ + setqtcreatorvalueoperation.cpp \ + addqtcreatorarrayvalueoperation.cpp \ + copydirectoryoperation.cpp \ + simplemovefileoperation.cpp \ + extractarchiveoperation.cpp \ + globalsettingsoperation.cpp \ + createshortcutoperation.cpp \ + createdesktopentryoperation.cpp \ + registerfiletypeoperation.cpp \ + environmentvariablesoperation.cpp \ + installiconsoperation.cpp \ + selfrestartoperation.cpp \ + getrepositorymetainfojob.cpp \ + downloadarchivesjob.cpp \ + init.cpp \ + updater.cpp \ + operationrunner.cpp \ + updatesettings.cpp \ + adminauthorization.cpp \ + fsengineclient.cpp \ + fsengineserver.cpp \ + elevatedexecuteoperation.cpp \ + fakestopprocessforupdateoperation.cpp \ + lazyplaintextedit.cpp \ + progresscoordinator.cpp \ + minimumprogressoperation.cpp \ + performinstallationform.cpp \ + messageboxhandler.cpp \ + getrepositoriesmetainfojob.cpp \ + licenseoperation.cpp \ + component_p.cpp \ + qtcreatorpersistentsettings.cpp \ + registerdefaultdebuggeroperation.cpp \ + updatecreatorsettingsfrom21to22operation.cpp \ + qprocesswrapper.cpp \ + templates.cpp \ + qsettingswrapper.cpp \ + settings.cpp \ + packagemanagerproxyfactory.cpp \ + createlocalrepositoryoperation.cpp \ + lib7z_facade.cpp + +RESOURCES += resources/patch_file_lists.qrc + +macx { + HEADERS += macrelocateqt.h \ + macreplaceinstallnamesoperation.h + SOURCES += adminauthorization_mac.cpp \ + macrelocateqt.cpp \ + macreplaceinstallnamesoperation.cpp +} + +unix:!macx:SOURCES += adminauthorization_x11.cpp + +win32 { + SOURCES += adminauthorization_win.cpp + LIBS += -loleaut32 -lUser32 # 7zip + LIBS += advapi32.lib psapi.lib # kdtools + LIBS += -lole32 # createshortcutoperation + CONFIG(shared, static|shared):LIBS += -lshell32 +} diff --git a/src/libs/installer/installer_global.h b/src/libs/installer/installer_global.h new file mode 100644 index 000000000..51091cd49 --- /dev/null +++ b/src/libs/installer/installer_global.h @@ -0,0 +1,41 @@ +/************************************************************************** +** +** This file is part of Installer Framework** +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).* +** +** Contact: Nokia Corporation qt-info@nokia.com** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception version +** 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you are unsure which license is appropriate for your use, please contact +** (qt-info@nokia.com). +** +**************************************************************************/ +#ifndef INSTALLER_GLOBAL_H +#define INSTALLER_GLOBAL_H + +#include <QtCore/QtGlobal> + +#ifdef LIB_INSTALLER_SHARED +#ifdef BUILD_LIB_INSTALLER +#define INSTALLER_EXPORT Q_DECL_EXPORT +#else +#define INSTALLER_EXPORT Q_DECL_IMPORT +#endif +#else +#define INSTALLER_EXPORT +#endif + +#endif //INSTALLER_GLOBAL_H diff --git a/src/libs/installer/installiconsoperation.cpp b/src/libs/installer/installiconsoperation.cpp new file mode 100644 index 000000000..c2fee33eb --- /dev/null +++ b/src/libs/installer/installiconsoperation.cpp @@ -0,0 +1,281 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ +#include "installiconsoperation.h" + +#include "fileutils.h" +#include "packagemanagercore.h" + +#include <QtCore/QDir> +#include <QtCore/QDirIterator> + +#if QT_VERSION >= 0x040600 +# include <QProcessEnvironment> +#else +# include <QProcess> +#endif + +using namespace QInstaller; + +QString InstallIconsOperation::targetDirectory() +{ + // we're not searching for the first time, let's re-use the old value + if (hasValue(QLatin1String("targetdirectory"))) + return value(QLatin1String("targetdirectory")).toString(); + +#if QT_VERSION >= 0x040600 + const QProcessEnvironment env; + QStringList XDG_DATA_DIRS = env.value(QLatin1String("XDG_DATA_DIRS")).split(QLatin1Char(':'), + QString::SkipEmptyParts); +#else + QStringList XDG_DATA_DIRS; + const QStringList env = QProcess::systemEnvironment(); + for (QStringList::const_iterator it = env.begin(); it != env.end(); ++it) { + if (it->startsWith(QLatin1String("XDG_DATA_DIRS="))) + XDG_DATA_DIRS = it->mid(it->indexOf(QLatin1Char('=')) + 1).split(QLatin1Char(':')); + } +#endif + + XDG_DATA_DIRS.push_back(QLatin1String("/usr/share/pixmaps")); // default path + XDG_DATA_DIRS.push_back(QDir::home().absoluteFilePath(QLatin1String(".local/share/icons"))); // default path + XDG_DATA_DIRS.push_back(QDir::home().absoluteFilePath(QLatin1String(".icons"))); // default path + + QString directory; + const QStringList& directories = XDG_DATA_DIRS; + for (QStringList::const_iterator it = directories.begin(); it != directories.end(); ++it) { + if (it->isEmpty()) + continue; + + // our default dirs are correct, XDG_DATA_DIRS set via env need "icon" at the end + if ((it + 1 == directories.end()) || (it + 2 == directories.end()) || (it + 3 == directories.end())) + directory = QDir(*it).absolutePath(); + else + directory = QDir(*it).absoluteFilePath(QLatin1String("icons")); + + QDir dir(directory); + // let's see if this dir exists or we're able to create it + if (!dir.exists() && !QDir().mkpath(directory)) + continue; + + // we just try if we're able to open the file in ReadWrite + QFile file(QDir(directory).absoluteFilePath(QLatin1String("tmpfile"))); + const bool existed = file.exists(); + if (!file.open(QIODevice::ReadWrite)) + continue; + + file.close(); + if (!existed) + file.remove(); + break; + } + + if (!QDir(directory).exists()) + QDir().mkpath(directory); + + setValue(QLatin1String("directory"), directory); + return directory; +} + +InstallIconsOperation::InstallIconsOperation() +{ + setName(QLatin1String("InstallIcons")); +} + +InstallIconsOperation::~InstallIconsOperation() +{ + const QStringList backupFiles = value(QLatin1String("backupfiles")).toStringList(); + for (QStringList::const_iterator it = backupFiles.begin(); it != backupFiles.end(); it += 2) { + const QString& backup = *(it + 1); + deleteFileNowOrLater(backup); + } +} + +void InstallIconsOperation::backup() +{ + // we backup on the fly +} + +bool InstallIconsOperation::performOperation() +{ + const QStringList args = arguments(); + if (args.count() != 1) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in %0: %1 arguments given, 1 expected.").arg(name()).arg(args + .count())); + return false; + } + + const QString source = args.first(); + if (source.isEmpty()) { + setError(InvalidArguments); + setErrorString(QObject::tr("Invalid Argument: source folder must not be empty.")); + return false; + } + + const QDir sourceDir = QDir(source); + const QDir targetDir = QDir(targetDirectory()); + + QStringList files; + QStringList backupFiles; + QStringList createdDirectories; + + PackageManagerCore *const core = qVariantValue<PackageManagerCore*>(value(QLatin1String("installer"))); + + // iterate a second time to get the actual work done + QDirIterator it(sourceDir.path(), QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoDotAndDotDot, + QDirIterator::Subdirectories); + while (it.hasNext()) { + qApp->processEvents(); + + const int status = core->status(); + if (status == PackageManagerCore::Canceled || status == PackageManagerCore::Failure) + return true; + + const QString source = it.next(); + const QString target = targetDir.absoluteFilePath(sourceDir.relativeFilePath(source)); + + emit outputTextChanged(target); + + const QFileInfo fi = it.fileInfo(); + if (!fi.isDir()) { + if (QFile(target).exists()) { + // first backup... + const QString backup = generateTemporaryFileName(target + QLatin1String("XXXXXX")); + QFile bf(target); + if (!bf.copy(backup)) { + setError(UserDefinedError); + setErrorString(QObject::tr("Could not backup file %1: %2").arg(target, bf.errorString())); + undoOperation(); + return false; + } + + backupFiles.push_back(target); + backupFiles.push_back(backup); + setValue(QLatin1String("backupfiles"), backupFiles); + + // then delete it + QString errStr; + if (!deleteFileNowOrLater(target, &errStr)) { + setError(UserDefinedError); + setErrorString(QObject::tr("Failed to overwrite %1: %2").arg(target, errStr)); + undoOperation(); + return false; + } + + } + + // copy the file to its new location + QFile cf(source); + if (!cf.copy(target)) { + setError(UserDefinedError); + setErrorString(QObject::tr("Failed to copy file %1: %2").arg(target, cf.errorString())); + undoOperation(); + return false; + } + deleteFileNowOrLater(source); + files.push_back(source); + files.push_back(target); + setValue(QLatin1String("files"), files); + } else if (fi.isDir() && !QDir(target).exists()) { + if (!QDir().mkpath(target)) { + setErrorString(QObject::tr("Could not create folder at %1: %2").arg(target, qt_error_string())); + undoOperation(); + return false; + } + createdDirectories.push_front(target); + setValue(QLatin1String("createddirectories"), createdDirectories); + } + } + + // this should work now if not, it's not _that_ problematic... + try { + removeDirectory(source); + } catch(...) { + } + return true; +} + +bool InstallIconsOperation::undoOperation() +{ + bool success = true; + + // first copy back all files to their origin + const QStringList files = value(QLatin1String("files")).toStringList(); + for (QStringList::const_iterator it = files.begin(); it != files.end(); it += 2) { + qApp->processEvents(); + + const QString& source = *it; + const QString& target = *(it + 1); + + // first make sure the "source" path is valid + QDir().mkpath(QFileInfo(source).absolutePath()); + + // now copy target to source (feels weird, I know...) + success = QFile::copy(target, source) && success; + // and remove target + success = QFile::remove(target) && success; + } + + // then copy back and remove all backuped files + const QStringList backupFiles = value(QLatin1String("backupfiles")).toStringList(); + for (QStringList::const_iterator it = backupFiles.begin(); it != backupFiles.end(); it += 2) { + const QString& target = *it; + const QString& backup = *(it + 1); + + // remove the target + if (QFile::exists(target)) + success = deleteFileNowOrLater(target) && success; + // then copy the backup onto the target + success = QFile::copy(backup, target) && success; + // finally remove the backp + success = deleteFileNowOrLater(backup) && success; + } + + // then remove all directories created by us + const QStringList createdDirectories = value(QLatin1String("createddirectories")).toStringList(); + for (QStringList::const_iterator it = createdDirectories.begin(); it != createdDirectories.end(); ++it) { + const QDir dir(*it); + removeSystemGeneratedFiles(dir.absolutePath()); + success = QDir::root().rmdir(dir.path()); + } + + return success; +} + +bool InstallIconsOperation::testOperation() +{ + return true; +} + +Operation *InstallIconsOperation::clone() const +{ + return new InstallIconsOperation(); +} diff --git a/src/libs/installer/installiconsoperation.h b/src/libs/installer/installiconsoperation.h new file mode 100644 index 000000000..52faaabc7 --- /dev/null +++ b/src/libs/installer/installiconsoperation.h @@ -0,0 +1,64 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef INSTALLICONSOPERATION_H +#define INSTALLICONSOPERATION_H + +#include "qinstallerglobal.h" + +#include <QtCore/QObject> + +namespace QInstaller { + +class INSTALLER_EXPORT InstallIconsOperation : public QObject, public Operation +{ + Q_OBJECT +public: + InstallIconsOperation(); + ~InstallIconsOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation *clone() const; + +Q_SIGNALS: + void outputTextChanged(const QString &progress); + +private: + QString targetDirectory(); +}; + +} + +#endif diff --git a/src/libs/installer/lazyplaintextedit.cpp b/src/libs/installer/lazyplaintextedit.cpp new file mode 100644 index 000000000..b9b868775 --- /dev/null +++ b/src/libs/installer/lazyplaintextedit.cpp @@ -0,0 +1,85 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "lazyplaintextedit.h" + +#include <QScrollBar> + +const int INTERVAL = 20; + +LazyPlainTextEdit::LazyPlainTextEdit(QWidget *parent) : + QPlainTextEdit(parent), m_timerId(0) +{ +} + +void LazyPlainTextEdit::timerEvent(QTimerEvent *event) +{ + if (event->timerId() == m_timerId) { + killTimer(m_timerId); + m_timerId = 0; + m_chachedOutput.chop(1); //removes the last \n + if (!m_chachedOutput.isEmpty()) + appendPlainText(m_chachedOutput); + horizontalScrollBar()->setValue( 0 ); + m_chachedOutput.clear(); + } +} + +void LazyPlainTextEdit::append(const QString &text) +{ + m_chachedOutput.append(text + QLatin1String("\n")); + if (isVisible() && m_timerId == 0) + m_timerId = startTimer(INTERVAL); +} + +void LazyPlainTextEdit::clear() +{ + if (m_timerId) { + killTimer(m_timerId); + m_timerId = 0; + m_chachedOutput.clear(); + } + QPlainTextEdit::clear(); +} + + +void LazyPlainTextEdit::setVisible(bool visible) +{ + if (m_timerId) { + killTimer(m_timerId); + m_timerId = 0; + } + if (visible) { + m_timerId = startTimer(INTERVAL); + } + QPlainTextEdit::setVisible(visible); +} diff --git a/src/libs/installer/lazyplaintextedit.h b/src/libs/installer/lazyplaintextedit.h new file mode 100644 index 000000000..40477deaf --- /dev/null +++ b/src/libs/installer/lazyplaintextedit.h @@ -0,0 +1,55 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef LAZYPLAINTEXTEDIT_H +#define LAZYPLAINTEXTEDIT_H + +#include <QPlainTextEdit> + +class LazyPlainTextEdit : public QPlainTextEdit +{ + Q_OBJECT +public: + explicit LazyPlainTextEdit(QWidget *parent = 0); + +public slots: + void append(const QString &text); + virtual void clear(); + virtual void setVisible ( bool visible ); +protected: + void timerEvent(QTimerEvent *event); +private: + QString m_chachedOutput; + int m_timerId; +}; + +#endif // LAZYPLAINTEXTEDIT_H diff --git a/src/libs/installer/lib7z_facade.cpp b/src/libs/installer/lib7z_facade.cpp new file mode 100644 index 000000000..c679572fd --- /dev/null +++ b/src/libs/installer/lib7z_facade.cpp @@ -0,0 +1,1372 @@ +#include "lib7z_facade.h" + +#include "errors.h" +#include "fileutils.h" + +#ifndef Q_OS_WIN +# include "StdAfx.h" +#endif + +#include "Common/MyInitGuid.h" + +#include "Common/CommandLineParser.h" +#include "Common/IntToString.h" +#include "Common/MyException.h" +#include "Common/StdOutStream.h" +#include "Common/StringConvert.h" +#include "Common/StringToInt.h" + +#include "Windows/Defs.h" +#include "Windows/Error.h" +#include "Windows/FileDir.h" +#include "Windows/FileName.h" + +#include "7zip/ICoder.h" +#include "7zip/IPassword.h" + +#include "7zip/UI/Common/ArchiveCommandLine.h" +#include "7zip/UI/Common/ExitCode.h" +#include "7zip/UI/Common/Extract.h" +#include "7zip/UI/Common/Update.h" +#include "7zip/UI/Common/ArchiveExtractCallback.h" +#include "7zip/UI/Common/LoadCodecs.h" +#include "7zip/UI/Common/PropIDUtils.h" + +#include "Windows/Defs.h" +#include "Windows/Error.h" +#include "Windows/FileDir.h" +#include "Windows/FileName.h" +#include "Windows/PropVariant.h" +#include "Windows/PropVariantConversions.h" + +#include <QtCore/QCoreApplication> +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QFileInfo> +#include <QtCore/QIODevice> +#include <QtCore/QMutex> +#include <QtCore/QMutexLocker> +#include <QtCore/QPointer> +#include <QtCore/QString> +#include <QtCore/QStringList> +#include <QtCore/QTemporaryFile> + +#ifdef _MSC_VER +#pragma warning(disable:4297) +#endif + +#ifdef Q_OS_WIN + +#include <time.h> +#define FILE_ATTRIBUTE_UNIX_EXTENSION 0x8000 /* trick for Unix */ +#define S_IFMT 00170000 +#define S_IFLNK 0120000 +#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) + +typedef BOOL (WINAPI *CREATEHARDLINK)(LPCSTR dst, LPCSTR str, LPSECURITY_ATTRIBUTES sa); + +bool CreateHardLinkWrapper(const QString &dest, const QString &file) +{ + static HMODULE module = 0; + static CREATEHARDLINK proc = 0; + + if (module == 0) + module = LoadLibrary(L"kernel32.dll"); + if (module == 0) + return false; + if (proc == 0) + proc = (CREATEHARDLINK) GetProcAddress(module, "CreateHardLinkA"); + if (proc == 0) + return false; + QString target = file; + if (!QFileInfo(file).isAbsolute()) + { + target = QFileInfo(dest).dir().absoluteFilePath(file); + } + const QString link = QDir::toNativeSeparators(dest); + const QString existingFile = QDir::toNativeSeparators(target); + return proc(link.toLocal8Bit(), existingFile.toLocal8Bit(), 0); +} + +#else +#include <sys/stat.h> +#endif + +#include <iostream> +#include <memory> + +#include <cassert> + +using namespace Lib7z; +using namespace NWindows; + +namespace { +/** +* RAII class to create a directory (tryCreate()) and delete it on destruction unless released. +*/ +struct DirectoryGuard { + explicit DirectoryGuard(const QString &path) + : m_path(path), + m_created(false), + m_released(false) + { + const QRegExp re(QLatin1String("\\\\|/")); + const QLatin1String sep("/"); + m_path.replace(re, sep); + } + + ~DirectoryGuard() + { + if (!m_created || m_released) + return; + QDir dir(m_path); + if (!dir.rmdir(m_path)) + qWarning() << "Could not delete directory " << m_path; + } + + /** + * Tries to create the directorie structure. + * Returns a list of every directory created. + */ + QStringList tryCreate() { + if (m_path.isEmpty()) + return QStringList(); + + const QFileInfo fi(m_path); + if (fi.exists() && fi.isDir()) + return QStringList(); + if (fi.exists() && !fi.isDir()) + throw SevenZipException(QObject::tr("Path exists but is not a folder: %1").arg(m_path)); + + QStringList created; + + QDir toCreate(m_path); + while (!toCreate.exists()) + { + QString p = toCreate.absolutePath(); + created.push_front(p); + p = p.section(QLatin1Char('/'), 0, -2); + toCreate = QDir(p); + } + + QDir dir(m_path); + m_created = dir.mkpath(m_path); + if (!m_created) + throw SevenZipException(QObject::tr("Could not create folder: %1").arg(m_path)); + + return created; + } + + void release() { + m_released = true; + } + + QString m_path; + bool m_created; + bool m_released; +}; +} + +static void throwIfNotOK(HRESULT result, const QString &msg) +{ + if (result != S_OK) + throw SevenZipException(msg); +} + +static UString QString2UString(const QString &str) +{ + return str.toStdWString().c_str(); +} + +static QString UString2QString(const UString& str) +{ + return QString::fromStdWString(static_cast<const wchar_t*>(str)); +} + +static QString generateTempFileName() +{ + QTemporaryFile tmp; + if (!tmp.open()) + throw SevenZipException(QObject::tr("Could not create temporary file")); + return QDir::toNativeSeparators(tmp.fileName()); +} + +/* +static QStringList UStringVector2QStringList(const UStringVector& vec) +{ +QStringList res; +for (int i = 0; i < vec.Size(); ++i) +res += UString2QString(vec[i]); +return res; +} +*/ + +static NCOM::CPropVariant readProperty(IInArchive* archive, int index, int propId) +{ + NCOM::CPropVariant prop; + throwIfNotOK(archive->GetProperty(index, propId, &prop), QObject::tr("Could not retrieve property %1 " + "for item %2").arg(QString::number(propId), QString::number(index))); + return prop; +} + +static bool IsFileTimeZero(const FILETIME *lpFileTime) +{ + return (lpFileTime->dwLowDateTime == 0) && (lpFileTime->dwHighDateTime == 0); +} + +static bool IsDST(const QDateTime& datetime = QDateTime()) +{ + const time_t seconds = static_cast< time_t >(datetime.isValid() ? datetime.toTime_t() + : QDateTime::currentDateTime().toTime_t()); + const tm* const t = localtime(&seconds); + return t->tm_isdst; +} + +static + QDateTime getDateTimeProperty(IInArchive* archive, int index, int propId, const QDateTime& defaultValue) +{ + const NCOM::CPropVariant prop = readProperty(archive, index, propId); + if (prop.vt != VT_FILETIME) { + throw SevenZipException(QObject::tr("Property %1 for item %2 not of type VT_FILETIME but %3") + .arg(QString::number(propId), QString::number(index), QString::number(prop.vt))); + } + if (IsFileTimeZero(&prop.filetime)) + return defaultValue; + + FILETIME localFileTime; + if (!FileTimeToLocalFileTime(&prop.filetime, &localFileTime)) + throw SevenZipException(QObject::tr("Could not convert file time to local time")); + + SYSTEMTIME st; + if (!BOOLToBool(FileTimeToSystemTime(&localFileTime, &st))) + throw SevenZipException(QObject::tr("Could not convert local file time to system time")); + + const QDate date(st.wYear, st.wMonth, st.wDay); + const QTime time(st.wHour, st.wMinute, st.wSecond); + QDateTime result(date, time); + + // fix daylight saving time + const bool dst = IsDST(); + if (dst != IsDST(result)) + result = result.addSecs(dst ? -3600 : 3600); + + return result; +} + +static quint64 getUInt64Property(IInArchive* archive, int index, int propId, quint64 defaultValue=0) +{ + const NCOM::CPropVariant prop = readProperty(archive, index, propId); + if (prop.vt == VT_EMPTY) + return defaultValue; + return static_cast<quint64>(ConvertPropVariantToUInt64(prop)); +} + +static quint32 getUInt32Property(IInArchive* archive, int index, int propId, quint32 defaultValue=0) +{ + const NCOM::CPropVariant prop = readProperty(archive, index, propId); + if (prop.vt == VT_EMPTY) + return defaultValue; + return static_cast< quint32 >(prop.ulVal); +} + +static QFile::Permissions getPermissions(IInArchive* archive, int index, bool* hasPermissions = 0) +{ + quint32 attributes = getUInt32Property(archive, index, kpidAttrib, 0); + QFile::Permissions permissions = 0; + if (attributes & FILE_ATTRIBUTE_UNIX_EXTENSION) { + if (hasPermissions != 0) + *hasPermissions = true; + // filter the unix permissions + attributes = (attributes >> 16) & 0777; + permissions |= static_cast< QFile::Permissions >((attributes & 0700) << 2); // owner rights + permissions |= static_cast< QFile::Permissions >((attributes & 0070) << 1); // group + permissions |= static_cast< QFile::Permissions >((attributes & 0007) << 0); // and world rights + } else if (hasPermissions != 0) { + *hasPermissions = false; + } + return permissions; +} + +namespace Lib7z { +class QIODeviceSequentialOutStream : public ISequentialOutStream, public CMyUnknownImp +{ +public: + MY_UNKNOWN_IMP + explicit QIODeviceSequentialOutStream(QIODevice* device); + ~QIODeviceSequentialOutStream(); + + /* reimp */ STDMETHOD(Write)(const void* data, UInt32 size, UInt32* processedSize); + +private: + QPointer<QIODevice> m_device; + const bool closeOnDestroy; +}; + +QIODeviceSequentialOutStream::QIODeviceSequentialOutStream(QIODevice* device) + : ISequentialOutStream(), + CMyUnknownImp(), + m_device(device), + closeOnDestroy(!device->isOpen()) +{ + assert(m_device); + if (closeOnDestroy) + m_device->open(QIODevice::WriteOnly); +} + +QIODeviceSequentialOutStream::~QIODeviceSequentialOutStream() +{ + if (closeOnDestroy) + { + m_device->close(); + delete m_device; + } +} + +HRESULT QIODeviceSequentialOutStream::Write(const void* data, UInt32 size, UInt32* processedSize) +{ + if (!m_device) { + if (processedSize) + *processedSize = 0; + return E_FAIL; + } + if (closeOnDestroy && !m_device->isOpen()) { + const bool opened = m_device->open(QIODevice::WriteOnly); + if (!opened) { + if (processedSize) + *processedSize = 0; + return E_FAIL; + } + } + + const qint64 written = m_device->write(reinterpret_cast<const char*>(data), size); + if (processedSize) + *processedSize = written; + return written >= 0 ? S_OK : E_FAIL; +} + +class QIODeviceInStream : public IInStream, public CMyUnknownImp +{ +public: + MY_UNKNOWN_IMP + + explicit QIODeviceInStream(QIODevice* device) : IInStream(), CMyUnknownImp(), m_device(device) + { + assert(m_device); + assert(!m_device->isSequential()); + } + + /* reimp */ STDMETHOD(Read)(void* data, UInt32 size, UInt32* processedSize) + { + assert(m_device); + assert(m_device->isReadable()); + const qint64 actual = m_device->read(reinterpret_cast<char*>(data), size); + Q_ASSERT(actual != 0 || m_device->atEnd()); + if (processedSize) + *processedSize = actual; + return actual >= 0 ? S_OK : E_FAIL; + } + + /* reimp */ STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64* newPosition) + { + assert(m_device); + assert(!m_device->isSequential()); + assert(m_device->isReadable()); + if (seekOrigin > STREAM_SEEK_END) + return STG_E_INVALIDFUNCTION; + UInt64 np = 0; + switch(seekOrigin) { + case STREAM_SEEK_SET: + np = offset; + break; + case STREAM_SEEK_CUR: + np = m_device->pos() + offset; + break; + case STREAM_SEEK_END: + np = m_device->size() + offset; + break; + default: + return STG_E_INVALIDFUNCTION; + } + + np = qBound(static_cast<UInt64>(0), np, static_cast<UInt64>(m_device->size() - 1)); + const bool ok = m_device->seek(np); + if (newPosition) + *newPosition = np; + return ok ? S_OK : E_FAIL; + } + +private: + QPointer<QIODevice> m_device; +}; +} + +File::File() + : permissions(0) + , uncompressedSize(0) + , compressedSize(0) + , isDirectory(false) +{ +} + +QVector<File> File::subtreeInPreorder() const +{ + QVector<File> res; + res += *this; + Q_FOREACH (const File& child, children) + res += child.subtreeInPreorder(); + return res; +} + +bool File::operator<(const File& other) const +{ + if (path != other.path) + return path < other.path; + if (mtime != other.mtime) + return mtime < other.mtime; + if (uncompressedSize != other.uncompressedSize) + return uncompressedSize < other.uncompressedSize; + if (compressedSize != other.compressedSize) + return compressedSize < other.compressedSize; + if (isDirectory != other.isDirectory) + return !isDirectory; + if (permissions != other.permissions) + return permissions < other.permissions; + return false; +} + +bool File::operator==(const File& other) const +{ + return mtime == other.mtime + && path == other.path + && uncompressedSize == other.uncompressedSize + && compressedSize == other.compressedSize + && isDirectory == other.isDirectory + && children == other.children + && (permissions == other.permissions + || permissions == static_cast< QFile::Permissions >(-1) + || other.permissions == static_cast< QFile::Permissions >(-1)); +} + +QByteArray Lib7z::formatKeyValuePairs(const QVariantList& l) +{ + assert(l.size() % 2 == 0); + QByteArray res; + for (QVariantList::ConstIterator it = l.constBegin(); it != l.constEnd(); ++it) { + if (!res.isEmpty()) + res += ", "; + res += qPrintable(it->toString()) + QByteArray(" = "); + ++it; + res += qPrintable(it->toString()); + } + return res; +} + +class Job::Private +{ +public: + Private() + : error(Lib7z::NoError) + {} + + int error; + QString errorString; +}; + +Job::Job(QObject* parent) + : QObject(parent) + , QRunnable() + , d(new Private) +{ +} + +Job::~Job() +{ + delete d; +} + +void Job::emitResult() +{ + emit finished(this); +} + +void Job::setError(int code) +{ + d->error = code; +} + +void Job::setErrorString(const QString &str) +{ + d->errorString = str; +} + +void Job::emitProgress(qint64 completed, qint64 total) +{ + emit progress(completed, total); +} + +int Job::error() const +{ + return d->error; +} + +bool Job::hasError() const +{ + return d->error != NoError; +} + +void Job::run() +{ + doStart(); +} + +QString Job::errorString() const +{ + return d->errorString; +} + +void Job::start() +{ + QMetaObject::invokeMethod(this, "doStart", Qt::QueuedConnection); +} + +class ListArchiveJob::Private +{ +public: + QPointer<QIODevice> archive; + QVector<File> files; +}; + +ListArchiveJob::ListArchiveJob(QObject* parent) + : Job(parent) + , d(new Private) +{ +} + +ListArchiveJob::~ListArchiveJob() +{ + delete d; +} + +QIODevice* ListArchiveJob::archive() const +{ + return d->archive; +} + +void ListArchiveJob::setArchive(QIODevice* device) +{ + d->archive = device; +} + +QVector<File> ListArchiveJob::index() const +{ + return d->files; +} + +class OpenArchiveInfo +{ +private: + OpenArchiveInfo(QIODevice* device) + : codecs(new CCodecs) + { + throwIfNotOK(codecs->Load(), QObject::tr("Could not load codecs")); + + if (!codecs->FindFormatForArchiveType(L"", formatIndices)) + throw SevenZipException(QObject::tr("Could not retrieve default format")); + + stream = new QIODeviceInStream(device); + throwIfNotOK(archiveLink.Open2(codecs.data(), formatIndices, false, stream, UString(), 0), + QObject::tr("Could not open archive")); + if (archiveLink.Arcs.Size() == 0) + throw SevenZipException(QObject::tr("No CArc found")); + + m_cleaner = new OpenArchiveInfoCleaner(); + m_cleaner->moveToThread(device->thread()); + QObject::connect(device, SIGNAL(destroyed(QObject*)), m_cleaner, SLOT(deviceDestroyed(QObject*))); + } + +public: + ~OpenArchiveInfo() + { + m_cleaner->deleteLater(); + } + + static OpenArchiveInfo* value(QIODevice* device) + { + QMutexLocker _(&m_mutex); + if (!instances.contains(device)) + instances.insert(device, new OpenArchiveInfo(device)); + return instances.value(device); + } + + static OpenArchiveInfo* take(QIODevice *device) + { + QMutexLocker _(&m_mutex); + if (instances.contains(device)) + return instances.take(device); + return 0; + } + + CArchiveLink archiveLink; + +private: + CIntVector formatIndices; + CMyComPtr<IInStream> stream; + QScopedPointer<CCodecs> codecs; + OpenArchiveInfoCleaner *m_cleaner; + + static QMutex m_mutex; + static QMap< QIODevice*, OpenArchiveInfo* > instances; +}; + +QMutex OpenArchiveInfo::m_mutex; +QMap< QIODevice*, OpenArchiveInfo* > OpenArchiveInfo::instances; + +void OpenArchiveInfoCleaner::deviceDestroyed(QObject* dev) +{ + QIODevice* device = static_cast<QIODevice*>(dev); + Q_ASSERT(device); + delete OpenArchiveInfo::take(device); +} + +QVector<File> Lib7z::listArchive(QIODevice* archive) +{ + assert(archive); + try { + const OpenArchiveInfo* const openArchive = OpenArchiveInfo::value(archive); + + QVector<File> flat; + + for (int i = 0; i < openArchive->archiveLink.Arcs.Size(); ++i) { + const CArc& arc = openArchive->archiveLink.Arcs[i]; + IInArchive* const arch = arc.Archive; + + UInt32 numItems = 0; + throwIfNotOK(arch->GetNumberOfItems(&numItems), QObject::tr("Could not retrieve number of items " + "in archive")); + + flat.reserve(flat.size() + numItems); + for (uint item = 0; item < numItems; ++item) { + UString s; + throwIfNotOK(arc.GetItemPath(item, s), QObject::tr("Could not retrieve path of archive item " + "%1").arg(item)); + File f; + f.archiveIndex.setX(i); + f.archiveIndex.setY(item); + f.path = UString2QString(s).replace(QLatin1Char('\\'), QLatin1Char('/')); + IsArchiveItemFolder(arch, item, f.isDirectory); + f.permissions = getPermissions(arch, item); + f.mtime = getDateTimeProperty(arch, item, kpidMTime, QDateTime()); + f.uncompressedSize = getUInt64Property(arch, item, kpidSize, 0); + f.compressedSize = getUInt64Property(arch, item, kpidPackSize, 0); + flat.push_back(f); + qApp->processEvents(); + } + } + return flat; + } catch (const SevenZipException& e) { + throw e; + } catch (const char *err) { + throw SevenZipException(err); + } catch (...) { + throw SevenZipException(QObject::tr("Unknown exception caught (%1)") + .arg(QString::fromLatin1(Q_FUNC_INFO))); + } + return QVector<File>(); // never reached +} + +void ListArchiveJob::doStart() +{ + try { + if (!d->archive) + throw SevenZipException(tr("Could not list archive: QIODevice already destroyed.")); + d->files = listArchive(d->archive); + } catch (const SevenZipException& e) { + setError(Failed); + setErrorString(e.message()); + } catch (...) { + setError(Failed); + setErrorString(QObject::tr("Unknown exception caught (%1)").arg(QObject::tr("Failed"))); + } + emitResult(); +} + +class Lib7z::ExtractCallbackImpl : public IArchiveExtractCallback, public CMyUnknownImp +{ +public: + MY_UNKNOWN_IMP + explicit ExtractCallbackImpl(ExtractCallback* qq) + : q(qq), + currentIndex(0), + arc(0), + total(0), + completed(0), + device(0) + { + } + + void setTarget(QIODevice* dev) + { + device = dev; + } + + void setTarget(const QString &targetDirectory) + { + targetDir = targetDirectory; + } + + // this method will be called by CFolderOutStream::OpenFile to stream via + // CDecoder::CodeSpec extracted content to an output stream. + /* reimp */ STDMETHOD(GetStream)(UInt32 index, ISequentialOutStream** outStream, Int32 askExtractMode) + { + Q_UNUSED(askExtractMode) + *outStream = 0; + if (device != 0) { + CMyComPtr<ISequentialOutStream> stream = new QIODeviceSequentialOutStream(device); + *outStream = stream.Detach(); + return S_OK; + } else if (!targetDir.isEmpty()) { + assert(arc); + + currentIndex = index; + + UString s; + throwIfNotOK(arc->GetItemPath(index, s), QObject::tr("Could not retrieve path of archive item " + "%1").arg(index)); + const QString path = UString2QString(s).replace(QLatin1Char('\\'), QLatin1Char('/')); + + const QFileInfo fi(QString::fromLatin1("%1/%2").arg(targetDir, path)); + DirectoryGuard guard(fi.absolutePath()); + const QStringList directories = guard.tryCreate(); + + bool isDir = false; + IsArchiveItemFolder(arc->Archive, index, isDir); + if (isDir) + QDir(fi.absolutePath()).mkdir(fi.fileName()); + + // this makes sure that all directories created get removed as well + for (QStringList::const_iterator it = directories.begin(); it != directories.end(); ++it) + q->setCurrentFile(*it); + + if (!isDir && !q->prepareForFile(fi.absoluteFilePath())) + return E_FAIL; + + q->setCurrentFile(fi.absoluteFilePath()); + + if (!isDir) { + CMyComPtr< ISequentialOutStream > stream = new QIODeviceSequentialOutStream(new QFile(fi + .absoluteFilePath())); + *outStream = stream; + stream.Detach(); + } + + guard.release(); + return S_OK; + } + return E_FAIL; + } + + /* reimp */ STDMETHOD(PrepareOperation)(Int32 askExtractMode) + { + Q_UNUSED(askExtractMode) + return S_OK; + } + + /* reimp */ STDMETHOD(SetOperationResult)(Int32 resultEOperationResult) + { + Q_UNUSED(resultEOperationResult) + + if (!targetDir.isEmpty()) { + bool hasPerm; + const QFile::Permissions permissions = getPermissions(arc->Archive, currentIndex, &hasPerm); + if (hasPerm) { + UString s; + throwIfNotOK(arc->GetItemPath(currentIndex, s), QObject::tr("Could not retrieve path of " + "archive item %1").arg(currentIndex)); + const QString path = UString2QString(s).replace(QLatin1Char('\\'), QLatin1Char('/')); + const QFileInfo fi(QString::fromLatin1("%1/%2").arg(targetDir, path)); + QFile::setPermissions(fi.absoluteFilePath(), permissions); + + // do we have a symlink? + const quint32 attributes = getUInt32Property(arc->Archive, currentIndex, kpidAttrib, 0); + struct stat stat_info; + stat_info.st_mode = attributes >> 16; + if (S_ISLNK(stat_info.st_mode)) { + QFile f(fi.absoluteFilePath()); + f.open(QIODevice::ReadOnly); + const QByteArray path = f.readAll(); + f.close(); + f.remove(); +#ifdef Q_OS_WIN + if (!CreateHardLinkWrapper(fi.absoluteFilePath(), QLatin1String(path))) { + throw SevenZipException(QObject::tr("Could not create file system link at %1") + .arg(fi.absoluteFilePath())); + } +#else + if (!QFile::link(QString::fromLatin1(path), fi.absoluteFilePath())) { + throw SevenZipException(QObject::tr("Could not create softlink at %1") + .arg(fi.absoluteFilePath())); + } +#endif + } + } + } + + return S_OK; + } + + /* reimp */ STDMETHOD(SetTotal)(UInt64 t) + { + total = t; + return S_OK; + } + + /* reimp */ STDMETHOD(SetCompleted)(const UInt64* c) + { + completed = *c; + if (total > 0) + return q->setCompleted(completed, total); + return S_OK; + } + + void setArchive(const CArc* archive) + { + arc = archive; + } + +private: + ExtractCallback* const q; + UInt32 currentIndex; + const CArc* arc; + UInt64 total; + UInt64 completed; + QPointer<QIODevice> device; + QString targetDir; +}; + + +class Lib7z::ExtractCallbackPrivate +{ +public: + explicit ExtractCallbackPrivate(ExtractCallback* qq) + { + impl = new ExtractCallbackImpl(qq); + } + + CMyComPtr<ExtractCallbackImpl> impl; +}; + +ExtractCallback::ExtractCallback() + : d(new ExtractCallbackPrivate(this)) +{ +} + +ExtractCallback::~ExtractCallback() +{ + delete d; +} + +ExtractCallbackImpl* ExtractCallback::impl() +{ + return d->impl; +} + +const ExtractCallbackImpl* ExtractCallback::impl() const +{ + return d->impl; +} + +void ExtractCallback::setTarget(QIODevice* dev) +{ + d->impl->setTarget(dev); +} + +void ExtractCallback::setTarget(const QString &dir) +{ + d->impl->setTarget(dir); +} + +HRESULT ExtractCallback::setCompleted(quint64, quint64) +{ + return S_OK; +} + +void ExtractCallback::setCurrentFile(const QString&) +{ +} + +bool ExtractCallback::prepareForFile(const QString&) +{ + return true; +} + +class Lib7z::ExtractCallbackJobImpl : public ExtractCallback +{ +public: + explicit ExtractCallbackJobImpl(ExtractItemJob* j) + : ExtractCallback() + , job(j) + {} + +private: + /* reimp */ HRESULT setCompleted(quint64 c, quint64 t) + { + emit job->progress(c, t); + return S_OK; + } + + ExtractItemJob* const job; +}; + +class Lib7z::UpdateCallbackImpl : public IUpdateCallbackUI2, public CMyUnknownImp +{ +public: + MY_UNKNOWN_IMP + explicit UpdateCallbackImpl(UpdateCallback* qq) + : q(qq) + { + } + virtual ~UpdateCallbackImpl() + { + } + /** + * \reimp + */ + HRESULT SetTotal(UInt64) + { + return S_OK; + } + /** + * \reimp + */ + HRESULT SetCompleted(const UInt64*) + { + return S_OK; + } + HRESULT SetRatioInfo(const UInt64*, const UInt64*) + { + return S_OK; + } + HRESULT CheckBreak() + { + return S_OK; + } + HRESULT Finilize() + { + return S_OK; + } + HRESULT SetNumFiles(UInt64) + { + return S_OK; + } + HRESULT GetStream(const wchar_t*, bool) + { + return S_OK; + } + HRESULT OpenFileError(const wchar_t*, DWORD) + { + return S_OK; + } + HRESULT CryptoGetTextPassword2(Int32* passwordIsDefined, OLECHAR** password) + { + *password = 0; + *passwordIsDefined = false; + return S_OK; + } + HRESULT CryptoGetTextPassword(OLECHAR**) + { + return E_NOTIMPL; + } + HRESULT OpenResult(const wchar_t*, LONG) + { + return S_OK; + } + HRESULT StartScanning() + { + return S_OK; + } + HRESULT ScanProgress(UInt64, UInt64, const wchar_t*) + { + return S_OK; + } + HRESULT CanNotFindError(const wchar_t*, DWORD) + { + return S_OK; + } + HRESULT FinishScanning() + { + return S_OK; + } + HRESULT StartArchive(const wchar_t*, bool) + { + return S_OK; + } + HRESULT FinishArchive() + { + return S_OK; + } + + /** + * \reimp + */ + HRESULT SetOperationResult(Int32) + { + // TODO! + return S_OK; + } + void setSource(const QStringList &dir) + { + sourceDir = dir; + } + void setTarget(QIODevice* archive) + { + target = archive; + } + +private: + UpdateCallback* const q; + + QIODevice* target; + QStringList sourceDir; +}; + +class Lib7z::UpdateCallbackPrivate +{ +public: + explicit UpdateCallbackPrivate(UpdateCallback* qq) + { + m_impl = new UpdateCallbackImpl(qq); + } + + UpdateCallbackImpl* impl() + { + return m_impl; + } + +private: + CMyComPtr< UpdateCallbackImpl > m_impl; +}; + +UpdateCallback::UpdateCallback() + : d(new UpdateCallbackPrivate(this)) +{ +} + +UpdateCallback::~UpdateCallback() +{ + delete d; +} + +UpdateCallbackImpl* UpdateCallback::impl() +{ + return d->impl(); +} + +void UpdateCallback::setSource(const QStringList &dir) +{ + d->impl()->setSource(dir); +} + +void UpdateCallback::setTarget(QIODevice* target) +{ + d->impl()->setTarget(target); +} + +class ExtractItemJob::Private +{ +public: + Private(ExtractItemJob* qq) + : q(qq), + target(0), + callback(new ExtractCallbackJobImpl(q)) + { + } + + ExtractItemJob* q; + File item; + QPointer<QIODevice> archive; + QString targetDirectory; + QIODevice* target; + ExtractCallback* callback; +}; + +ExtractItemJob::ExtractItemJob(QObject* parent) + : Job(parent) + , d(new Private(this)) +{ +} + +ExtractItemJob::~ExtractItemJob() +{ + delete d; +} + +File ExtractItemJob::item() const +{ + return d->item; +} + +void ExtractItemJob::setItem(const File& item) +{ + d->item = item; +} + +QIODevice* ExtractItemJob::archive() const +{ + return d->archive; +} + +void ExtractItemJob::setArchive(QIODevice* archive) +{ + d->archive = archive; +} + +QString ExtractItemJob::targetDirectory() const +{ + return d->targetDirectory; +} + +void ExtractItemJob::setTargetDirectory(const QString &dir) +{ + d->targetDirectory = dir; + d->target = 0; +} + +void ExtractItemJob::setTarget(QIODevice* dev) +{ + d->target = dev; +} + +void Lib7z::createArchive(QIODevice* archive, const QStringList &sourceDirectories, UpdateCallback* callback) +{ + assert(archive); + + std::auto_ptr< UpdateCallback > dummyCallback(callback ? 0 : new UpdateCallback); + if (!callback) + callback = dummyCallback.get(); + + try + { + std::auto_ptr< CCodecs > codecs(new CCodecs); + throwIfNotOK(codecs->Load(), QObject::tr("Could not load codecs")); + + CIntVector formatIndices; + + if (!codecs.get()->FindFormatForArchiveType(L"", formatIndices)) + throw SevenZipException(QObject::tr("Could not retrieve default format")); + + // yes this is crap, but there seems to be no streaming solution to this... + + const QString tempFile = generateTempFileName(); + + NWildcard::CCensor censor; + foreach(QString dir, sourceDirectories) { + const UString sourceDirectoryPath = QString2UString(QDir::toNativeSeparators(dir)); + if (UString2QString(sourceDirectoryPath) != QDir::toNativeSeparators(dir)) + throw UString2QString(sourceDirectoryPath).toLatin1().data(); + censor.AddItem(true, sourceDirectoryPath, true); + } + + CUpdateOptions options; + CArchivePath archivePath; + archivePath.ParseFromPath(QString2UString(tempFile)); + CUpdateArchiveCommand command; + command.ArchivePath = archivePath; + command.ActionSet = NUpdateArchive::kAddActionSet; + options.Commands.Add(command); + options.ArchivePath = archivePath; + options.MethodMode.FormatIndex = codecs->FindFormatForArchiveType(L"7z"); + + CUpdateErrorInfo errorInfo; + + callback->setTarget(archive); + callback->setSource(sourceDirectories); + const HRESULT res = UpdateArchive(codecs.get(), censor, options, errorInfo, 0, callback->impl()); + if (res != S_OK || !QFile::exists(tempFile)) + throw SevenZipException(QObject::tr("Could not create archive %1").arg(tempFile)); + { + //TODO remove temp file even if one the following throws + QFile file(tempFile); + QInstaller::openForRead(&file, tempFile); + QInstaller::blockingCopy(&file, archive, file.size()); + } + QFile file(tempFile); + if (!file.remove()) { + qWarning("%s: Could not remove temporary file %s: %s", Q_FUNC_INFO, qPrintable(tempFile), + qPrintable(file.errorString())); + } + } catch (const char *err) { + std::cout << err << std::endl; + throw SevenZipException(err); + } catch (const QInstaller::Error &err) { + throw SevenZipException(err.message()); + } catch (...) { + throw SevenZipException(QObject::tr("Unknown exception caught (%1)") + .arg(QString::fromLatin1(Q_FUNC_INFO))); + } +} + +void Lib7z::extractArchive(QIODevice* archive, const File& item, QIODevice* target, ExtractCallback* callback) +{ + assert(archive); + assert(target); + + std::auto_ptr<ExtractCallback> dummyCallback(callback ? 0 : new ExtractCallback); + if (!callback) + callback = dummyCallback.get(); + + try { + const OpenArchiveInfo* const openArchive = OpenArchiveInfo::value(archive); + + const int arcIdx = item.archiveIndex.x(); + if (arcIdx < 0 || arcIdx >= openArchive->archiveLink.Arcs.Size()) { + throw SevenZipException(QObject::tr("CArc index %1 out of bounds [0, %2]") + .arg(openArchive->archiveLink.Arcs.Size() - 1)); + } + const CArc& arc = openArchive->archiveLink.Arcs[arcIdx]; + IInArchive* const parchive = arc.Archive; + + const UInt32 itemIdx = item.archiveIndex.y(); + UInt32 numItems = 0; + throwIfNotOK(parchive->GetNumberOfItems(&numItems), QObject::tr("Could not retrieve number of items " + "in archive")); + if (itemIdx >= numItems) { + throw SevenZipException(QObject::tr("Item index %1 out of bounds [0, %2]").arg(itemIdx) + .arg(numItems - 1)); + } + + UString s; + throwIfNotOK(arc.GetItemPath(itemIdx, s), QObject::tr("Could not retrieve path of archive item %1") + .arg(itemIdx)); + assert(item.path == UString2QString(s).replace(QLatin1Char('\\'), QLatin1Char('/'))); + + callback->setTarget(target); + const LONG extractResult = parchive->Extract(&itemIdx, 1, /*testmode=*/1, callback->impl()); + //TODO: how to interpret result? + throwIfNotOK(extractResult, QObject::tr("Extracting %1 failed.").arg(item.path)); + } catch (const char *err) { + throw SevenZipException(err); + } catch (...) { + throw SevenZipException(QObject::tr("Unknown exception caught (%1)") + .arg(QString::fromLatin1(Q_FUNC_INFO))); + } +} + +void Lib7z::extractArchive(QIODevice* archive, const File& item, const QString &targetDirectory, + ExtractCallback* callback) +{ + assert(archive); + + std::auto_ptr<ExtractCallback> dummyCallback(callback ? 0 : new ExtractCallback); + if (!callback) + callback = dummyCallback.get(); + + QFileInfo fi(targetDirectory + QLatin1String("/") + item.path); + DirectoryGuard outDir(fi.absolutePath()); + outDir.tryCreate(); + QFile out(fi.absoluteFilePath()); + if (!out.open(QIODevice::WriteOnly)) { //TODO use tmp file + throw SevenZipException(QObject::tr("Could not create output file for writing: %1") + .arg(fi.absoluteFilePath())); + } + if (item.permissions) + out.setPermissions(item.permissions); + callback->setTarget(&out); + extractArchive(archive, item, &out, callback); + outDir.release(); +} + +void Lib7z::extractArchive(QIODevice* archive, const QString &targetDirectory, ExtractCallback* callback) +{ + assert(archive); + + std::auto_ptr<ExtractCallback> dummyCallback(callback ? 0 : new ExtractCallback); + if (!callback) + callback = dummyCallback.get(); + + callback->setTarget(targetDirectory); + + const QFileInfo fi(targetDirectory); + DirectoryGuard outDir(fi.absolutePath()); + outDir.tryCreate(); + + const OpenArchiveInfo* const openArchive = OpenArchiveInfo::value(archive); + + for (int a = 0; a < openArchive->archiveLink.Arcs.Size(); ++a) + { + const CArc& arc = openArchive->archiveLink.Arcs[a]; + IInArchive* const arch = arc.Archive; + callback->impl()->setArchive(&arc); + const LONG extractResult = arch->Extract(0, static_cast< UInt32 >(-1), false, callback->impl()); + //TODO is it possible to get a more detailed error? + throwIfNotOK(extractResult, QObject::tr("Extraction failed.")); + } + + outDir.release(); +} + +bool Lib7z::isSupportedArchive(const QString &archive) +{ + QFile file(archive); + if (!file.open(QIODevice::ReadOnly)) + return false; + + return isSupportedArchive(&file); +} + +bool Lib7z::isSupportedArchive(QIODevice* archive) +{ + assert(archive); + assert(!archive->isSequential()); + const qint64 initialPos = archive->pos(); + try { + std::auto_ptr<CCodecs> codecs(new CCodecs); + throwIfNotOK(codecs->Load(), QObject::tr("Could not load codecs")); + + CIntVector formatIndices; + + if (!codecs.get()->FindFormatForArchiveType(L"", formatIndices)) + throw SevenZipException(QObject::tr("Could not retrieve default format")); + + CArchiveLink archiveLink; + //CMyComPtr is needed, otherwise it crashes in OpenStream() + const CMyComPtr<IInStream> stream = new QIODeviceInStream(archive); + + const HRESULT result = archiveLink.Open2(codecs.get(), formatIndices, /*stdInMode*/false, stream, + UString(), 0); + + archive->seek(initialPos); + return result == S_OK; + } catch (const SevenZipException& e) { + archive->seek(initialPos); + throw e; + } catch (const char *err) { + archive->seek(initialPos); + throw SevenZipException(err); + } catch (...) { + archive->seek(initialPos); + throw SevenZipException(QObject::tr("Unknown exception caught (%1)") + .arg(QString::fromLatin1(Q_FUNC_INFO))); + } + return false; // never reached +} + +void ExtractItemJob::doStart() +{ + try { + if (!d->archive) + throw SevenZipException(tr("Could not list archive: QIODevice not set or already destroyed.")); + if (d->target) + extractArchive(d->archive, d->item, d->target, d->callback); + else if (!d->item.path.isEmpty()) + extractArchive(d->archive, d->item, d->targetDirectory, d->callback); + else + extractArchive(d->archive, d->targetDirectory, d->callback); + } catch (const SevenZipException& e) { + setError(Failed); + setErrorString(e.message()); + } catch (...) { + setError(Failed); + setErrorString(QObject::tr("Unknown exception caught (%1)").arg(QObject::tr("Failed"))); + } + emitResult(); +} diff --git a/src/libs/installer/lib7z_facade.h b/src/libs/installer/lib7z_facade.h new file mode 100644 index 000000000..58774df11 --- /dev/null +++ b/src/libs/installer/lib7z_facade.h @@ -0,0 +1,242 @@ +#ifndef LIB7Z_FACADE_H +#define LIB7Z_FACADE_H + +#include "installer_global.h" + +#include "Common/MyWindows.h" + +#include <QtCore/QCoreApplication> +#include <QtCore/QDateTime> +#include <QtCore/QFile> +#include <QtCore/QPoint> +#include <QtCore/QRunnable> +#include <QtCore/QString> +#include <QtCore/QVector> +#include <QtCore/QVariant> + +#include <stdexcept> +#include <string> + +QT_BEGIN_NAMESPACE +class QStringList; +template <typename T> class QVector; +QT_END_NAMESPACE + +namespace Lib7z { + + class INSTALLER_EXPORT SevenZipException : public std::runtime_error { + public: + explicit SevenZipException( const QString& msg ) : std::runtime_error( msg.toStdString() ), m_message( msg ) {} + explicit SevenZipException( const char* msg ) : std::runtime_error( msg ), m_message( QString::fromLocal8Bit( msg ) ) {} + explicit SevenZipException( const std::string& msg ) : std::runtime_error( msg ), m_message( QString::fromLocal8Bit( msg.c_str() ) ) {} + + ~SevenZipException() throw() {} + QString message() const { return m_message; } + private: + QString m_message; + }; + + class INSTALLER_EXPORT File { + public: + File(); + QVector<File> subtreeInPreorder() const; + + bool operator<( const File& other ) const; + bool operator==( const File& other ) const; + + QFile::Permissions permissions; + QString path; + QString name; + QDateTime mtime; + quint64 uncompressedSize; + quint64 compressedSize; + bool isDirectory; + QVector<File> children; + QPoint archiveIndex; + }; + + class ExtractCallbackPrivate; + class ExtractCallbackImpl; + + class ExtractCallback { + friend class ::Lib7z::ExtractCallbackImpl; + public: + ExtractCallback(); + virtual ~ExtractCallback(); + + void setTarget( QIODevice* archive ); + void setTarget( const QString& dir ); + + protected: + /** + * Reimplement to prepare for file @p filename to be extracted, e.g. by renaming existing files. + * @return @p true if the preparation was successful and extraction can be continued. + * If @p false is returned, the extraction will be aborted. Default implementation returns @p true. + */ + virtual bool prepareForFile( const QString& filename ); + virtual void setCurrentFile( const QString& filename ); + virtual HRESULT setCompleted( quint64 completed, quint64 total ); + + public: //for internal use + const ExtractCallbackImpl* impl() const; + ExtractCallbackImpl* impl(); + + private: + ExtractCallbackPrivate* const d; + }; + + class UpdateCallbackPrivate; + class UpdateCallbackImpl; + + class UpdateCallback + { + friend class ::Lib7z::UpdateCallbackImpl; + public: + UpdateCallback(); + virtual ~UpdateCallback(); + + void setTarget( QIODevice* archive ); + void setSource( const QStringList& dir ); + + virtual UpdateCallbackImpl* impl(); + + private: + UpdateCallbackPrivate* const d; + }; + + class OpenArchiveInfoCleaner : public QObject { + Q_OBJECT + public: + OpenArchiveInfoCleaner() {} + private Q_SLOTS: + void deviceDestroyed(QObject*); + }; + + /* + * @throws Lib7z::SevenZipException + */ + void INSTALLER_EXPORT extractArchive( QIODevice* archive, const File& item, QIODevice* out, ExtractCallback* callback=0 ); + + /* + * @throws Lib7z::SevenZipException + */ + void INSTALLER_EXPORT extractArchive( QIODevice* archive, const File& item, const QString& targetDirectory, ExtractCallback* callback=0 ); + + /* + * @throws Lib7z::SevenZipException + */ + void INSTALLER_EXPORT extractArchive( QIODevice* archive, const QString& targetDirectory, ExtractCallback* callback=0 ); + + /* + * @thows Lib7z::SevenZipException + */ + void INSTALLER_EXPORT createArchive( QIODevice* archive, const QStringList& sourceDirectory, UpdateCallback* callback = 0 ); + + /* + * @throws Lib7z::SevenZipException + */ + QVector<File> INSTALLER_EXPORT listArchive( QIODevice* archive ); + + /* + * @throws Lib7z::SevenZipException + */ + bool INSTALLER_EXPORT isSupportedArchive( QIODevice* archive ); + + /* + * @throws Lib7z::SevenZipException + */ + bool INSTALLER_EXPORT isSupportedArchive( const QString& archive ); + + + + enum Error { + NoError=0, + Failed=1, + UserDefinedError=128 + }; + + class ExtractCallbackJobImpl; + + class INSTALLER_EXPORT Job : public QObject, public QRunnable { + friend class ::Lib7z::ExtractCallbackJobImpl; + Q_OBJECT + public: + + explicit Job( QObject* parent=0 ); + ~Job(); + void start(); + int error() const; + bool hasError() const; + QString errorString() const; + + /* reimp */ void run(); + + protected: + void emitResult(); + void setError( int code ); + void setErrorString( const QString& err ); + void emitProgress( qint64 completed, qint64 total ); + + Q_SIGNALS: + void finished( Lib7z::Job* job ); + void progress( qint64 completed, qint64 total ); + + private Q_SLOTS: + virtual void doStart() = 0; + + private: + class Private; + Private* const d; + }; + + class INSTALLER_EXPORT ListArchiveJob : public Job { + Q_OBJECT + public: + + explicit ListArchiveJob( QObject* parent=0 ); + ~ListArchiveJob(); + + QIODevice* archive() const; + void setArchive( QIODevice* archive ); + + QVector<File> index() const; + + private: + /* reimp */ void doStart(); + + private: + class Private; + Private* const d; + }; + + class INSTALLER_EXPORT ExtractItemJob : public Job { + Q_OBJECT + friend class ::Lib7z::ExtractCallback; + public: + + explicit ExtractItemJob( QObject* parent=0 ); + ~ExtractItemJob(); + + File item() const; + void setItem( const File& item ); + + QIODevice* archive() const; + void setArchive( QIODevice* archive ); + + QString targetDirectory() const; + void setTargetDirectory( const QString& dir ); + + void setTarget( QIODevice* dev ); + + private: + /* reimp */ void doStart(); + + private: + class Private; + Private* const d; + }; + + QByteArray INSTALLER_EXPORT formatKeyValuePairs( const QVariantList& l ); +} + +#endif // LIB7Z_FACADE_H diff --git a/src/libs/installer/licenseoperation.cpp b/src/libs/installer/licenseoperation.cpp new file mode 100644 index 000000000..d002afdb4 --- /dev/null +++ b/src/libs/installer/licenseoperation.cpp @@ -0,0 +1,119 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "licenseoperation.h" + +#include "packagemanagercore.h" +#include "settings.h" + +#include <QtCore/QDir> +#include <QtCore/QFile> +#include <QtCore/QTextStream> + +using namespace QInstaller; + +LicenseOperation::LicenseOperation() +{ + setName(QLatin1String("License")); +} + +void LicenseOperation::backup() +{ +} + +bool LicenseOperation::performOperation() +{ + QVariantMap licenses = value(QLatin1String("licenses")).toMap(); + if (licenses.isEmpty()) { + setError(UserDefinedError); + setErrorString(tr("No license files found to copy.")); + return false; + } + + PackageManagerCore *const core = qVariantValue<PackageManagerCore*>(value(QLatin1String("installer"))); + if (!core) { + setError( UserDefinedError ); + setErrorString(tr("Needed installer object in %1 operation is empty.").arg(name())); + return false; + } + + QString targetDir = QString::fromLatin1("%1/%2").arg(core->value(scTargetDir), + QLatin1String("Licenses")); + + QDir dir; + dir.mkpath(targetDir); + setArguments(QStringList(targetDir)); + + for (QVariantMap::const_iterator it = licenses.begin(); it != licenses.end(); ++it) { + QFile file(targetDir + QDir::separator() + it.key()); + if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + setError(UserDefinedError); + setErrorString(tr("Can not write license file: %1.").arg(targetDir + QDir::separator() + + it.key())); + return false; + } + + QTextStream stream(&file); + stream << it.value().toString(); + } + + return true; +} + +bool LicenseOperation::undoOperation() +{ + QVariantMap licenses = value(QLatin1String("licenses")).toMap(); + if (licenses.isEmpty()) { + setError(UserDefinedError); + setErrorString(tr("No license files found to delete.")); + return false; + } + + QString targetDir = arguments().value(0); + for (QVariantMap::const_iterator it = licenses.begin(); it != licenses.end(); ++it) + QFile::remove(targetDir + QDir::separator() + it.key()); + + QDir dir; + dir.rmdir(targetDir); + + return true; +} + +bool LicenseOperation::testOperation() +{ + return true; +} + +Operation *LicenseOperation::clone() const +{ + return new LicenseOperation(); +} diff --git a/src/libs/installer/licenseoperation.h b/src/libs/installer/licenseoperation.h new file mode 100644 index 000000000..324ca4e57 --- /dev/null +++ b/src/libs/installer/licenseoperation.h @@ -0,0 +1,54 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef LICENSEOPERATION_H +#define LICENSEOPERATION_H + +#include "qinstallerglobal.h" + +namespace QInstaller { + +class INSTALLER_EXPORT LicenseOperation : public Operation +{ +public: + LicenseOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation* clone() const; +}; + +} // namespace QInstaller + +#endif //LICENSEOPERATION_H diff --git a/src/libs/installer/linereplaceoperation.cpp b/src/libs/installer/linereplaceoperation.cpp new file mode 100644 index 000000000..94a8fb910 --- /dev/null +++ b/src/libs/installer/linereplaceoperation.cpp @@ -0,0 +1,112 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "linereplaceoperation.h" + +#include <QtCore/QDir> +#include <QtCore/QFile> +#include <QtCore/QTextStream> + +using namespace QInstaller; + +LineReplaceOperation::LineReplaceOperation() +{ + setName(QLatin1String("LineReplace")); +} + +void LineReplaceOperation::backup() +{ +} + +bool LineReplaceOperation::performOperation() +{ + const QStringList args = arguments(); + + // Arguments: + // 1. filename + // 2. startsWith Search-String + // 3. Replace-Line-String + if (args.count() != 3) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in %0: %1 arguments given, exactly 3 expected.").arg(name()).arg(args + .count())); + return false; + } + const QString fileName = args.at(0); + const QString searchString = args.at(1); + const QString replaceString = args.at(2); + + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + setError(UserDefinedError); + setErrorString(QObject::tr("Failed to open %1 for reading.").arg(fileName)); + return false; + } + + QString replacement; + QTextStream stream(&file); + while (!stream.atEnd()) { + const QString line = stream.readLine(); + if (line.trimmed().startsWith(searchString)) + replacement.append(replaceString + QLatin1String("\n")); + else + replacement.append(line + QLatin1String("\n")); + } + file.close(); + + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + setError(UserDefinedError); + setErrorString(QObject::tr("Failed to open %1 for writing.").arg(fileName)); + return false; + } + + stream.setDevice(&file); + stream << replacement; + file.close(); + + return true; +} + +bool LineReplaceOperation::undoOperation() +{ + return true; +} + +bool LineReplaceOperation::testOperation() +{ + return true; +} + +Operation *LineReplaceOperation::clone() const +{ + return new LineReplaceOperation(); +} diff --git a/src/libs/installer/linereplaceoperation.h b/src/libs/installer/linereplaceoperation.h new file mode 100644 index 000000000..e385169bb --- /dev/null +++ b/src/libs/installer/linereplaceoperation.h @@ -0,0 +1,54 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef LINEREPLACEOPERATION_H +#define LINEREPLACEOPERATION_H + +#include "qinstallerglobal.h" + +namespace QInstaller { + +class INSTALLER_EXPORT LineReplaceOperation : public Operation +{ +public: + LineReplaceOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation *clone() const; +}; + +} // namespace + +#endif // LINEREPLACEOPERATION_H diff --git a/src/libs/installer/macrelocateqt.cpp b/src/libs/installer/macrelocateqt.cpp new file mode 100644 index 000000000..98f8153c5 --- /dev/null +++ b/src/libs/installer/macrelocateqt.cpp @@ -0,0 +1,94 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "macrelocateqt.h" +#include "macreplaceinstallnamesoperation.h" + +#include <QtCore/QDebug> +#include <QtCore/QFile> + + +using namespace QInstaller; + +Relocator::Relocator() +{ +} + +bool Relocator::apply(const QString &qtInstallDir, const QString &targetDir) +{ +// Relocator::apply(/Users/rakeller/QtSDKtest2/Simulator/Qt/gcc) +// Relocator uses indicator: /QtSDKtest2operation 'QtPatch' with arguments: 'mac; /Users/rakeller/QtSDKtest2/Simulator/Qt/gcc' failed: Error while relocating Qt: "ReplaceInsta + if (qtInstallDir.isEmpty()) { + m_errorMessage = QLatin1String("qtInstallDir can't be empty"); + return false; + } + if (targetDir.isEmpty()) { + m_errorMessage = QLatin1String("targetDir can't be empty"); + return false; + } + qDebug() << Q_FUNC_INFO << qtInstallDir; + + m_errorMessage.clear(); + m_installDir.clear(); + + m_installDir = targetDir; + if (!m_installDir.endsWith(QLatin1Char('/'))) + m_installDir.append(QLatin1Char('/')); + if (!QFile::exists(qtInstallDir + QLatin1String("/bin/qmake"))) { + m_errorMessage = QLatin1String("This is not a Qt installation directory."); + return false; + } + + QString indicator = qtInstallDir; + indicator = indicator.replace(targetDir, QString()); + //to get realy only the first subdirectory as an indicator like the old behaviour was till Mobility don't use this qt patch hack + indicator = indicator.left(indicator.indexOf(QLatin1String("/"), 1)); + + qDebug() << "Relocator uses indicator:" << indicator; + QString replacement = targetDir; + + + MacReplaceInstallNamesOperation operation; + QStringList arguments; + arguments << indicator + << replacement + << qtInstallDir + QLatin1String("/plugins") + << qtInstallDir + QLatin1String("/lib") + << qtInstallDir + QLatin1String("/imports") + << qtInstallDir + QLatin1String("/bin"); + + operation.setArguments(arguments); + operation.performOperation(); + + m_errorMessage = operation.errorString(); + return m_errorMessage.isNull(); +} diff --git a/src/libs/installer/macrelocateqt.h b/src/libs/installer/macrelocateqt.h new file mode 100644 index 000000000..769cb11aa --- /dev/null +++ b/src/libs/installer/macrelocateqt.h @@ -0,0 +1,55 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef RELOCATOR_H +#define RELOCATOR_H + +#include <QString> + +namespace QInstaller { + +class Relocator +{ +public: + Relocator(); + + bool apply(const QString &qtInstallDir, const QString &targetDir); + QString errorMessage() const { return m_errorMessage; } + +private: + QString m_errorMessage; + QString m_installDir; +}; + +} // namespace QInstaller + +#endif // RELOCATOR_H diff --git a/src/libs/installer/macreplaceinstallnamesoperation.cpp b/src/libs/installer/macreplaceinstallnamesoperation.cpp new file mode 100644 index 000000000..69797b040 --- /dev/null +++ b/src/libs/installer/macreplaceinstallnamesoperation.cpp @@ -0,0 +1,273 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "macreplaceinstallnamesoperation.h" + +#include "qprocesswrapper.h" + +#include <QtCore/QBuffer> +#include <QtCore/QDebug> +#include <QtCore/QDirIterator> + + +using namespace QInstaller; + +MacReplaceInstallNamesOperation::MacReplaceInstallNamesOperation() +{ + setName(QLatin1String("ReplaceInstallNames")); +} + +void MacReplaceInstallNamesOperation::backup() +{ +} + +bool MacReplaceInstallNamesOperation::performOperation() +{ + // Arguments: + // 1. indicator to find the original build directory, + // means the path till this will be used to replace it with 2. + // 2. new/current target install directory(the replacement) + // 3. directory containing frameworks + // 4. other directory containing frameworks + // 5. other directory containing frameworks + // 6. ... + + if (arguments().count() < 3) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in %0: %1 arguments given, 3 expected.").arg(name()) + .arg(arguments().count())); + return false; + } + + QString indicator = arguments().at(0); + QString installationDir = arguments().at(1); + QStringList searchDirList = arguments().mid(2); + foreach (const QString &searchDir, searchDirList) { + if (!apply(indicator, installationDir, searchDir)) + return false; + } + + return true; +} + +bool MacReplaceInstallNamesOperation::undoOperation() +{ + return true; +} + +bool MacReplaceInstallNamesOperation::testOperation() +{ + return true; +} + +Operation *MacReplaceInstallNamesOperation::clone() const +{ + return new MacReplaceInstallNamesOperation; +} + +bool MacReplaceInstallNamesOperation::apply(const QString &indicator, const QString &installationDir, + const QString &searchDir) +{ + m_indicator = indicator; + m_installationDir = installationDir; + + QStringList alreadyPatchedFrameworks; + QLatin1String frameworkSuffix(".framework"); + + QDirIterator dirIterator(searchDir, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); + while (dirIterator.hasNext()) { + QString fileName = dirIterator.next(); + + //check that we don't do anything for already patched framework pathes + if (fileName.contains(frameworkSuffix)) { + QString alreadyPatchedSearchString = fileName.left(fileName.lastIndexOf(frameworkSuffix)) + + frameworkSuffix; + if (alreadyPatchedFrameworks.contains(alreadyPatchedSearchString)) { + continue; + } + } + if (dirIterator.fileInfo().isDir() && fileName.endsWith(frameworkSuffix)) { + relocateFramework(fileName); + alreadyPatchedFrameworks.append(fileName); + } + else if (dirIterator.fileInfo().isDir()) + continue; + else if (fileName.endsWith(QLatin1String(".dylib"))) + relocateBinary(fileName); + else if (dirIterator.fileInfo().isExecutable() && !fileName.endsWith(QLatin1String(".h")) + && !fileName.endsWith(QLatin1String(".cpp")) && !fileName.endsWith(QLatin1String(".pro")) + && !fileName.endsWith(QLatin1String(".pri"))) { + //the endsWith check are here because there were wrongly commited files in the repositories + relocateBinary(fileName); + } + } + + return error() == NoError; +} + +void MacReplaceInstallNamesOperation::extractExecutableInfo(const QString &fileName, QString &frameworkId, + QStringList &frameworks, QString &originalBuildDir) +{ + qDebug() << "Relocator calling otool -l for" << fileName; + QProcessWrapper otool; + otool.start(QLatin1String("otool"), QStringList() << QLatin1String("-l") << fileName); + if (!otool.waitForStarted()) { + setError(UserDefinedError, tr("Can't invoke otool. Is Xcode installed?")); + return; + } + otool.waitForFinished(); + enum State { + State_Start, + State_LC_ID_DYLIB, + State_LC_LOAD_DYLIB + }; + State state = State_Start; + QByteArray outputData = otool.readAllStandardOutput(); + QBuffer output(&outputData); + output.open(QBuffer::ReadOnly); + while (!output.atEnd()) { + QString line = QString::fromLocal8Bit(output.readLine()); + line = line.trimmed(); + if (line.startsWith(QLatin1String("cmd "))) { + line.remove(0, 4); + if (line == QLatin1String("LC_LOAD_DYLIB")) + state = State_LC_LOAD_DYLIB; + else if (line == QLatin1String("LC_ID_DYLIB")) + state = State_LC_ID_DYLIB; + else + state = State_Start; + } else if (state == State_LC_LOAD_DYLIB && line.startsWith(QLatin1String("name "))) { + line.remove(0, 5); + int idx = line.indexOf(QLatin1String("(offset")); + if (idx > 0) + line.truncate(idx); + line = line.trimmed(); + frameworks.append(line); + } else if (state == State_LC_ID_DYLIB && line.startsWith(QLatin1String("name "))) { + line.remove(0, 5); + int idx = line.indexOf(QLatin1String("(offset")); + if (idx > 0) + line.truncate(idx); + line = line.trimmed(); + frameworkId = line; + + originalBuildDir = frameworkId; + idx = originalBuildDir.indexOf(m_indicator); + if (idx < 0) { + originalBuildDir.clear(); + } else { + originalBuildDir.truncate(idx); + } + if (originalBuildDir.endsWith(QLatin1Char('/'))) + originalBuildDir.chop(1); + qDebug() << "originalBuildDir is:" << originalBuildDir; + } + } + qDebug() << "END - Relocator calling otool -l for" << fileName; +} + +void MacReplaceInstallNamesOperation::relocateBinary(const QString &fileName) +{ + QString frameworkId; + QStringList frameworks; + QString originalBuildDir; + extractExecutableInfo(fileName, frameworkId, frameworks, originalBuildDir); + + qDebug() << QString::fromLatin1("got following informations(fileName: %1, frameworkId: %2, frameworks: %3," + "orginalBuildDir: %4)").arg(fileName, frameworkId, frameworks.join(QLatin1String("|")), originalBuildDir); + + QStringList args; + if (frameworkId.contains(m_indicator) || QFileInfo(frameworkId).fileName() == frameworkId) { + args << QLatin1String("-id") << fileName << fileName; + if (!execCommand(QLatin1String("install_name_tool"), args)) + return; + } + + + foreach (const QString &fw, frameworks) { + if (originalBuildDir.isEmpty() && fw.contains(m_indicator)) { + originalBuildDir = fw.left(fw.indexOf(m_indicator)); + } + if (originalBuildDir.isEmpty() || !fw.contains(originalBuildDir)) + continue; + QString newPath = fw; + newPath.replace(originalBuildDir, m_installationDir); + + args.clear(); + args << QLatin1String("-change") << fw << newPath << fileName; + if (!execCommand(QLatin1String("install_name_tool"), args)) + return; + } +} + +void MacReplaceInstallNamesOperation::relocateFramework(const QString &directoryName) +{ + QFileInfo fi(directoryName); + QString frameworkName = fi.baseName(); + + QString absoluteVersionDirectory = directoryName + QLatin1String("/Versions/Current"); + if (QFileInfo(absoluteVersionDirectory).isSymLink()) { + absoluteVersionDirectory = QFileInfo(absoluteVersionDirectory).symLinkTarget(); + } + + fi.setFile(absoluteVersionDirectory + QDir::separator() + frameworkName); + if (fi.exists()) { + QString fileName = fi.isSymLink() ? fi.symLinkTarget() : fi.absoluteFilePath(); + relocateBinary(fileName); + } + + fi.setFile(absoluteVersionDirectory + QDir::separator() + frameworkName + QLatin1String("_debug")); + if (fi.exists()) { + QString fileName = fi.isSymLink() ? fi.symLinkTarget() : fi.absoluteFilePath(); + relocateBinary(fileName); + } +} + +bool MacReplaceInstallNamesOperation::execCommand(const QString &cmd, const QStringList &args) +{ + qDebug() << Q_FUNC_INFO << cmd << " " << args; + + QProcessWrapper process; + process.start(cmd, args); + if (!process.waitForStarted()) { + setError(UserDefinedError, tr("Can't start process %0.").arg(cmd)); + return false; + } + process.waitForFinished(); + if (process.exitCode() != 0) { + QString errorMessage = QLatin1String("Command %1 failed.\nArguments: %2\nOutput: %3\n"); + setError(UserDefinedError, errorMessage.arg(cmd, args.join(QLatin1String(" ")), + QString::fromLocal8Bit(process.readAll()))); + return false; + } + return true; +} diff --git a/src/libs/installer/macreplaceinstallnamesoperation.h b/src/libs/installer/macreplaceinstallnamesoperation.h new file mode 100644 index 000000000..29f226de6 --- /dev/null +++ b/src/libs/installer/macreplaceinstallnamesoperation.h @@ -0,0 +1,67 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef MACREPLACEINSTALLNAMEOPERATION_H +#define MACREPLACEINSTALLNAMEOPERATION_H + +#include "qinstallerglobal.h" + +namespace QInstaller { + +class INSTALLER_EXPORT MacReplaceInstallNamesOperation : public Operation +{ +public: + MacReplaceInstallNamesOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation *clone() const; + + bool apply(const QString &oldString, const QString &newString, const QString &frameworkDir); + +private: + void extractExecutableInfo(const QString &fileName, QString &frameworkId, QStringList &frameworks, + QString &originalBuildDir); + void relocateFramework(const QString &directoryName); + void relocateBinary(const QString &fileName); + bool execCommand(const QString &cmd, const QStringList &args); + +private: + QString m_indicator; + QString m_installationDir; +}; + +} // namespace QInstaller + +#endif // MACREPLACEINSTALLNAMEOPERATION_H diff --git a/src/libs/installer/messageboxhandler.cpp b/src/libs/installer/messageboxhandler.cpp new file mode 100644 index 000000000..a50590382 --- /dev/null +++ b/src/libs/installer/messageboxhandler.cpp @@ -0,0 +1,309 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "messageboxhandler.h" + +#include <QtCore/QDebug> + +#include <QtGui/QApplication> +#include <QtGui/QPushButton> +#include <QtGui/QDialogButtonBox> + +#include <QtScript/QScriptEngine> +#include <QtScript/QScriptValue> + +QScriptValue QInstaller::registerMessageBox(QScriptEngine *scriptEngine) +{ + // register QMessageBox::StandardButton enum in the script connection + QScriptValue messageBox = scriptEngine->newQObject(MessageBoxHandler::instance()); + messageBox.setProperty(QLatin1String("Ok"), + scriptEngine->newVariant(static_cast<int>(QMessageBox::Yes))); + messageBox.setProperty(QLatin1String("Open"), + scriptEngine->newVariant(static_cast<int>(QMessageBox::Open))); + messageBox.setProperty(QLatin1String("Save"), + scriptEngine->newVariant(static_cast<int>(QMessageBox::Save))); + messageBox.setProperty(QLatin1String("Cancel"), + scriptEngine->newVariant(static_cast<int>(QMessageBox::Cancel))); + messageBox.setProperty(QLatin1String("Close"), + scriptEngine->newVariant(static_cast<int>(QMessageBox::Close))); + messageBox.setProperty(QLatin1String("Discard"), + scriptEngine->newVariant(static_cast<int>(QMessageBox::Discard))); + messageBox.setProperty(QLatin1String("Apply"), + scriptEngine->newVariant(static_cast<int>(QMessageBox::Apply))); + messageBox.setProperty(QLatin1String("Reset"), + scriptEngine->newVariant(static_cast<int>(QMessageBox::Reset))); + messageBox.setProperty(QLatin1String("RestoreDefaults"), + scriptEngine->newVariant(static_cast<int>(QMessageBox::RestoreDefaults))); + messageBox.setProperty(QLatin1String("Help"), + scriptEngine->newVariant(static_cast<int>(QMessageBox::Help))); + messageBox.setProperty(QLatin1String("SaveAll"), + scriptEngine->newVariant(static_cast<int>(QMessageBox::SaveAll))); + messageBox.setProperty(QLatin1String("Yes"), + scriptEngine->newVariant(static_cast<int>(QMessageBox::Yes))); + messageBox.setProperty(QLatin1String("YesToAll"), + scriptEngine->newVariant(static_cast<int>(QMessageBox::YesToAll))); + messageBox.setProperty(QLatin1String("No"), + scriptEngine->newVariant(static_cast<int>(QMessageBox::No))); + messageBox.setProperty(QLatin1String("NoToAll"), + scriptEngine->newVariant(static_cast<int>(QMessageBox::NoToAll))); + messageBox.setProperty(QLatin1String("Abort"), + scriptEngine->newVariant(static_cast<int>(QMessageBox::Abort))); + messageBox.setProperty(QLatin1String("Retry"), + scriptEngine->newVariant(static_cast<int>(QMessageBox::Retry))); + messageBox.setProperty(QLatin1String("Ignore"), + scriptEngine->newVariant(static_cast<int>(QMessageBox::Ignore))); + messageBox.setProperty(QLatin1String("NoButton"), + scriptEngine->newVariant(static_cast<int>(QMessageBox::NoButton))); + scriptEngine->globalObject().setProperty(QLatin1String("QMessageBox"), messageBox); + + return messageBox; +} + +using namespace QInstaller; + +template <typename T> +static QList<T> reversed(const QList<T> &list) +{ + qFatal("This seems to be broken, check this!!!!"); + // TODO: Figure out what should happen here. See setDefaultAction(...). +#if 1 + // Note: This does not what the function name implies??? + QList<T> res = list; + qCopyBackward(list.begin(), list.end(), res.end()); + return res; +#else + // Note: This does what the function name implies, but we need to check if this is what we want. + QList<T> res = list; + std::reverse(res.begin(), res.end()); + return res; +#endif +} + + +// -- MessageBoxHandler + +MessageBoxHandler *MessageBoxHandler::m_instance = 0; + +MessageBoxHandler::MessageBoxHandler(QObject *parent) + : QObject(parent) + , m_defaultAction(MessageBoxHandler::AskUser) +{ +} + +MessageBoxHandler *MessageBoxHandler::instance() +{ + if (m_instance == 0) + m_instance = new MessageBoxHandler(qApp); + return m_instance; +} + +QWidget *MessageBoxHandler::currentBestSuitParent() +{ + if (QApplication::type() == QApplication::Tty) { + return 0; + } + + if (qApp->activeModalWidget()) + return qApp->activeModalWidget(); + + return qApp->activeWindow(); +} + +void MessageBoxHandler::setDefaultAction(DefaultAction defaultAction) +{ + if (m_defaultAction == defaultAction) + return; + m_defaultAction = defaultAction; + + m_buttonOrder.clear(); + if (m_defaultAction != AskUser) { + m_buttonOrder << QMessageBox::YesToAll << QMessageBox::Yes << QMessageBox::Ok << QMessageBox::Apply + << QMessageBox::SaveAll << QMessageBox::Save <<QMessageBox::Retry << QMessageBox::Ignore + << QMessageBox::Help << QMessageBox::RestoreDefaults << QMessageBox::Reset << QMessageBox::Open + << QMessageBox::Cancel << QMessageBox::Close << QMessageBox::Abort << QMessageBox::Discard + << QMessageBox::No << QMessageBox::NoToAll; + } + + if (m_defaultAction == Reject) { + // If we want to reject everything, we need the lowest button. For example, if Cancel is existing it + // could use Cancel, but if Close is existing it will use Close. + m_buttonOrder = reversed(m_buttonOrder); + } +} + +void MessageBoxHandler::setAutomaticAnswer(const QString &identifier, QMessageBox::StandardButton answer) +{ + m_automaticAnswers.insert(identifier, answer); +} + +// -- static + +QMessageBox::StandardButton MessageBoxHandler::critical(QWidget *parent, const QString &identifier, + const QString &title, const QString &text, QMessageBox::StandardButtons buttons, + QMessageBox::StandardButton button) +{ + return instance()->showMessageBox(criticalType, parent, identifier, title, text, buttons, button); +} + +QMessageBox::StandardButton MessageBoxHandler::information(QWidget *parent, const QString &identifier, + const QString &title, const QString &text, QMessageBox::StandardButtons buttons, + QMessageBox::StandardButton button) +{ + return instance()->showMessageBox(informationType, parent, identifier, title, text, buttons, button); +} + +QMessageBox::StandardButton MessageBoxHandler::question(QWidget *parent, const QString &identifier, + const QString &title, const QString &text, QMessageBox::StandardButtons buttons, + QMessageBox::StandardButton button) +{ + return instance()->showMessageBox(questionType, parent, identifier, title, text, buttons, button); +} + +QMessageBox::StandardButton MessageBoxHandler::warning(QWidget *parent, const QString &identifier, + const QString &title, const QString &text, QMessageBox::StandardButtons buttons, + QMessageBox::StandardButton button) +{ + return instance()->showMessageBox(warningType, parent, identifier, title, text, buttons, button); +} + +// -- invokable + +int MessageBoxHandler::critical(const QString &identifier, const QString &title, const QString &text, + QMessageBox::StandardButtons buttons, QMessageBox::StandardButton button) const +{ + return showMessageBox(criticalType, currentBestSuitParent(), identifier, title, text, buttons, button); +} + +int MessageBoxHandler::information(const QString &identifier, const QString &title, const QString &text, + QMessageBox::StandardButtons buttons, QMessageBox::StandardButton button) const +{ + return showMessageBox(informationType, currentBestSuitParent(), identifier, title, text, buttons, button); +} + +int MessageBoxHandler::question(const QString &identifier, const QString &title, const QString &text, + QMessageBox::StandardButtons buttons, QMessageBox::StandardButton button) const +{ + return showMessageBox(questionType, currentBestSuitParent(), identifier, title, text, buttons, button); +} + +int MessageBoxHandler::warning(const QString &identifier, const QString &title, const QString &text, + QMessageBox::StandardButtons buttons, QMessageBox::StandardButton button) const +{ + return showMessageBox(warningType, currentBestSuitParent(), identifier, title, text, buttons, button); +} + +// -- private + +QMessageBox::StandardButton MessageBoxHandler::autoReply(QMessageBox::StandardButtons buttons) const +{ + if (buttons == QMessageBox::NoButton) + return QMessageBox::NoButton; + + foreach (const QMessageBox::StandardButton ¤tButton, m_buttonOrder) { + if ((buttons & currentButton) != 0) + return currentButton; + } + Q_ASSERT(!"the list must have all possible buttons"); + return QMessageBox::NoButton; +} + +static QMessageBox::StandardButton showNewMessageBox(QWidget *parent, QMessageBox::Icon icon, + const QString &title, const QString &text, QMessageBox::StandardButtons buttons, + QMessageBox::StandardButton defaultButton) +{ + QMessageBox msgBox(icon, title, text, QMessageBox::NoButton, parent); + QDialogButtonBox *buttonBox = msgBox.findChild<QDialogButtonBox *>(); + Q_ASSERT(buttonBox != 0); + + uint mask = QMessageBox::FirstButton; + while (mask <= QMessageBox::LastButton) { + uint sb = buttons & mask; + mask <<= 1; + if (!sb) + continue; + QPushButton *button = msgBox.addButton((QMessageBox::StandardButton)sb); + // Choose the first accept role as the default + if (msgBox.defaultButton()) + continue; + if ((defaultButton == QMessageBox::NoButton + && buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) + || (defaultButton != QMessageBox::NoButton && sb == uint(defaultButton))) { + msgBox.setDefaultButton(button); + } + } +#if defined(Q_WS_MAC) + msgBox.setWindowModality(Qt::WindowModal); +#endif + if (msgBox.exec() == -1) + return QMessageBox::Cancel; + return msgBox.standardButton(msgBox.clickedButton()); +} + +QMessageBox::StandardButton MessageBoxHandler::showMessageBox(MessageType messageType, QWidget *parent, + const QString &identifier, const QString &title, const QString &text, QMessageBox::StandardButtons buttons, + QMessageBox::StandardButton defaultButton) const +{ + static QHash<MessageType, QString> messageTypeHash; + if (messageTypeHash.isEmpty()) { + messageTypeHash.insert(criticalType, QLatin1String("critical")); + messageTypeHash.insert(informationType, QLatin1String("information")); + messageTypeHash.insert(questionType, QLatin1String("question")); + messageTypeHash.insert(warningType, QLatin1String("warning")); + }; + + qDebug() << QString::fromLatin1("created %1 message box %2: '%3', %4").arg(messageTypeHash + .value(messageType),identifier, title, text); + + if (QApplication::type() == QApplication::Tty) + return defaultButton; + + if (m_automaticAnswers.contains(identifier)) + return m_automaticAnswers.value(identifier); + + if (m_defaultAction == AskUser) { + switch (messageType) { + case criticalType: + return showNewMessageBox(parent, QMessageBox::Critical, title, text, buttons, defaultButton); + case informationType: + return showNewMessageBox(parent, QMessageBox::Information, title, text, buttons, defaultButton); + case questionType: + return showNewMessageBox(parent, QMessageBox::Question, title, text, buttons, defaultButton); + case warningType: + return showNewMessageBox(parent, QMessageBox::Warning, title, text, buttons, defaultButton); + } + } else { + return autoReply(buttons); + } + + Q_ASSERT_X(false, Q_FUNC_INFO, "Something went really wrong."); + return defaultButton; +} diff --git a/src/libs/installer/messageboxhandler.h b/src/libs/installer/messageboxhandler.h new file mode 100644 index 000000000..367459b6f --- /dev/null +++ b/src/libs/installer/messageboxhandler.h @@ -0,0 +1,131 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef QINSTALLER_MESSAGEBOXHANDLER_H +#define QINSTALLER_MESSAGEBOXHANDLER_H + +#include <installer_global.h> + +#include <QtCore/QHash> +#include <QtCore/QObject> + +#include <QtGui/QMessageBox> + +#include <QtScript/QScriptable> + +namespace QInstaller { + +QScriptValue registerMessageBox(QScriptEngine *scriptEngine); + +class INSTALLER_EXPORT MessageBoxHandler : public QObject, private QScriptable +{ + Q_OBJECT + +public: + enum DefaultAction { + AskUser, + Accept, + Reject + }; + + enum MessageType{ + criticalType, + informationType, + questionType, + warningType + }; + + static MessageBoxHandler *instance(); + static QWidget *currentBestSuitParent(); + + void setDefaultAction(DefaultAction defaultAction); + void setAutomaticAnswer(const QString &identifier, QMessageBox::StandardButton answer); + + static QMessageBox::StandardButton critical(QWidget *parent, const QString &identifier, + const QString &title, const QString &text, QMessageBox::StandardButtons buttons = QMessageBox::Ok, + QMessageBox::StandardButton button = QMessageBox::NoButton); + + static QMessageBox::StandardButton information(QWidget *parent, const QString &identifier, + const QString &title, const QString &text, QMessageBox::StandardButtons buttons = QMessageBox::Ok, + QMessageBox::StandardButton button=QMessageBox::NoButton); + + static QMessageBox::StandardButton question(QWidget *parent, const QString &identifier, + const QString &title, const QString &text, + QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No, + QMessageBox::StandardButton button = QMessageBox::NoButton); + + static QMessageBox::StandardButton warning(QWidget *parent, const QString &identifier, + const QString &title, const QString &text, QMessageBox::StandardButtons buttons = QMessageBox::Ok, + QMessageBox::StandardButton button = QMessageBox::NoButton); + + Q_INVOKABLE int critical(const QString &identifier, const QString &title, const QString &text, + QMessageBox::StandardButtons buttons = QMessageBox::Ok, + QMessageBox::StandardButton button = QMessageBox::NoButton) const; + + Q_INVOKABLE int information(const QString &identifier, const QString &title, const QString &text, + QMessageBox::StandardButtons buttons = QMessageBox::Ok, + QMessageBox::StandardButton button = QMessageBox::NoButton) const; + + Q_INVOKABLE int question(const QString &identifier, const QString &title, const QString &text, + QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No, + QMessageBox::StandardButton button = QMessageBox::NoButton) const; + + Q_INVOKABLE int warning(const QString &identifier, const QString &title, const QString &text, + QMessageBox::StandardButtons buttons = QMessageBox::Ok, + QMessageBox::StandardButton button = QMessageBox::NoButton) const; + +private Q_SLOTS: + //this removes the slot from the script area + virtual void deleteLater() { + QObject::deleteLater(); + } + +private: + explicit MessageBoxHandler(QObject *parent); + + QMessageBox::StandardButton autoReply(QMessageBox::StandardButtons buttons) const; + QMessageBox::StandardButton showMessageBox(MessageType messageType, QWidget *parent, + const QString &identifier, const QString &title, const QString &text, + QMessageBox::StandardButtons buttons = QMessageBox::Ok, + QMessageBox::StandardButton defaultButton = QMessageBox::NoButton) const; + +private: + static MessageBoxHandler *m_instance; + + DefaultAction m_defaultAction; + QList<QMessageBox::Button> m_buttonOrder; + QHash<QString, QMessageBox::StandardButton> m_automaticAnswers; +}; + +} + +#endif // QINSTALLER_MESSAGEBOXHANDLER_H diff --git a/src/libs/installer/minimumprogressoperation.cpp b/src/libs/installer/minimumprogressoperation.cpp new file mode 100644 index 000000000..bc4b02fc3 --- /dev/null +++ b/src/libs/installer/minimumprogressoperation.cpp @@ -0,0 +1,68 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "minimumprogressoperation.h" + +using namespace QInstaller; + +MinimumProgressOperation::MinimumProgressOperation() +{ + //this shouldn't be call able by script, but we need a name for the binary format + setName(QLatin1String("MinimumProgress")); +} + +void MinimumProgressOperation::backup() +{ +} + +bool MinimumProgressOperation::performOperation() +{ + progressChanged(1); + return true; +} + +bool MinimumProgressOperation::undoOperation() +{ + progressChanged(1); + return true; +} + +bool MinimumProgressOperation::testOperation() +{ + return true; +} + +Operation *MinimumProgressOperation::clone() const +{ + return new MinimumProgressOperation(); +} + diff --git a/src/libs/installer/minimumprogressoperation.h b/src/libs/installer/minimumprogressoperation.h new file mode 100644 index 000000000..c945de951 --- /dev/null +++ b/src/libs/installer/minimumprogressoperation.h @@ -0,0 +1,61 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef MINIMUMPROGRESSOPERATION_H +#define MINIMUMPROGRESSOPERATION_H + +#include "qinstallerglobal.h" + +#include <QtCore/QObject> + +namespace QInstaller { + +class MinimumProgressOperation : public QObject, public Operation +{ + Q_OBJECT + +public: + MinimumProgressOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation *clone() const; + +signals: + void progressChanged(double progress); +}; + +} // namespace QInstaller + +#endif // MINIMUMPROGRESSOPERATION_H diff --git a/src/libs/installer/operationrunner.cpp b/src/libs/installer/operationrunner.cpp new file mode 100644 index 000000000..26e7e6b02 --- /dev/null +++ b/src/libs/installer/operationrunner.cpp @@ -0,0 +1,165 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ +#include "operationrunner.h" + +#include "binaryformat.h" +#include "component.h" +#include "errors.h" +#include "init.h" +#include "packagemanagercore.h" +#include "utils.h" + +#include "kdupdaterupdateoperation.h" +#include "kdupdaterupdateoperationfactory.h" + +#include <iostream> + +namespace { +class OutputHandler : public QObject +{ + Q_OBJECT + +public slots: + void drawItToCommandLine(const QString &outPut) + { + std::cout << qPrintable(outPut) << std::endl; + } +}; +} + +using namespace QInstaller; +using namespace QInstallerCreator; + +OperationRunner::OperationRunner() + : m_core(0) +{ + QInstaller::init(); +} + +OperationRunner::~OperationRunner() +{ + delete m_core; +} + +bool OperationRunner::init() +{ + try { + BinaryContent content = BinaryContent::readAndRegisterFromApplicationFile(); + m_core = new PackageManagerCore(content.magicMarker(), content.performedOperations()); + } catch (const Error &e) { + std::cerr << qPrintable(e.message()) << std::endl; + return false; + } catch (...) { + return false; + } + + return true; +} + +void OperationRunner::setVerbose(bool verbose) +{ + QInstaller::setVerbose(verbose); +} + +int OperationRunner::runOperation(const QStringList &arguments) +{ + if (!init()) { + qDebug() << "Could not init the package manager core - without this not all operations are working " + "as expected."; + } + + bool isPerformType = arguments.contains(QLatin1String("--runoperation")); + bool isUndoType = arguments.contains(QLatin1String("--undooperation")); + + if ((!isPerformType && !isUndoType) || (isPerformType && isUndoType)) { + std::cerr << "wrong arguments are used, cannot run this operation"; + return EXIT_FAILURE; + } + + QStringList argumentList; + + if (isPerformType) + argumentList = arguments.mid(arguments.indexOf(QLatin1String("--runoperation")) + 1); + else + argumentList = arguments.mid(arguments.indexOf(QLatin1String("--undooperation")) + 1); + + + try { + const QString operationName = argumentList.takeFirst(); + KDUpdater::UpdateOperation* const operation = KDUpdater::UpdateOperationFactory::instance() + .create(operationName); + if (!operation) { + std::cerr << "Can not find the operation: " << qPrintable(operationName) << std::endl; + return EXIT_FAILURE; + } + + OutputHandler myOutPutHandler; + QObject *const operationObject = dynamic_cast<QObject *>(operation); + if (operationObject != 0) { + const QMetaObject *const mo = operationObject->metaObject(); + if (mo->indexOfSignal(QMetaObject::normalizedSignature("outputTextChanged(QString)")) > -1) { + QObject::connect(operationObject, SIGNAL(outputTextChanged(QString)), + &myOutPutHandler, SLOT(drawItToCommandLine(QString))); + } + } + + operation->setValue(QLatin1String("installer"), QVariant::fromValue(m_core)); + + operation->setArguments(argumentList); + + bool readyPerformed = false; + if (isPerformType) + readyPerformed = operation->performOperation(); + + if (isUndoType) + readyPerformed = operation->undoOperation(); + + std::cout << "========================================" << std::endl; + if (readyPerformed) { + std::cout << "Operation was successfully performed." << std::endl; + } else { + std::cerr << "There was a problem while performing the operation: " + << qPrintable(operation->errorString()) << std::endl; + return EXIT_FAILURE; + } + } catch (const QInstaller::Error &e) { + std::cerr << qPrintable(e.message()) << std::endl; + return EXIT_FAILURE; + } catch (...) { + std::cerr << "Caught unknown exception" << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +#include "operationrunner.moc" diff --git a/src/libs/installer/operationrunner.h b/src/libs/installer/operationrunner.h new file mode 100644 index 000000000..9d3056e16 --- /dev/null +++ b/src/libs/installer/operationrunner.h @@ -0,0 +1,60 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef OPERATIONRUNNER_H +#define OPERATIONRUNNER_H + +#include "installer_global.h" + +QT_FORWARD_DECLARE_CLASS(QStringList) + +namespace QInstaller { + +class PackageManagerCore; + +class INSTALLER_EXPORT OperationRunner +{ +public: + explicit OperationRunner(); + ~OperationRunner(); + + bool init(); + void setVerbose(bool verbose); + int runOperation(const QStringList &arguments); + +private: + PackageManagerCore *m_core; +}; + +} // namespace QInstaller + +#endif diff --git a/src/libs/installer/packagemanagercore.cpp b/src/libs/installer/packagemanagercore.cpp new file mode 100644 index 000000000..8b871c61e --- /dev/null +++ b/src/libs/installer/packagemanagercore.cpp @@ -0,0 +1,1845 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ +#include "packagemanagercore.h" + +#include "adminauthorization.h" +#include "binaryformat.h" +#include "component.h" +#include "downloadarchivesjob.h" +#include "errors.h" +#include "fsengineclient.h" +#include "getrepositoriesmetainfojob.h" +#include "messageboxhandler.h" +#include "packagemanagercore_p.h" +#include "packagemanagerproxyfactory.h" +#include "progresscoordinator.h" +#include "qinstallerglobal.h" +#include "qprocesswrapper.h" +#include "qsettingswrapper.h" +#include "settings.h" +#include "utils.h" + +#include <QtCore/QTemporaryFile> + +#include <QtGui/QDesktopServices> + +#include <QtScript/QScriptEngine> +#include <QtScript/QScriptContext> + +#include "kdsysinfo.h" +#include "kdupdaterupdateoperationfactory.h" + +#ifdef Q_OS_WIN +# include "qt_windows.h" +#endif + +using namespace QInstaller; + +static QFont sVirtualComponentsFont; + +static bool sNoForceInstallation = false; +static bool sVirtualComponentsVisible = false; + +static QScriptValue checkArguments(QScriptContext *context, int amin, int amax) +{ + if (context->argumentCount() < amin || context->argumentCount() > amax) { + if (amin != amax) { + return context->throwError(QObject::tr("Invalid arguments: %1 arguments given, %2 to " + "%3 expected.").arg(QString::number(context->argumentCount()), + QString::number(amin), QString::number(amax))); + } + return context->throwError(QObject::tr("Invalid arguments: %1 arguments given, %2 expected.") + .arg(QString::number(context->argumentCount()), QString::number(amin))); + } + return QScriptValue(); +} + +static bool componentMatches(const Component *component, const QString &name, + const QString &version = QString()) +{ + if (name.isEmpty() || component->name() != name) + return false; + + if (version.isEmpty()) + return true; + + // can be remote or local version + return PackageManagerCore::versionMatches(component->value(scVersion), version); +} + +Component *PackageManagerCore::subComponentByName(const QInstaller::PackageManagerCore *installer, + const QString &name, const QString &version, Component *check) +{ + if (name.isEmpty()) + return 0; + + if (check != 0 && componentMatches(check, name, version)) + return check; + + if (installer->runMode() == AllMode) { + QList<Component*> rootComponents; + if (check == 0) + rootComponents = installer->rootComponents(); + else + rootComponents = check->childComponents(false, AllMode); + + foreach (QInstaller::Component *component, rootComponents) { + Component *const result = subComponentByName(installer, name, version, component); + if (result != 0) + return result; + } + } else { + const QList<Component*> updaterComponents = installer->updaterComponents() + + installer->d->m_updaterComponentsDeps; + foreach (QInstaller::Component *component, updaterComponents) { + if (componentMatches(component, name, version)) + return component; + } + } + return 0; +} + +/*! + Scriptable version of PackageManagerCore::componentByName(QString). + \sa PackageManagerCore::componentByName + */ +QScriptValue QInstaller::qInstallerComponentByName(QScriptContext *context, QScriptEngine *engine) +{ + const QScriptValue check = checkArguments(context, 1, 1); + if (check.isError()) + return check; + + // well... this is our "this" pointer + PackageManagerCore *const core = dynamic_cast<PackageManagerCore*>(engine->globalObject() + .property(QLatin1String("installer")).toQObject()); + + const QString name = context->argument(0).toString(); + return engine->newQObject(core->componentByName(name)); +} + +QScriptValue QInstaller::qDesktopServicesOpenUrl(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + const QScriptValue check = checkArguments(context, 1, 1); + if (check.isError()) + return check; + QString url = context->argument(0).toString(); + url.replace(QLatin1String("\\\\"), QLatin1String("/")); + url.replace(QLatin1String("\\"), QLatin1String("/")); + return QDesktopServices::openUrl(QUrl::fromUserInput(url)); +} + +QScriptValue QInstaller::qDesktopServicesDisplayName(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + const QScriptValue check = checkArguments(context, 1, 1); + if (check.isError()) + return check; + const QDesktopServices::StandardLocation location = + static_cast< QDesktopServices::StandardLocation >(context->argument(0).toInt32()); + return QDesktopServices::displayName(location); +} + +QScriptValue QInstaller::qDesktopServicesStorageLocation(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + const QScriptValue check = checkArguments(context, 1, 1); + if (check.isError()) + return check; + const QDesktopServices::StandardLocation location = + static_cast< QDesktopServices::StandardLocation >(context->argument(0).toInt32()); + return QDesktopServices::storageLocation(location); +} + +QString QInstaller::uncaughtExceptionString(QScriptEngine *scriptEngine, const QString &context) +{ + QString error(QLatin1String("\n\n%1\n\nBacktrace:\n\t%2")); + if (!context.isEmpty()) + error.prepend(context); + + return error.arg(scriptEngine->uncaughtException().toString(), scriptEngine->uncaughtExceptionBacktrace() + .join(QLatin1String("\n\t"))); +} + + +/*! + \class QInstaller::PackageManagerCore + PackageManagerCore forms the core of the installation, update, maintenance and un-installation system. + */ + +/*! + \enum QInstaller::PackageManagerCore::WizardPage + WizardPage is used to number the different pages known to the Installer GUI. + */ + +/*! + \var QInstaller::PackageManagerCore::Introduction + I ntroduction page. + */ + +/*! + \var QInstaller::PackageManagerCore::LicenseCheck + License check page + */ +/*! + \var QInstaller::PackageManagerCore::TargetDirectory + Target directory selection page + */ +/*! + \var QInstaller::PackageManagerCore::ComponentSelection + %Component selection page + */ +/*! + \var QInstaller::PackageManagerCore::StartMenuSelection + Start menu directory selection page - Microsoft Windows only + */ +/*! + \var QInstaller::PackageManagerCore::ReadyForInstallation + "Ready for Installation" page + */ +/*! + \var QInstaller::PackageManagerCore::PerformInstallation + Page shown while performing the installation + */ +/*! + \var QInstaller::PackageManagerCore::InstallationFinished + Page shown when the installation was finished + */ +/*! + \var QInstaller::PackageManagerCore::End + Non-existing page - this value has to be used if you want to insert a page after \a InstallationFinished + */ + +void PackageManagerCore::writeUninstaller() +{ + if (d->m_needToWriteUninstaller) { + try { + d->writeUninstaller(d->m_performedOperationsOld + d->m_performedOperationsCurrentSession); + + bool gainedAdminRights = false; + QTemporaryFile tempAdminFile(d->targetDir() + + QLatin1String("/testjsfdjlkdsjflkdsjfldsjlfds") + QString::number(qrand() % 1000)); + if (!tempAdminFile.open() || !tempAdminFile.isWritable()) { + gainAdminRights(); + gainedAdminRights = true; + } + d->m_updaterApplication.packagesInfo()->writeToDisk(); + if (gainedAdminRights) + dropAdminRights(); + d->m_needToWriteUninstaller = false; + } catch (const Error &error) { + MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), + QLatin1String("WriteError"), tr("Error writing Uninstaller"), error.message(), + QMessageBox::Ok, QMessageBox::Ok); + } + } +} + +void PackageManagerCore::reset(const QHash<QString, QString> ¶ms) +{ + d->m_completeUninstall = false; + d->m_forceRestart = false; + d->m_status = PackageManagerCore::Unfinished; + d->m_installerBaseBinaryUnreplaced.clear(); + d->m_vars.clear(); + d->m_vars = params; + d->initialize(); +} + +/*! + Sets the uninstallation to be \a complete. If \a complete is false, only components deselected + by the user will be uninstalled. This option applies only on uninstallation. + */ +void PackageManagerCore::setCompleteUninstallation(bool complete) +{ + d->m_completeUninstall = complete; +} + +void PackageManagerCore::cancelMetaInfoJob() +{ + if (d->m_repoMetaInfoJob) + d->m_repoMetaInfoJob->cancel(); +} + +void PackageManagerCore::componentsToInstallNeedsRecalculation() +{ + d->m_componentsToInstallCalculated = false; +} + +void PackageManagerCore::autoAcceptMessageBoxes() +{ + MessageBoxHandler::instance()->setDefaultAction(MessageBoxHandler::Accept); +} + +void PackageManagerCore::autoRejectMessageBoxes() +{ + MessageBoxHandler::instance()->setDefaultAction(MessageBoxHandler::Reject); +} + +void PackageManagerCore::setMessageBoxAutomaticAnswer(const QString &identifier, int button) +{ + MessageBoxHandler::instance()->setAutomaticAnswer(identifier, + static_cast<QMessageBox::Button>(button)); +} + +quint64 size(QInstaller::Component *component, const QString &value) +{ + if (!component->isSelected() || component->isInstalled()) + return quint64(0); + return component->value(value).toLongLong(); +} + +quint64 PackageManagerCore::requiredDiskSpace() const +{ + quint64 result = 0; + + foreach (QInstaller::Component *component, rootComponents()) + result += component->updateUncompressedSize(); + + return result; +} + +quint64 PackageManagerCore::requiredTemporaryDiskSpace() const +{ + quint64 result = 0; + + foreach (QInstaller::Component *component, orderedComponentsToInstall()) + result += size(component, scCompressedSize); + + return result; +} + +/*! + Returns the count of archives that will be downloaded. +*/ +int PackageManagerCore::downloadNeededArchives(double partProgressSize) +{ + Q_ASSERT(partProgressSize >= 0 && partProgressSize <= 1); + + QList<QPair<QString, QString> > archivesToDownload; + QList<Component*> neededComponents = orderedComponentsToInstall(); + foreach (Component *component, neededComponents) { + // collect all archives to be downloaded + const QStringList toDownload = component->downloadableArchives(); + foreach (const QString &versionFreeString, toDownload) { + archivesToDownload.push_back(qMakePair(QString::fromLatin1("installer://%1/%2") + .arg(component->name(), versionFreeString), QString::fromLatin1("%1/%2/%3") + .arg(component->repositoryUrl().toString(), component->name(), versionFreeString))); + } + } + + if (archivesToDownload.isEmpty()) + return 0; + + ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("\nDownloading packages...")); + + // don't have it on the stack, since it keeps the temporary files + DownloadArchivesJob *const archivesJob = new DownloadArchivesJob(this); + archivesJob->setAutoDelete(false); + archivesJob->setArchivesToDownload(archivesToDownload); + connect(this, SIGNAL(installationInterrupted()), archivesJob, SLOT(cancel())); + connect(archivesJob, SIGNAL(outputTextChanged(QString)), ProgressCoordinator::instance(), + SLOT(emitLabelAndDetailTextChanged(QString))); + connect(archivesJob, SIGNAL(downloadStatusChanged(QString)), ProgressCoordinator::instance(), + SIGNAL(downloadStatusChanged(QString))); + + ProgressCoordinator::instance()->registerPartProgress(archivesJob, SIGNAL(progressChanged(double)), + partProgressSize); + + archivesJob->start(); + archivesJob->waitForFinished(); + + if (archivesJob->error() == KDJob::Canceled) + interrupt(); + else if (archivesJob->error() != KDJob::NoError) + throw Error(archivesJob->errorString()); + + if (d->statusCanceledOrFailed()) + throw Error(tr("Installation canceled by user")); + ProgressCoordinator::instance()->emitDownloadStatus(tr("All downloads finished.")); + + return archivesToDownload.count(); +} + +void PackageManagerCore::installComponent(Component *component, double progressOperationSize) +{ + Q_ASSERT(progressOperationSize); + + d->setStatus(PackageManagerCore::Running); + try { + d->installComponent(component, progressOperationSize); + d->setStatus(PackageManagerCore::Success); + } catch (const Error &error) { + if (status() != PackageManagerCore::Canceled) { + d->setStatus(PackageManagerCore::Failure); + MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), + QLatin1String("installationError"), tr("Error"), error.message()); + } + } +} + +/*! + If a component marked as important was installed during update + process true is returned. +*/ +bool PackageManagerCore::needsRestart() const +{ + return d->m_forceRestart; +} + +void PackageManagerCore::rollBackInstallation() +{ + emit titleMessageChanged(tr("Cancelling the Installer")); + + //this unregisters all operation progressChanged connects + ProgressCoordinator::instance()->setUndoMode(); + const int progressOperationCount = d->countProgressOperations(d->m_performedOperationsCurrentSession); + const double progressOperationSize = double(1) / progressOperationCount; + + //re register all the undo operations with the new size to the ProgressCoordninator + foreach (Operation *const operation, d->m_performedOperationsCurrentSession) { + QObject *const operationObject = dynamic_cast<QObject*> (operation); + if (operationObject != 0) { + const QMetaObject* const mo = operationObject->metaObject(); + if (mo->indexOfSignal(QMetaObject::normalizedSignature("progressChanged(double)")) > -1) { + ProgressCoordinator::instance()->registerPartProgress(operationObject, + SIGNAL(progressChanged(double)), progressOperationSize); + } + } + } + + KDUpdater::PackagesInfo &packages = *d->m_updaterApplication.packagesInfo(); + while (!d->m_performedOperationsCurrentSession.isEmpty()) { + try { + Operation *const operation = d->m_performedOperationsCurrentSession.takeLast(); + const bool becameAdmin = !d->m_FSEngineClientHandler->isActive() + && operation->value(QLatin1String("admin")).toBool() && gainAdminRights(); + + PackageManagerCorePrivate::performOperationThreaded(operation, PackageManagerCorePrivate::Undo); + + const QString componentName = operation->value(QLatin1String("component")).toString(); + if (!componentName.isEmpty()) { + Component *component = componentByName(componentName); + if (!component) + component = d->componentsToReplace(runMode()).value(componentName).second; + if (component) { + component->setUninstalled(); + packages.removePackage(component->name()); + } + } + + if (becameAdmin) + dropAdminRights(); + } catch (const Error &e) { + MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), + QLatin1String("ElevationError"), tr("Authentication Error"), tr("Some components " + "could not be removed completely because admin rights could not be acquired: %1.") + .arg(e.message())); + } catch (...) { + MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), QLatin1String("unknown"), + tr("Unknown error."), tr("Some components could not be removed completely because an unknown " + "error happened.")); + } + } + packages.writeToDisk(); +} + +bool PackageManagerCore::isFileExtensionRegistered(const QString &extension) const +{ + QSettingsWrapper settings(QLatin1String("HKEY_CLASSES_ROOT"), QSettingsWrapper::NativeFormat); + return settings.value(QString::fromLatin1(".%1/Default").arg(extension)).isValid(); +} + + +// -- QInstaller + +/*! + Used by operation runner to get a fake installer, can be removed if installerbase can do what operation + runner does. +*/ +PackageManagerCore::PackageManagerCore() + : d(new PackageManagerCorePrivate(this)) +{ +} + +PackageManagerCore::PackageManagerCore(qint64 magicmaker, const OperationList &performedOperations) + : d(new PackageManagerCorePrivate(this, magicmaker, performedOperations)) +{ + qRegisterMetaType<QInstaller::PackageManagerCore::Status>("QInstaller::PackageManagerCore::Status"); + qRegisterMetaType<QInstaller::PackageManagerCore::WizardPage>("QInstaller::PackageManagerCore::WizardPage"); + + d->initialize(); +} + +PackageManagerCore::~PackageManagerCore() +{ + if (!isUninstaller() && !(isInstaller() && status() == PackageManagerCore::Canceled)) { + QDir targetDir(value(scTargetDir)); + QString logFileName = targetDir.absoluteFilePath(value(QLatin1String("LogFileName"), + QLatin1String("InstallationLog.txt"))); + QInstaller::VerboseWriter::instance()->setOutputStream(logFileName); + } + delete d; +} + +/* static */ +QFont PackageManagerCore::virtualComponentsFont() +{ + return sVirtualComponentsFont; +} + +/* static */ +void PackageManagerCore::setVirtualComponentsFont(const QFont &font) +{ + sVirtualComponentsFont = font; +} + +/* static */ +bool PackageManagerCore::virtualComponentsVisible() +{ + return sVirtualComponentsVisible; +} + +/* static */ +void PackageManagerCore::setVirtualComponentsVisible(bool visible) +{ + sVirtualComponentsVisible = visible; +} + +/* static */ +bool PackageManagerCore::noForceInstallation() +{ + return sNoForceInstallation; +} + +/* static */ +void PackageManagerCore::setNoForceInstallation(bool value) +{ + sNoForceInstallation = value; +} + +RunMode PackageManagerCore::runMode() const +{ + return isUpdater() ? UpdaterMode : AllMode; +} + +bool PackageManagerCore::fetchLocalPackagesTree() +{ + d->setStatus(Running); + + if (!isPackageManager()) { + d->setStatus(Failure, tr("Application not running in Package Manager mode!")); + return false; + } + + LocalPackagesHash installedPackages = d->localInstalledPackages(); + if (installedPackages.isEmpty()) { + if (status() != Failure) + d->setStatus(Failure, tr("No installed packages found.")); + return false; + } + + emit startAllComponentsReset(); + + d->clearAllComponentLists(); + QHash<QString, QInstaller::Component*> components; + + const QStringList &keys = installedPackages.keys(); + foreach (const QString &key, keys) { + QScopedPointer<QInstaller::Component> component(new QInstaller::Component(this)); + component->loadDataFromPackage(installedPackages.value(key)); + const QString &name = component->name(); + if (components.contains(name)) { + qCritical("Could not register component! Component with identifier %s already registered.", + qPrintable(name)); + continue; + } + components.insert(name, component.take()); + } + + if (!d->buildComponentTree(components, false)) + return false; + + updateDisplayVersions(scDisplayVersion); + + emit finishAllComponentsReset(); + d->setStatus(Success); + + return true; +} + +LocalPackagesHash PackageManagerCore::localInstalledPackages() +{ + return d->localInstalledPackages(); +} + +void PackageManagerCore::networkSettingsChanged() +{ + cancelMetaInfoJob(); + + d->m_updates = false; + d->m_repoFetched = false; + d->m_updateSourcesAdded = false; + + if (d->isUpdater() || d->isPackageManager()) + d->writeMaintenanceConfigFiles(); + KDUpdater::FileDownloaderFactory::instance().setProxyFactory(proxyFactory()); + + emit coreNetworkSettingsChanged(); +} + +KDUpdater::FileDownloaderProxyFactory *PackageManagerCore::proxyFactory() const +{ + if (d->m_proxyFactory) + return d->m_proxyFactory->clone(); + return new PackageManagerProxyFactory(this); +} + +void PackageManagerCore::setProxyFactory(KDUpdater::FileDownloaderProxyFactory *factory) +{ + delete d->m_proxyFactory; + d->m_proxyFactory = factory; + KDUpdater::FileDownloaderFactory::instance().setProxyFactory(proxyFactory()); +} + +PackagesList PackageManagerCore::remotePackages() +{ + return d->remotePackages(); +} + +bool PackageManagerCore::fetchRemotePackagesTree() +{ + d->setStatus(Running); + + if (isUninstaller()) { + d->setStatus(Failure, tr("Application running in Uninstaller mode!")); + return false; + } + + const LocalPackagesHash installedPackages = d->localInstalledPackages(); + if (!isInstaller() && status() == Failure) + return false; + + if (!d->fetchMetaInformationFromRepositories()) + return false; + + if (!d->addUpdateResourcesFromRepositories(true)) + return false; + + const PackagesList &packages = d->remotePackages(); + if (packages.isEmpty()) + return false; + + bool success = false; + if (runMode() == AllMode) + success = fetchAllPackages(packages, installedPackages); + else { + success = fetchUpdaterPackages(packages, installedPackages); + } + + updateDisplayVersions(scRemoteDisplayVersion); + + if (success && !d->statusCanceledOrFailed()) + d->setStatus(Success); + return success; +} + +/*! + Adds the widget with objectName() \a name registered by \a component as a new page + into the installer's GUI wizard. The widget is added before \a page. + \a page has to be a value of \ref QInstaller::PackageManagerCore::WizardPage "WizardPage". +*/ +bool PackageManagerCore::addWizardPage(Component *component, const QString &name, int page) +{ + if (QWidget* const widget = component->userInterface(name)) { + emit wizardPageInsertionRequested(widget, static_cast<WizardPage>(page)); + return true; + } + return false; +} + +/*! + Removes the widget with objectName() \a name previously added to the installer's wizard + by \a component. +*/ +bool PackageManagerCore::removeWizardPage(Component *component, const QString &name) +{ + if (QWidget* const widget = component->userInterface(name)) { + emit wizardPageRemovalRequested(widget); + return true; + } + return false; +} + +/*! + Sets the visibility of the default page with id \a page to \a visible, i.e. + removes or adds it from/to the wizard. This works only for pages which have been + in the installer when it was started. + */ +bool PackageManagerCore::setDefaultPageVisible(int page, bool visible) +{ + emit wizardPageVisibilityChangeRequested(visible, page); + return true; +} + +/*! + Adds the widget with objectName() \a name registered by \a component as an GUI element + into the installer's GUI wizard. The widget is added on \a page. + \a page has to be a value of \ref QInstaller::PackageManagerCore::WizardPage "WizardPage". +*/ +bool PackageManagerCore::addWizardPageItem(Component *component, const QString &name, int page) +{ + if (QWidget* const widget = component->userInterface(name)) { + emit wizardWidgetInsertionRequested(widget, static_cast<WizardPage>(page)); + return true; + } + return false; +} + +/*! + Removes the widget with objectName() \a name previously added to the installer's wizard + by \a component. +*/ +bool PackageManagerCore::removeWizardPageItem(Component *component, const QString &name) +{ + if (QWidget* const widget = component->userInterface(name)) { + emit wizardWidgetRemovalRequested(widget); + return true; + } + return false; +} + +void PackageManagerCore::addUserRepositories(const QSet<Repository> &repositories) +{ + d->m_settings.addUserRepositories(repositories); +} + +/*! + Sets additional repository for this instance of the installer or updater. + Will be removed after invoking it again. +*/ +void PackageManagerCore::setTemporaryRepositories(const QSet<Repository> &repositories, bool replace) +{ + d->m_settings.setTemporaryRepositories(repositories, replace); +} + +/*! + Checks if the downloader should try to download sha1 checksums for archives. +*/ +bool PackageManagerCore::testChecksum() const +{ + return d->m_testChecksum; +} + +/*! + Defines if the downloader should try to download sha1 checksums for archives. +*/ +void PackageManagerCore::setTestChecksum(bool test) +{ + d->m_testChecksum = test; +} + +/*! + Returns the number of components in the list for installer and package manager mode. Might return 0 in + case the engine has only been run in updater mode or no components have been fetched. +*/ +int PackageManagerCore::rootComponentCount() const +{ + return d->m_rootComponents.size(); +} + +/*! + Returns the component at index position \a i in the components list. \a i must be a valid index + position in the list (i.e., 0 <= i < rootComponentCount()). +*/ +Component *PackageManagerCore::rootComponent(int i) const +{ + return d->m_rootComponents.value(i, 0); +} + +/*! + Returns a list of root components if run in installer or package manager mode. Might return an empty list + in case the engine has only been run in updater mode or no components have been fetched. +*/ +QList<Component*> PackageManagerCore::rootComponents() const +{ + return d->m_rootComponents; +} + +/*! + Appends a component as root component to the internal storage for installer or package manager components. + To append a component as a child to an already existing component, use Component::appendComponent(). Emits + the componentAdded() signal. +*/ +void PackageManagerCore::appendRootComponent(Component *component) +{ + d->m_rootComponents.append(component); + emit componentAdded(component); +} + +/*! + Returns the number of components in the list for updater mode. Might return 0 in case the engine has only + been run in installer or package manager mode or no components have been fetched. +*/ +int PackageManagerCore::updaterComponentCount() const +{ + return d->m_updaterComponents.size(); +} + +/*! + Returns the component at index position \a i in the updates component list. \a i must be a valid index + position in the list (i.e., 0 <= i < updaterComponentCount()). +*/ +Component *PackageManagerCore::updaterComponent(int i) const +{ + return d->m_updaterComponents.value(i, 0); +} + +/*! + Returns a list of components if run in updater mode. Might return an empty list in case the engine has only + been run in installer or package manager mode or no components have been fetched. +*/ +QList<Component*> PackageManagerCore::updaterComponents() const +{ + return d->m_updaterComponents; +} + +/*! + Appends a component to the internal storage for updater components. Emits the componentAdded() signal. +*/ +void PackageManagerCore::appendUpdaterComponent(Component *component) +{ + component->setUpdateAvailable(true); + d->m_updaterComponents.append(component); + emit componentAdded(component); +} + +/*! + Returns a list of all available components found during a fetch. Note that depending on the run mode the + returned list might have different values. In case of updater mode, components scheduled for an + update as well as all possible dependencies are returned. +*/ +QList<Component*> PackageManagerCore::availableComponents() const +{ + if (isUpdater()) + return d->m_updaterComponents + d->m_updaterComponentsDeps + d->m_updaterDependencyReplacements; + + QList<Component*> result = d->m_rootComponents; + foreach (QInstaller::Component *component, d->m_rootComponents) + result += component->childComponents(true, AllMode); + return result + d->m_rootDependencyReplacements; +} + +/*! + Returns a component matching \a name. \a name can also contains a version requirement. + E.g. "com.nokia.sdk.qt" returns any component with that name, "com.nokia.sdk.qt->=4.5" requires + the returned component to have at least version 4.5. + If no component matches the requirement, 0 is returned. +*/ +Component *PackageManagerCore::componentByName(const QString &name) const +{ + if (name.isEmpty()) + return 0; + + if (name.contains(QChar::fromLatin1('-'))) { + // the last part is considered to be the version, then + const QString version = name.section(QLatin1Char('-'), 1); + return subComponentByName(this, name.section(QLatin1Char('-'), 0, 0), version); + } + + return subComponentByName(this, name); +} + +/*! + Calculates an ordered list of components to install based on the current run mode. Also auto installed + dependencies are resolved. +*/ +bool PackageManagerCore::calculateComponentsToInstall() const +{ + if (!d->m_componentsToInstallCalculated) { + d->clearComponentsToInstall(); + QList<Component*> components; + if (runMode() == UpdaterMode) { + foreach (Component *component, updaterComponents()) { + if (component->updateRequested()) + components.append(component); + } + } else if (runMode() == AllMode) { + // relevant means all components which are not replaced + QList<Component*> relevantComponents = rootComponents(); + foreach (QInstaller::Component *component, rootComponents()) + relevantComponents += component->childComponents(true, AllMode); + foreach (Component *component, relevantComponents) { + // ask for all components which will be installed to get all dependencies + // even dependencies wich are changed without an increased version + if (component->installationRequested() || (component->isInstalled() && !component->uninstallationRequested())) + components.append(component); + } + } + + d->m_componentsToInstallCalculated = d->appendComponentsToInstall(components); + } + return d->m_componentsToInstallCalculated; +} + +/*! + Returns a list of ordered components to install. The list can be empty. +*/ +QList<Component*> PackageManagerCore::orderedComponentsToInstall() const +{ + return d->m_orderedComponentsToInstall; +} + +/*! + Calculates a list of components to uninstall based on the current run mode. Auto installed dependencies + are resolved as well. +*/ +bool PackageManagerCore::calculateComponentsToUninstall() const +{ + if (runMode() == UpdaterMode) + return true; + + // hack to avoid removeing needed dependencies + QSet<Component*> componentsToInstall = d->m_orderedComponentsToInstall.toSet(); + + QList<Component*> components; + foreach (Component *component, availableComponents()) { + if (component->uninstallationRequested() && !componentsToInstall.contains(component)) + components.append(component); + } + + + d->m_componentsToUninstall.clear(); + return d->appendComponentsToUninstall(components); +} + +/*! + Returns a list of components to uninstall. The list can be empty. +*/ +QList<Component *> PackageManagerCore::componentsToUninstall() const +{ + return d->m_componentsToUninstall.toList(); +} + +QString PackageManagerCore::componentsToInstallError() const +{ + return d->m_componentsToInstallError; +} + +/*! + Returns the reason why the component needs to be installed. Reasons can be: The component was scheduled + for installation, the component was added as a dependency for an other component or added as an automatic + dependency. +*/ +QString PackageManagerCore::installReason(Component *component) const +{ + return d->installReason(component); +} + +/*! + Returns a list of components that dependend on \a component. The list can be empty. Note: Auto + installed dependencies are not resolved. +*/ +QList<Component*> PackageManagerCore::dependees(const Component *_component) const +{ + QList<Component*> dependees; + const QList<Component*> components = availableComponents(); + if (!_component || components.isEmpty()) + return dependees; + + const QLatin1Char dash('-'); + foreach (Component *component, components) { + const QStringList &dependencies = component->dependencies(); + foreach (const QString &dependency, dependencies) { + // the last part is considered to be the version then + const QString name = dependency.contains(dash) ? dependency.section(dash, 0, 0) : dependency; + const QString version = dependency.contains(dash) ? dependency.section(dash, 1) : QString(); + if (componentMatches(_component, name, version)) + dependees.append(component); + } + } + return dependees; +} + +/*! + Returns a list of dependencies for \a component. If there's a dependency which cannot be fulfilled, + \a missingComponents will contain the missing components. Note: Auto installed dependencies are not + resolved. +*/ +QList<Component*> PackageManagerCore::dependencies(const Component *component, QStringList &missingComponents) const +{ + QList<Component*> result; + foreach (const QString &dependency, component->dependencies()) { + Component *component = componentByName(dependency); + if (component) + result.append(component); + else + missingComponents.append(dependency); + } + return result; +} + +Settings &PackageManagerCore::settings() const +{ + return d->m_settings; +} + +/*! + This method tries to gain admin rights. On success, it returns true. +*/ +bool PackageManagerCore::gainAdminRights() +{ + if (AdminAuthorization::hasAdminRights()) + return true; + + d->m_FSEngineClientHandler->setActive(true); + if (!d->m_FSEngineClientHandler->isActive()) + throw Error(QObject::tr("Error while elevating access rights.")); + return true; +} + +/*! + This method drops gained admin rights. +*/ +void PackageManagerCore::dropAdminRights() +{ + d->m_FSEngineClientHandler->setActive(false); +} + +/*! + Return true, if a process with \a name is running. On Windows, the comparison + is case-insensitive. +*/ +bool PackageManagerCore::isProcessRunning(const QString &name) const +{ + return PackageManagerCorePrivate::isProcessRunning(name, runningProcesses()); +} + +/*! + Executes a program. + + \param program The program that should be executed. + \param arguments Optional list of arguments. + \param stdIn Optional stdin the program reads. + \return If the command could not be executed, an empty QList, otherwise the output of the + command as first item, the return code as second item. + \note On Unix, the output is just the output to stdout, not to stderr. +*/ + +QList<QVariant> PackageManagerCore::execute(const QString &program, const QStringList &arguments, + const QString &stdIn) const +{ + QEventLoop loop; + QProcessWrapper process; + + QString adjustedProgram = replaceVariables(program); + QStringList adjustedArguments; + foreach (const QString &argument, arguments) + adjustedArguments.append(replaceVariables(argument)); + QString adjustedStdIn = replaceVariables(stdIn); + + connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)), &loop, SLOT(quit())); + process.start(adjustedProgram, adjustedArguments, + adjustedStdIn.isNull() ? QIODevice::ReadOnly : QIODevice::ReadWrite); + + if (!process.waitForStarted()) + return QList< QVariant >(); + + if (!adjustedStdIn.isNull()) { + process.write(adjustedStdIn.toLatin1()); + process.closeWriteChannel(); + } + + if (process.state() != QProcessWrapper::NotRunning) + loop.exec(); + + return QList<QVariant>() << QString::fromLatin1(process.readAllStandardOutput()) << process.exitCode(); +} + +/*! + Executes a program. + + \param program The program that should be executed. + \param arguments Optional list of arguments. + \return If the command could not be executed, an false will be returned +*/ + +bool PackageManagerCore::executeDetached(const QString &program, const QStringList &arguments) const +{ + QString adjustedProgram = replaceVariables(program); + QStringList adjustedArguments; + foreach (const QString &argument, arguments) + adjustedArguments.append(replaceVariables(argument)); + return QProcess::startDetached(adjustedProgram, adjustedArguments); +} + + +/*! + Returns an environment variable. +*/ +QString PackageManagerCore::environmentVariable(const QString &name) const +{ +#ifdef Q_WS_WIN + const LPCWSTR n = (LPCWSTR) name.utf16(); + LPTSTR buff = (LPTSTR) malloc(4096 * sizeof(TCHAR)); + DWORD getenvret = GetEnvironmentVariable(n, buff, 4096); + const QString actualValue = getenvret != 0 + ? QString::fromUtf16((const unsigned short *) buff) : QString(); + free(buff); + return actualValue; +#else + const char *pPath = name.isEmpty() ? 0 : getenv(name.toLatin1()); + return pPath ? QLatin1String(pPath) : QString(); +#endif +} + +/*! + Instantly performs an operation \a name with \a arguments. + \sa Component::addOperation +*/ +bool PackageManagerCore::performOperation(const QString &name, const QStringList &arguments) +{ + QScopedPointer<Operation> op(KDUpdater::UpdateOperationFactory::instance().create(name)); + if (!op.data()) + return false; + + op->setArguments(replaceVariables(arguments)); + op->backup(); + if (!PackageManagerCorePrivate::performOperationThreaded(op.data())) { + PackageManagerCorePrivate::performOperationThreaded(op.data(), PackageManagerCorePrivate::Undo); + return false; + } + return true; +} + +/*! + Returns true when \a version matches the \a requirement. + \a requirement can be a fixed version number or it can be prefix by the comparators '>', '>=', + '<', '<=' and '='. +*/ +bool PackageManagerCore::versionMatches(const QString &version, const QString &requirement) +{ + QRegExp compEx(QLatin1String("([<=>]+)(.*)")); + const QString comparator = compEx.exactMatch(requirement) ? compEx.cap(1) : QLatin1String("="); + const QString ver = compEx.exactMatch(requirement) ? compEx.cap(2) : requirement; + + const bool allowEqual = comparator.contains(QLatin1Char('=')); + const bool allowLess = comparator.contains(QLatin1Char('<')); + const bool allowMore = comparator.contains(QLatin1Char('>')); + + if (allowEqual && version == ver) + return true; + + if (allowLess && KDUpdater::compareVersion(ver, version) > 0) + return true; + + if (allowMore && KDUpdater::compareVersion(ver, version) < 0) + return true; + + return false; +} + +/*! + Finds a library named \a name in \a paths. + If \a paths is empty, it gets filled with platform dependent default paths. + The resulting path is stored in \a library. + This method can be used by scripts to check external dependencies. +*/ +QString PackageManagerCore::findLibrary(const QString &name, const QStringList &paths) +{ + QStringList findPaths = paths; +#if defined(Q_WS_WIN) + return findPath(QString::fromLatin1("%1.lib").arg(name), findPaths); +#else + if (findPaths.isEmpty()) { + findPaths.push_back(QLatin1String("/lib")); + findPaths.push_back(QLatin1String("/usr/lib")); + findPaths.push_back(QLatin1String("/usr/local/lib")); + findPaths.push_back(QLatin1String("/opt/local/lib")); + } +#if defined(Q_WS_MAC) + const QString dynamic = findPath(QString::fromLatin1("lib%1.dylib").arg(name), findPaths); +#else + const QString dynamic = findPath(QString::fromLatin1("lib%1.so*").arg(name), findPaths); +#endif + if (!dynamic.isEmpty()) + return dynamic; + return findPath(QString::fromLatin1("lib%1.a").arg(name), findPaths); +#endif +} + +/*! + Tries to find a file name \a name in one of \a paths. + The resulting path is stored in \a path. + This method can be used by scripts to check external dependencies. +*/ +QString PackageManagerCore::findPath(const QString &name, const QStringList &paths) +{ + foreach (const QString &path, paths) { + const QDir dir(path); + const QStringList entries = dir.entryList(QStringList() << name, QDir::Files | QDir::Hidden); + if (entries.isEmpty()) + continue; + + return dir.absoluteFilePath(entries.first()); + } + return QString(); +} + +/*! + Sets the "installerbase" binary to use when writing the package manager/uninstaller. + Set this if an update to installerbase is available. + If not set, the executable segment of the running un/installer will be used. +*/ +void PackageManagerCore::setInstallerBaseBinary(const QString &path) +{ + d->m_installerBaseBinaryUnreplaced = path; +} + +/*! + Returns the installer value for \a key. If \a key is not known to the system, \a defaultValue is + returned. Additionally, on Windows, \a key can be a registry key. +*/ +QString PackageManagerCore::value(const QString &key, const QString &defaultValue) const +{ +#ifdef Q_WS_WIN + if (!d->m_vars.contains(key)) { + static const QRegExp regex(QLatin1String("\\\\|/")); + const QString filename = key.section(regex, 0, -2); + const QString regKey = key.section(regex, -1); + const QSettingsWrapper registry(filename, QSettingsWrapper::NativeFormat); + if (!filename.isEmpty() && !regKey.isEmpty() && registry.contains(regKey)) + return registry.value(regKey).toString(); + } +#else + if (key == scTargetDir) { + const QString dir = d->m_vars.value(key, defaultValue); + if (dir.startsWith(QLatin1String("~/"))) + return QDir::home().absoluteFilePath(dir.mid(2)); + return dir; + } +#endif + return d->m_vars.value(key, defaultValue); +} + +/*! + Sets the installer value for \a key to \a value. +*/ +void PackageManagerCore::setValue(const QString &key, const QString &value) +{ + if (d->m_vars.value(key) == value) + return; + + d->m_vars.insert(key, value); + emit valueChanged(key, value); +} + +/*! + Returns true, when the installer contains a value for \a key. +*/ +bool PackageManagerCore::containsValue(const QString &key) const +{ + return d->m_vars.contains(key); +} + +void PackageManagerCore::setSharedFlag(const QString &key, bool value) +{ + d->m_sharedFlags.insert(key, value); +} + +bool PackageManagerCore::sharedFlag(const QString &key) const +{ + return d->m_sharedFlags.value(key, false); +} + +bool PackageManagerCore::isVerbose() const +{ + return QInstaller::isVerbose(); +} + +void PackageManagerCore::setVerbose(bool on) +{ + QInstaller::setVerbose(on); +} + +PackageManagerCore::Status PackageManagerCore::status() const +{ + return PackageManagerCore::Status(d->m_status); +} + +QString PackageManagerCore::error() const +{ + return d->m_error; +} + +/*! + Returns true if at least one complete installation/update was successful, even if the user cancelled the + newest installation process. +*/ +bool PackageManagerCore::finishedWithSuccess() const +{ + return d->m_status == PackageManagerCore::Success || d->m_needToWriteUninstaller; +} + +void PackageManagerCore::interrupt() +{ + setCanceled(); + emit installationInterrupted(); +} + +void PackageManagerCore::setCanceled() +{ + cancelMetaInfoJob(); + d->setStatus(PackageManagerCore::Canceled); +} + +/*! + Replaces all variables within \a str by their respective values and returns the result. +*/ +QString PackageManagerCore::replaceVariables(const QString &str) const +{ + return d->replaceVariables(str); +} + +/*! + \overload + Replaces all variables in any of \a str by their respective values and returns the results. +*/ +QStringList PackageManagerCore::replaceVariables(const QStringList &str) const +{ + QStringList result; + foreach (const QString &s, str) + result.push_back(d->replaceVariables(s)); + + return result; +} + +/*! + \overload + Replaces all variables within \a ba by their respective values and returns the result. +*/ +QByteArray PackageManagerCore::replaceVariables(const QByteArray &ba) const +{ + return d->replaceVariables(ba); +} + +/*! + Returns the path to the installer binary. +*/ +QString PackageManagerCore::installerBinaryPath() const +{ + return d->installerBinaryPath(); +} + +/*! + Returns true when this is the installer running. +*/ +bool PackageManagerCore::isInstaller() const +{ + return d->isInstaller(); +} + +/*! + Returns true if this is an offline-only installer. +*/ +bool PackageManagerCore::isOfflineOnly() const +{ + return d->isOfflineOnly(); +} + +void PackageManagerCore::setUninstaller() +{ + d->m_magicBinaryMarker = QInstaller::MagicUninstallerMarker; +} + +/*! + Returns true when this is the uninstaller running. +*/ +bool PackageManagerCore::isUninstaller() const +{ + return d->isUninstaller(); +} + +void PackageManagerCore::setUpdater() +{ + d->m_magicBinaryMarker = QInstaller::MagicUpdaterMarker; +} + +/*! + Returns true when this is neither an installer nor an uninstaller running. + Must be an updater, then. +*/ +bool PackageManagerCore::isUpdater() const +{ + return d->isUpdater(); +} + +void PackageManagerCore::setPackageManager() +{ + d->m_magicBinaryMarker = QInstaller::MagicPackageManagerMarker; +} + +/*! + Returns true when this is the package manager running. +*/ +bool PackageManagerCore::isPackageManager() const +{ + return d->isPackageManager(); +} + +/*! + Runs the installer. Returns true on success, false otherwise. +*/ +bool PackageManagerCore::runInstaller() +{ + try { + d->runInstaller(); + return true; + } catch (...) { + return false; + } +} + +/*! + Runs the uninstaller. Returns true on success, false otherwise. +*/ +bool PackageManagerCore::runUninstaller() +{ + try { + d->runUninstaller(); + return true; + } catch (...) { + return false; + } +} + +/*! + Runs the package updater. Returns true on success, false otherwise. +*/ +bool PackageManagerCore::runPackageUpdater() +{ + try { + d->runPackageUpdater(); + return true; + } catch (...) { + return false; + } +} + +/*! + \internal + Calls languangeChanged on all components. +*/ +void PackageManagerCore::languageChanged() +{ + foreach (Component *component, availableComponents()) + component->languageChanged(); +} + +/*! + Runs the installer, un-installer, updater or package manager, depending on the type of this binary. +*/ +bool PackageManagerCore::run() +{ + try { + if (isInstaller()) + d->runInstaller(); + else if (isUninstaller()) + d->runUninstaller(); + else if (isPackageManager() || isUpdater()) + d->runPackageUpdater(); + return true; + } catch (const Error &err) { + qDebug() << "Caught Installer Error:" << err.message(); + return false; + } +} + +/*! + Returns the path name of the uninstaller binary. +*/ +QString PackageManagerCore::uninstallerName() const +{ + return d->uninstallerName(); +} + +bool PackageManagerCore::updateComponentData(struct Data &data, Component *component) +{ + try { + // check if we already added the component to the available components list + const QString name = data.package->data(scName).toString(); + if (data.components->contains(name)) { + qCritical("Could not register component! Component with identifier %s already registered.", + qPrintable(name)); + return false; + } + + component->setUninstalled(); + const QString localPath = component->localTempPath(); + if (isVerbose()) { + static QString lastLocalPath; + if (lastLocalPath != localPath) + qDebug() << "Url is:" << localPath; + lastLocalPath = localPath; + } + + if (d->m_repoMetaInfoJob) { + const Repository &repo = d->m_repoMetaInfoJob->repositoryForTemporaryDirectory(localPath); + component->setRepositoryUrl(repo.url()); + component->setValue(QLatin1String("username"), repo.username()); + component->setValue(QLatin1String("password"), repo.password()); + } + + // add downloadable archive from xml + const QStringList downloadableArchives = data.package->data(scDownloadableArchives).toString() + .split(QRegExp(QLatin1String("\\b(,|, )\\b")), QString::SkipEmptyParts); + + if (component->isFromOnlineRepository()) { + foreach (const QString downloadableArchive, downloadableArchives) + component->addDownloadableArchive(downloadableArchive); + } + + const QStringList componentsToReplace = data.package->data(scReplaces).toString() + .split(QRegExp(QLatin1String("\\b(,|, )\\b")), QString::SkipEmptyParts); + + if (!componentsToReplace.isEmpty()) { + // Store the component (this is a component that replaces others) and all components that + // this one will replace. + data.replacementToExchangeables.insert(component, componentsToReplace); + } + + if (isInstaller()) { + // Running as installer means no component is installed, we do not need to check if the + // replacement needs to be marked as installed, just return. + return true; + } + + if (data.installedPackages->contains(name)) { + // The replacement is already installed, we can mark it as installed and skip the search for + // a possible component to replace that might be installed (to mark the replacement as installed). + component->setInstalled(); + component->setValue(scInstalledVersion, data.installedPackages->value(name).version); + return true; + } + + // The replacement is not yet installed, check all components to replace for there install state. + foreach (const QString &componentName, componentsToReplace) { + if (data.installedPackages->contains(componentName)) { + // We found a replacement that is installed. + if (isPackageManager()) { + // Mark the replacement component as installed as well. Only do this in package manager + // mode, otherwise it would not show up in the updaters component list. + component->setInstalled(); + component->setValue(scInstalledVersion, data.installedPackages->value(componentName).version); + break; // Break as soon as we know we found an installed component this one replaces. + } + } + } + } catch (...) { + return false; + } + + return true; +} + +void PackageManagerCore::storeReplacedComponents(QHash<QString, Component *> &components, const struct Data &data) +{ + QHash<Component*, QStringList>::const_iterator it = data.replacementToExchangeables.constBegin(); + // remember all components that got a replacement, required for uninstall + for (; it != data.replacementToExchangeables.constEnd(); ++it) { + foreach (const QString &componentName, it.value()) { + Component *component = components.take(componentName); + // if one component has a replaces which is not existing in the current component list anymore + // just ignore it + if (!component) { + // This case can happen when in installer mode, but should not occur when updating + if (isUpdater()) + qWarning() << componentName << "- Does not exist in the repositories anymore."; + continue; + } + if (!component && !d->componentsToReplace(data.runMode).contains(componentName)) { + component = new Component(this); + component->setValue(scName, componentName); + } else { + component->loadComponentScript(); + d->replacementDependencyComponents(data.runMode).append(component); + } + d->componentsToReplace(data.runMode).insert(componentName, qMakePair(it.key(), component)); + } + } +} + +bool PackageManagerCore::fetchAllPackages(const PackagesList &remotes, const LocalPackagesHash &locals) +{ + emit startAllComponentsReset(); + + d->clearAllComponentLists(); + QHash<QString, QInstaller::Component*> components; + + Data data; + data.runMode = AllMode; + data.components = &components; + data.installedPackages = &locals; + + foreach (Package *const package, remotes) { + if (d->statusCanceledOrFailed()) + return false; + + QScopedPointer<QInstaller::Component> component(new QInstaller::Component(this)); + data.package = package; + component->loadDataFromPackage(*package); + if (updateComponentData(data, component.data())) { + const QString name = component->name(); + components.insert(name, component.take()); + } + } + + foreach (const QString &key, locals.keys()) { + QScopedPointer<QInstaller::Component> component(new QInstaller::Component(this)); + component->loadDataFromPackage(locals.value(key)); + const QString &name = component->name(); + if (!components.contains(name)) + components.insert(name, component.take()); + } + + // store all components that got a replacement + storeReplacedComponents(components, data); + + if (!d->buildComponentTree(components, true)) + return false; + + emit finishAllComponentsReset(); + return true; +} + +bool PackageManagerCore::fetchUpdaterPackages(const PackagesList &remotes, const LocalPackagesHash &locals) +{ + emit startUpdaterComponentsReset(); + + d->clearUpdaterComponentLists(); + QHash<QString, QInstaller::Component *> components; + + Data data; + data.runMode = UpdaterMode; + data.components = &components; + data.installedPackages = &locals; + + bool foundEssentialUpdate = false; + LocalPackagesHash installedPackages = locals; + QStringList replaceMes; + + foreach (Package *const update, remotes) { + if (d->statusCanceledOrFailed()) + return false; + + QScopedPointer<QInstaller::Component> component(new QInstaller::Component(this)); + data.package = update; + component->loadDataFromPackage(*update); + if (updateComponentData(data, component.data())) { + // Keep a reference so we can resolve dependencies during update. + d->m_updaterComponentsDeps.append(component.take()); + +// const QString isNew = update->data(scNewComponent).toString(); +// if (isNew.toLower() != scTrue) +// continue; + + const QString &name = d->m_updaterComponentsDeps.last()->name(); + const QString replaces = data.package->data(scReplaces).toString(); + installedPackages.take(name); // remove from local installed packages + + bool isValidUpdate = locals.contains(name); + if (!isValidUpdate && !replaces.isEmpty()) { + const QStringList possibleNames = replaces.split(QRegExp(QLatin1String("\\b(,|, )\\b")), + QString::SkipEmptyParts); + foreach (const QString &possibleName, possibleNames) { + if (locals.contains(possibleName)) { + isValidUpdate = true; + replaceMes << possibleName; + } + } + } + + if (!isValidUpdate) + continue; // Update for not installed package found, skip it. + + const LocalPackage &localPackage = locals.value(name); + const QString updateVersion = update->data(scRemoteVersion).toString(); + if (KDUpdater::compareVersion(updateVersion, localPackage.version) <= 0) + continue; + + // It is quite possible that we may have already installed the update. Lets check the last + // update date of the package and the release date of the update. This way we can compare and + // figure out if the update has been installed or not. + const QDate updateDate = update->data(scReleaseDate).toDate(); + if (localPackage.lastUpdateDate > updateDate) + continue; + + if (update->data(scEssential, scFalse).toString().toLower() == scTrue) + foundEssentialUpdate = true; + + // this is not a dependency, it is a real update + components.insert(name, d->m_updaterComponentsDeps.takeLast()); + } + } + + QHash<QString, QInstaller::Component *> localReplaceMes; + foreach (const QString &key, installedPackages.keys()) { + QInstaller::Component *component = new QInstaller::Component(this); + component->loadDataFromPackage(installedPackages.value(key)); + d->m_updaterComponentsDeps.append(component); + // Keep a list of local components that should be replaced + if (replaceMes.contains(component->name())) + localReplaceMes.insert(component->name(), component); + } + + // store all components that got a replacement, but do not modify the components list + storeReplacedComponents(localReplaceMes.unite(components), data); + + try { + if (!components.isEmpty()) { + // load the scripts and append all components w/o parent to the direct list + foreach (QInstaller::Component *component, components) { + if (d->statusCanceledOrFailed()) + return false; + + component->loadComponentScript(); + component->setCheckState(Qt::Checked); + appendUpdaterComponent(component); + } + + // after everything is set up, check installed components + foreach (QInstaller::Component *component, d->m_updaterComponentsDeps) { + if (d->statusCanceledOrFailed()) + return false; + // even for possible dependency we need to load the script for example to get archives + component->loadComponentScript(); + if (component->isInstalled()) { + // since we do not put them into the model, which would force a update of e.g. tri state + // components, we have to check all installed components ourselves + component->setCheckState(Qt::Checked); + } + } + + if (foundEssentialUpdate) { + foreach (QInstaller::Component *component, components) { + if (d->statusCanceledOrFailed()) + return false; + + component->setCheckable(false); + component->setSelectable(false); + if (component->value(scEssential, scFalse).toLower() == scFalse) { + // non essential updates are disabled, not checkable and unchecked + component->setEnabled(false); + component->setCheckState(Qt::Unchecked); + } else { + // essential updates are enabled, still not checkable but checked + component->setEnabled(true); + } + } + } + } else { + // we have no updates, no need to store possible dependencies + d->clearUpdaterComponentLists(); + } + } catch (const Error &error) { + d->clearUpdaterComponentLists(); + emit finishUpdaterComponentsReset(); + d->setStatus(Failure, error.message()); + + // TODO: make sure we remove all message boxes inside the library at some point. + MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), QLatin1String("Error"), + tr("Error"), error.message()); + return false; + } + + emit finishUpdaterComponentsReset(); + return true; +} + +void PackageManagerCore::resetComponentsToUserCheckedState() +{ + d->resetComponentsToUserCheckedState(); +} + +void PackageManagerCore::updateDisplayVersions(const QString &displayKey) +{ + QHash<QString, QInstaller::Component *> components; + const QList<QInstaller::Component *> componentList = availableComponents(); + foreach (QInstaller::Component *component, componentList) + components[component->name()] = component; + + // set display version for all components in list + const QStringList &keys = components.keys(); + foreach (const QString &key, keys) { + QHash<QString, bool> visited; + if (components.value(key)->isInstalled()) { + components.value(key)->setValue(scDisplayVersion, + findDisplayVersion(key, components, scInstalledVersion, visited)); + } + visited.clear(); + const QString displayVersionRemote = findDisplayVersion(key, components, scRemoteVersion, visited); + if (displayVersionRemote.isEmpty()) + components.value(key)->setValue(displayKey, tr("invalid")); + else + components.value(key)->setValue(displayKey, displayVersionRemote); + } + +} + +QString PackageManagerCore::findDisplayVersion(const QString &componentName, + const QHash<QString, Component *> &components, const QString &versionKey, QHash<QString, bool> &visited) +{ + if (!components.contains(componentName)) + return QString(); + const QString replaceWith = components.value(componentName)->value(scInheritVersion); + visited[componentName] = true; + + if (replaceWith.isEmpty()) + return components.value(componentName)->value(versionKey); + + if (visited.contains(replaceWith)) // cycle + return QString(); + + return findDisplayVersion(replaceWith, components, versionKey, visited); +} + +bool PackageManagerCore::createLocalRepositoryFromBinary() const +{ + return d->m_createLocalRepositoryFromBinary; +} + +void PackageManagerCore::setCreateLocalRepositoryFromBinary(bool create) +{ + if (!isOfflineOnly()) + return; + d->m_createLocalRepositoryFromBinary = create; +} diff --git a/src/libs/installer/packagemanagercore.h b/src/libs/installer/packagemanagercore.h new file mode 100644 index 000000000..4e02b3ba3 --- /dev/null +++ b/src/libs/installer/packagemanagercore.h @@ -0,0 +1,308 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ +#ifndef PACKAGEMANAGERCORE_H +#define PACKAGEMANAGERCORE_H + +#include "repository.h" +#include "qinstallerglobal.h" + +#include <QtCore/QHash> +#include <QtCore/QObject> +#include <QtCore/QStringList> +#include <QtCore/QVector> + +namespace KDUpdater { + class FileDownloaderProxyFactory; +} + +namespace QInstaller { + +class Component; +class PackageManagerCorePrivate; +class Settings; + +// -- PackageManagerCore + +class INSTALLER_EXPORT PackageManagerCore : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(PackageManagerCore) + + Q_ENUMS(Status WizardPage) + Q_PROPERTY(int status READ status NOTIFY statusChanged) + +public: + explicit PackageManagerCore(); + explicit PackageManagerCore(qint64 magicmaker, const OperationList &oldOperations = OperationList()); + ~PackageManagerCore(); + + // status + enum Status { + Success = EXIT_SUCCESS, + Failure = EXIT_FAILURE, + Running, + Canceled, + Unfinished + }; + Status status() const; + QString error() const; + + enum WizardPage { + Introduction = 0x1000, + TargetDirectory = 0x2000, + ComponentSelection = 0x3000, + LicenseCheck = 0x4000, + StartMenuSelection = 0x5000, + ReadyForInstallation = 0x6000, + PerformInstallation = 0x7000, + InstallationFinished = 0x8000, + End = 0xffff + }; + + static QFont virtualComponentsFont(); + static void setVirtualComponentsFont(const QFont &font); + + static bool virtualComponentsVisible(); + static void setVirtualComponentsVisible(bool visible); + + static bool noForceInstallation(); + static void setNoForceInstallation(bool value); + + bool fetchLocalPackagesTree(); + LocalPackagesHash localInstalledPackages(); + + void networkSettingsChanged(); + KDUpdater::FileDownloaderProxyFactory *proxyFactory() const; + void setProxyFactory(KDUpdater::FileDownloaderProxyFactory *factory); + + PackagesList remotePackages(); + bool fetchRemotePackagesTree(); + + bool run(); + RunMode runMode() const; + void reset(const QHash<QString, QString> ¶ms); + + Q_INVOKABLE QList<QVariant> execute(const QString &program, + const QStringList &arguments = QStringList(), const QString &stdIn = QString()) const; + Q_INVOKABLE bool executeDetached(const QString &program, + const QStringList &arguments = QStringList()) const; + Q_INVOKABLE QString environmentVariable(const QString &name) const; + + Q_INVOKABLE bool performOperation(const QString &name, const QStringList &arguments); + + Q_INVOKABLE static bool versionMatches(const QString &version, const QString &requirement); + + Q_INVOKABLE static QString findLibrary(const QString &name, const QStringList &paths = QStringList()); + Q_INVOKABLE static QString findPath(const QString &name, const QStringList &paths = QStringList()); + + Q_INVOKABLE void setInstallerBaseBinary(const QString &path); + + // parameter handling + Q_INVOKABLE bool containsValue(const QString &key) const; + Q_INVOKABLE void setValue(const QString &key, const QString &value); + Q_INVOKABLE QString value(const QString &key, const QString &defaultValue = QString()) const; + + //a way to have global flags share able from a component script to another one + Q_INVOKABLE bool sharedFlag(const QString &key) const; + Q_INVOKABLE void setSharedFlag(const QString &key, bool value = true); + + QString replaceVariables(const QString &str) const; + QByteArray replaceVariables(const QByteArray &str) const; + QStringList replaceVariables(const QStringList &str) const; + + void writeUninstaller(); + QString uninstallerName() const; + QString installerBinaryPath() const; + + bool testChecksum() const; + void setTestChecksum(bool test); + + void addUserRepositories(const QSet<Repository> &repositories); + void setTemporaryRepositories(const QSet<Repository> &repositories, bool replace = false); + + Q_INVOKABLE void autoAcceptMessageBoxes(); + Q_INVOKABLE void autoRejectMessageBoxes(); + Q_INVOKABLE void setMessageBoxAutomaticAnswer(const QString &identifier, int button); + + Q_INVOKABLE bool isFileExtensionRegistered(const QString &extension) const; + +public: + // component handling + int rootComponentCount() const; + Component *rootComponent(int i) const; + QList<Component*> rootComponents() const; + void appendRootComponent(Component *components); + + Q_INVOKABLE int updaterComponentCount() const; + Component *updaterComponent(int i) const; + QList<Component*> updaterComponents() const; + void appendUpdaterComponent(Component *components); + + QList<Component*> availableComponents() const; + Component *componentByName(const QString &identifier) const; + + bool calculateComponentsToInstall() const; + QList<Component*> orderedComponentsToInstall() const; + + bool calculateComponentsToUninstall() const; + QList<Component*> componentsToUninstall() const; + + QString componentsToInstallError() const; + QString installReason(Component *component) const; + + QList<Component*> dependees(const Component *component) const; + QList<Component*> dependencies(const Component *component, QStringList &missingComponents) const; + + // convenience + Q_INVOKABLE bool isInstaller() const; + Q_INVOKABLE bool isOfflineOnly() const; + + Q_INVOKABLE void setUninstaller(); + Q_INVOKABLE bool isUninstaller() const; + + Q_INVOKABLE void setUpdater(); + Q_INVOKABLE bool isUpdater() const; + + Q_INVOKABLE void setPackageManager(); + Q_INVOKABLE bool isPackageManager() const; + + bool isVerbose() const; + void setVerbose(bool on); + + Q_INVOKABLE bool gainAdminRights(); + Q_INVOKABLE void dropAdminRights(); + + Q_INVOKABLE quint64 requiredDiskSpace() const; + Q_INVOKABLE quint64 requiredTemporaryDiskSpace() const; + + Q_INVOKABLE bool isProcessRunning(const QString &name) const; + + Settings &settings() const; + + Q_INVOKABLE bool addWizardPage(QInstaller::Component *component, const QString &name, int page); + Q_INVOKABLE bool removeWizardPage(QInstaller::Component *component, const QString &name); + Q_INVOKABLE bool addWizardPageItem(QInstaller::Component *component, const QString &name, int page); + Q_INVOKABLE bool removeWizardPageItem(QInstaller::Component *component, const QString &name); + Q_INVOKABLE bool setDefaultPageVisible(int page, bool visible); + + void rollBackInstallation(); + + int downloadNeededArchives(double partProgressSize); + void installComponent(Component *component, double progressOperationSize); + + bool needsRestart() const; + bool finishedWithSuccess() const; + + Q_INVOKABLE bool createLocalRepositoryFromBinary() const; + Q_INVOKABLE void setCreateLocalRepositoryFromBinary(bool create); + +public Q_SLOTS: + bool runInstaller(); + bool runUninstaller(); + bool runPackageUpdater(); + void interrupt(); + void setCanceled(); + void languageChanged(); + void setCompleteUninstallation(bool complete); + void cancelMetaInfoJob(); + void componentsToInstallNeedsRecalculation(); + +Q_SIGNALS: + void componentAdded(QInstaller::Component *comp); + void rootComponentsAdded(QList<QInstaller::Component*> components); + void updaterComponentsAdded(QList<QInstaller::Component*> components); + void componentsAboutToBeCleared(); + void valueChanged(const QString &key, const QString &value); + void statusChanged(QInstaller::PackageManagerCore::Status); + void currentPageChanged(int page); + void finishButtonClicked(); + + void metaJobInfoMessage(const QString &message); + + void startAllComponentsReset(); + void finishAllComponentsReset(); + + void startUpdaterComponentsReset(); + void finishUpdaterComponentsReset(); + + void installationStarted(); + void installationInterrupted(); + void installationFinished(); + void updateFinished(); + void uninstallationStarted(); + void uninstallationFinished(); + void titleMessageChanged(const QString &title); + + void wizardPageInsertionRequested(QWidget *widget, QInstaller::PackageManagerCore::WizardPage page); + void wizardPageRemovalRequested(QWidget *widget); + void wizardWidgetInsertionRequested(QWidget *widget, QInstaller::PackageManagerCore::WizardPage page); + void wizardWidgetRemovalRequested(QWidget *widget); + void wizardPageVisibilityChangeRequested(bool visible, int page); + + void setAutomatedPageSwitchEnabled(bool request); + void coreNetworkSettingsChanged(); + +private: + struct Data { + RunMode runMode; + Package *package; + QHash<QString, Component*> *components; + const LocalPackagesHash *installedPackages; + QHash<Component*, QStringList> replacementToExchangeables; + }; + + bool updateComponentData(struct Data &data, QInstaller::Component *component); + void storeReplacedComponents(QHash<QString, Component*> &components, const struct Data &data); + bool fetchAllPackages(const PackagesList &remotePackages, const LocalPackagesHash &localPackages); + bool fetchUpdaterPackages(const PackagesList &remotePackages, const LocalPackagesHash &localPackages); + + static Component *subComponentByName(const QInstaller::PackageManagerCore *installer, const QString &name, + const QString &version = QString(), Component *check = 0); + + void updateDisplayVersions(const QString &displayKey); + QString findDisplayVersion(const QString &componentName, const QHash<QString, QInstaller::Component*> &components, + const QString& versionKey, QHash<QString, bool> &visited); +private: + PackageManagerCorePrivate *const d; + friend class PackageManagerCorePrivate; + +private: + // remove once we deprecate isSelected, setSelected etc... + friend class ComponentSelectionPage; + void resetComponentsToUserCheckedState(); +}; + +} + +Q_DECLARE_METATYPE(QInstaller::PackageManagerCore*) + +#endif // PACKAGEMANAGERCORE_H diff --git a/src/libs/installer/packagemanagercore_p.cpp b/src/libs/installer/packagemanagercore_p.cpp new file mode 100644 index 000000000..765db4e4e --- /dev/null +++ b/src/libs/installer/packagemanagercore_p.cpp @@ -0,0 +1,2326 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ +#include "packagemanagercore_p.h" + +#include "adminauthorization.h" +#include "binaryformat.h" +#include "component.h" +#include "errors.h" +#include "fileutils.h" +#include "fsengineclient.h" +#include "messageboxhandler.h" +#include "packagemanagercore.h" +#include "progresscoordinator.h" +#include "qprocesswrapper.h" +#include "qsettingswrapper.h" + +#include "kdsavefile.h" +#include "kdselfrestarter.h" +#include "kdupdaterfiledownloaderfactory.h" +#include "kdupdaterupdatesourcesinfo.h" +#include "kdupdaterupdateoperationfactory.h" +#include "kdupdaterupdatefinder.h" + +#include <QtCore/QtConcurrentRun> +#include <QtCore/QCoreApplication> +#include <QtCore/QDir> +#include <QtCore/QDirIterator> +#include <QtCore/QFuture> +#include <QtCore/QFutureWatcher> +#include <QtCore/QTemporaryFile> + +#include <QtXml/QXmlStreamReader> +#include <QtXml/QXmlStreamWriter> + +#include <errno.h> + +namespace QInstaller { + +static bool runOperation(Operation *op, PackageManagerCorePrivate::OperationType type) +{ + switch (type) { + case PackageManagerCorePrivate::Backup: + op->backup(); + return true; + case PackageManagerCorePrivate::Perform: + return op->performOperation(); + case PackageManagerCorePrivate::Undo: + return op->undoOperation(); + default: + Q_ASSERT(!"unexpected operation type"); + } + return false; +} + +/*! + \internal + Creates and initializes a FSEngineClientHandler -> makes us get admin rights for QFile operations +*/ +static FSEngineClientHandler *sClientHandlerInstance = 0; +static FSEngineClientHandler *initFSEngineClientHandler() +{ + if (sClientHandlerInstance == 0) { + sClientHandlerInstance = &FSEngineClientHandler::instance(); + + // Initialize the created FSEngineClientHandler instance. + const int port = 30000 + qrand() % 1000; + sClientHandlerInstance->init(port); + sClientHandlerInstance->setStartServerCommand(QCoreApplication::applicationFilePath(), + QStringList() << QLatin1String("--startserver") << QString::number(port) + << sClientHandlerInstance->authorizationKey(), true); + } + return sClientHandlerInstance; +} + +static QStringList checkRunningProcessesFromList(const QStringList &processList) +{ + const QList<ProcessInfo> allProcesses = runningProcesses(); + QStringList stillRunningProcesses; + foreach (const QString &process, processList) { + if (!process.isEmpty() && PackageManagerCorePrivate::isProcessRunning(process, allProcesses)) + stillRunningProcesses.append(process); + } + return stillRunningProcesses; +} + +static void deferredRename(const QString &oldName, const QString &newName, bool restart = false) +{ +#ifdef Q_OS_WIN + QStringList arguments; + { + QTemporaryFile f(QDir::temp().absoluteFilePath(QLatin1String("deferredrenameXXXXXX.vbs"))); + openForWrite(&f, f.fileName()); + f.setAutoRemove(false); + + arguments << QDir::toNativeSeparators(f.fileName()) << QDir::toNativeSeparators(oldName) + << QDir::toNativeSeparators(QFileInfo(oldName).dir().absoluteFilePath(QFileInfo(newName) + .fileName())); + + QTextStream batch(&f); + batch << "Set fso = WScript.CreateObject(\"Scripting.FileSystemObject\")\n"; + batch << "Set tmp = WScript.CreateObject(\"WScript.Shell\")\n"; + batch << QString::fromLatin1("file = \"%1\"\n").arg(arguments[2]); + batch << "on error resume next\n"; + + batch << "while fso.FileExists(file)\n"; + batch << " fso.DeleteFile(file)\n"; + batch << " WScript.Sleep(1000)\n"; + batch << "wend\n"; + batch << QString::fromLatin1("fso.MoveFile \"%1\", file\n").arg(arguments[1]); + if (restart) + batch << QString::fromLatin1("tmp.exec \"%1 --updater\"\n").arg(arguments[2]); + batch << "fso.DeleteFile(WScript.ScriptFullName)\n"; + } + + QProcessWrapper::startDetached(QLatin1String("cscript"), QStringList() << QLatin1String("//Nologo") + << arguments[0]); +#else + QFile::remove(newName); + QFile::rename(oldName, newName); + KDSelfRestarter::setRestartOnQuit(restart); +#endif +} + + +// -- PackageManagerCorePrivate + +PackageManagerCorePrivate::PackageManagerCorePrivate(PackageManagerCore *core) + : m_updateFinder(0) + , m_FSEngineClientHandler(0) + , m_core(core) + , m_repoMetaInfoJob(0) + , m_updates(false) + , m_repoFetched(false) + , m_updateSourcesAdded(false) + , m_componentsToInstallCalculated(false) + , m_proxyFactory(0) + , m_createLocalRepositoryFromBinary(false) +{ +} + +PackageManagerCorePrivate::PackageManagerCorePrivate(PackageManagerCore *core, qint64 magicInstallerMaker, + const OperationList &performedOperations) + : m_updateFinder(0) + , m_FSEngineClientHandler(initFSEngineClientHandler()) + , m_status(PackageManagerCore::Unfinished) + , m_forceRestart(false) + , m_testChecksum(false) + , m_launchedAsRoot(AdminAuthorization::hasAdminRights()) + , m_completeUninstall(false) + , m_needToWriteUninstaller(false) + , m_performedOperationsOld(performedOperations) + , m_core(core) + , m_repoMetaInfoJob(0) + , m_updates(false) + , m_repoFetched(false) + , m_updateSourcesAdded(false) + , m_magicBinaryMarker(magicInstallerMaker) + , m_componentsToInstallCalculated(false) + , m_proxyFactory(0) + , m_createLocalRepositoryFromBinary(false) +{ + connect(this, SIGNAL(installationStarted()), m_core, SIGNAL(installationStarted())); + connect(this, SIGNAL(installationFinished()), m_core, SIGNAL(installationFinished())); + connect(this, SIGNAL(uninstallationStarted()), m_core, SIGNAL(uninstallationStarted())); + connect(this, SIGNAL(uninstallationFinished()), m_core, SIGNAL(uninstallationFinished())); +} + +PackageManagerCorePrivate::~PackageManagerCorePrivate() +{ + clearAllComponentLists(); + clearUpdaterComponentLists(); + clearComponentsToInstall(); + + qDeleteAll(m_ownedOperations); + qDeleteAll(m_performedOperationsOld); + qDeleteAll(m_performedOperationsCurrentSession); + + // check for fake installer case + if (m_FSEngineClientHandler) + m_FSEngineClientHandler->setActive(false); + + delete m_updateFinder; + delete m_proxyFactory; +} + +/*! + Return true, if a process with \a name is running. On Windows, comparison is case-insensitive. +*/ +/* static */ +bool PackageManagerCorePrivate::isProcessRunning(const QString &name, + const QList<ProcessInfo> &processes) +{ + QList<ProcessInfo>::const_iterator it; + for (it = processes.constBegin(); it != processes.constEnd(); ++it) { + if (it->name.isEmpty()) + continue; + +#ifndef Q_WS_WIN + if (it->name == name) + return true; + const QFileInfo fi(it->name); + if (fi.fileName() == name || fi.baseName() == name) + return true; +#else + if (it->name.toLower() == name.toLower()) + return true; + if (it->name.toLower() == QDir::toNativeSeparators(name.toLower())) + return true; + const QFileInfo fi(it->name); + if (fi.fileName().toLower() == name.toLower() || fi.baseName().toLower() == name.toLower()) + return true; +#endif + } + return false; +} + +/* static */ +bool PackageManagerCorePrivate::performOperationThreaded(Operation *operation, OperationType type) +{ + QFutureWatcher<bool> futureWatcher; + const QFuture<bool> future = QtConcurrent::run(runOperation, operation, type); + + QEventLoop loop; + loop.connect(&futureWatcher, SIGNAL(finished()), SLOT(quit()), Qt::QueuedConnection); + futureWatcher.setFuture(future); + + if (!future.isFinished()) + loop.exec(); + + return future.result(); +} + +QString PackageManagerCorePrivate::targetDir() const +{ + return m_core->value(scTargetDir); +} + +QString PackageManagerCorePrivate::configurationFileName() const +{ + return m_core->value(scTargetConfigurationFile, QLatin1String("components.xml")); +} + +QString PackageManagerCorePrivate::componentsXmlPath() const +{ + return QDir::toNativeSeparators(QDir(QDir::cleanPath(targetDir())) + .absoluteFilePath(configurationFileName())); +} + +bool PackageManagerCorePrivate::buildComponentTree(QHash<QString, Component*> &components, bool loadScript) +{ + try { + // append all components to their respective parents + QHash<QString, Component*>::const_iterator it; + for (it = components.begin(); it != components.end(); ++it) { + if (statusCanceledOrFailed()) + return false; + + QString id = it.key(); + QInstaller::Component *component = it.value(); + while (!id.isEmpty() && component->parentComponent() == 0) { + id = id.section(QLatin1Char('.'), 0, -2); + if (components.contains(id)) + components[id]->appendComponent(component); + } + } + + // append all components w/o parent to the direct list + foreach (QInstaller::Component *component, components) { + if (statusCanceledOrFailed()) + return false; + + if (component->parentComponent() == 0) + m_core->appendRootComponent(component); + } + + // after everything is set up, load the scripts + foreach (QInstaller::Component *component, components) { + if (statusCanceledOrFailed()) + return false; + + if (loadScript) + component->loadComponentScript(); + + // set the checked state for all components without child (means without tristate) + if (component->isCheckable() && !component->isTristate()) { + if (component->isDefault() && isInstaller()) + component->setCheckState(Qt::Checked); + else if (component->isInstalled()) + component->setCheckState(Qt::Checked); + } + } + } catch (const Error &error) { + clearAllComponentLists(); + emit m_core->finishAllComponentsReset(); + setStatus(PackageManagerCore::Failure, error.message()); + + // TODO: make sure we remove all message boxes inside the library at some point. + MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), QLatin1String("Error"), + tr("Error"), error.message()); + return false; + } + return true; +} + +void PackageManagerCorePrivate::clearAllComponentLists() +{ + qDeleteAll(m_rootComponents); + m_rootComponents.clear(); + + m_rootDependencyReplacements.clear(); + + const QList<QPair<Component*, Component*> > list = m_componentsToReplaceAllMode.values(); + for (int i = 0; i < list.count(); ++i) + delete list.at(i).second; + m_componentsToReplaceAllMode.clear(); + m_componentsToInstallCalculated = false; +} + +void PackageManagerCorePrivate::clearUpdaterComponentLists() +{ + QSet<Component*> usedComponents = + QSet<Component*>::fromList(m_updaterComponents + m_updaterComponentsDeps); + + const QList<QPair<Component*, Component*> > list = m_componentsToReplaceUpdaterMode.values(); + for (int i = 0; i < list.count(); ++i) { + if (usedComponents.contains(list.at(i).second)) + qWarning() << "a replacement was already in the list - is that correct?"; + else + usedComponents.insert(list.at(i).second); + } + + qDeleteAll(usedComponents); + + m_updaterComponents.clear(); + m_updaterComponentsDeps.clear(); + + m_updaterDependencyReplacements.clear(); + + m_componentsToReplaceUpdaterMode.clear(); + m_componentsToInstallCalculated = false; +} + +QList<Component *> &PackageManagerCorePrivate::replacementDependencyComponents(RunMode mode) +{ + return mode == AllMode ? m_rootDependencyReplacements : m_updaterDependencyReplacements; +} + +QHash<QString, QPair<Component*, Component*> > &PackageManagerCorePrivate::componentsToReplace(RunMode mode) +{ + return mode == AllMode ? m_componentsToReplaceAllMode : m_componentsToReplaceUpdaterMode; +} + +void PackageManagerCorePrivate::clearComponentsToInstall() +{ + m_visitedComponents.clear(); + m_toInstallComponentIds.clear(); + m_componentsToInstallError.clear(); + m_orderedComponentsToInstall.clear(); + m_toInstallComponentIdReasonHash.clear(); +} + +bool PackageManagerCorePrivate::appendComponentsToInstall(const QList<Component *> &components) +{ + if (components.isEmpty()) { + qDebug() << "components list is empty in" << Q_FUNC_INFO; + return true; + } + + QList<Component*> relevantComponentForAutoDependOn; + if (isUpdater()) + relevantComponentForAutoDependOn = m_updaterComponents + m_updaterComponentsDeps; + else { + foreach (QInstaller::Component *component, m_rootComponents) + relevantComponentForAutoDependOn += component->childComponents(true, AllMode); + } + + QList<Component*> notAppendedComponents; // for example components with unresolved dependencies + foreach (Component *component, components){ + if (m_toInstallComponentIds.contains(component->name())) { + QString errorMessage = QString::fromLatin1("Recursion detected component(%1) already added with " + "reason: \"%2\"").arg(component->name(), installReason(component)); + qDebug() << qPrintable(errorMessage); + m_componentsToInstallError.append(errorMessage); + Q_ASSERT_X(!m_toInstallComponentIds.contains(component->name()), Q_FUNC_INFO, + qPrintable(errorMessage)); + return false; + } + + if (component->dependencies().isEmpty()) + realAppendToInstallComponents(component); + else + notAppendedComponents.append(component); + } + + foreach (Component *component, notAppendedComponents) { + if (!appendComponentToInstall(component)) + return false; + } + + QList<Component *> foundAutoDependOnList; + // All regular dependencies are resolved. Now we are looking for auto depend on components. + foreach (Component *component, relevantComponentForAutoDependOn) { + // If a components is already installed or is scheduled for installation, no need to check for + // auto depend installation. + if ((!component->isInstalled() || component->updateRequested()) + && !m_toInstallComponentIds.contains(component->name())) { + // If we figure out a component requests auto installation, keep it to resolve their deps as + // well. + if (component->isAutoDependOn(m_toInstallComponentIds)) { + foundAutoDependOnList.append(component); + insertInstallReason(component, tr("Component(s) added as automatic dependencies")); + } + } + } + + if (!foundAutoDependOnList.isEmpty()) + return appendComponentsToInstall(foundAutoDependOnList); + return true; +} + +bool PackageManagerCorePrivate::appendComponentToInstall(Component *component) +{ + QSet<QString> allDependencies = component->dependencies().toSet(); + + foreach (const QString &dependencyComponentName, allDependencies) { + //componentByName return 0 if dependencyComponentName contains a version which is not available + Component *dependencyComponent = m_core->componentByName(dependencyComponentName); + if (dependencyComponent == 0) { + QString errorMessage; + if (!dependencyComponent) + errorMessage = QString::fromLatin1("Can't find missing dependency (%1) for %2."); + errorMessage = errorMessage.arg(dependencyComponentName, component->name()); + qDebug() << qPrintable(errorMessage); + m_componentsToInstallError.append(errorMessage); + Q_ASSERT_X(false, Q_FUNC_INFO, qPrintable(errorMessage)); + return false; + } + + if ((!dependencyComponent->isInstalled() || dependencyComponent->updateRequested()) + && !m_toInstallComponentIds.contains(dependencyComponent->name())) { + if (m_visitedComponents.value(component).contains(dependencyComponent)) { + QString errorMessage = QString::fromLatin1("Recursion detected component(%1) already " + "added with reason: \"%2\"").arg(component->name(), installReason(component)); + qDebug() << qPrintable(errorMessage); + m_componentsToInstallError = errorMessage; + Q_ASSERT_X(!m_visitedComponents.value(component).contains(dependencyComponent), Q_FUNC_INFO, + qPrintable(errorMessage)); + return false; + } + m_visitedComponents[component].insert(dependencyComponent); + + // add needed dependency components to the next run + insertInstallReason(dependencyComponent, tr("Added as dependency for %1.").arg(component->name())); + + if (!appendComponentToInstall(dependencyComponent)) + return false; + } + } + + if (!m_toInstallComponentIds.contains(component->name())) { + realAppendToInstallComponents(component); + insertInstallReason(component, tr("Component(s) that have resolved Dependencies")); + } + return true; +} + +QString PackageManagerCorePrivate::installReason(Component *component) +{ + const QString reason = m_toInstallComponentIdReasonHash.value(component->name()); + if (reason.isEmpty()) + return tr("Selected Component(s) without Dependencies"); + return m_toInstallComponentIdReasonHash.value(component->name()); +} + + +void PackageManagerCorePrivate::initialize() +{ + m_coreCheckedHash.clear(); + m_componentsToInstallCalculated = false; + m_createLocalRepositoryFromBinary = false; + + // first set some common variables that may used e.g. as placeholder + // in some of the settings variables or in a script or... + m_vars.insert(QLatin1String("rootDir"), QDir::rootPath()); + m_vars.insert(QLatin1String("homeDir"), QDir::homePath()); + m_vars.insert(scTargetConfigurationFile, QLatin1String("components.xml")); + +#ifdef Q_WS_WIN + m_vars.insert(QLatin1String("os"), QLatin1String("win")); +#elif defined(Q_WS_MAC) + m_vars.insert(QLatin1String("os"), QLatin1String("mac")); +#elif defined(Q_WS_X11) + m_vars.insert(QLatin1String("os"), QLatin1String("x11")); +#elif defined(Q_WS_QWS) + m_vars.insert(QLatin1String("os"), QLatin1String("Qtopia")); +#else + // TODO: add more platforms as needed... +#endif + + try { + m_settings = Settings(Settings::fromFileAndPrefix(QLatin1String(":/metadata/installer-config/config.xml"), + QLatin1String(":/metadata/installer-config/"))); + } catch (const Error &e) { + qCritical("Could not parse Config: %s", qPrintable(e.message())); + // TODO: try better error handling + return; + } + + // fill the variables defined in the settings + m_vars.insert(QLatin1String("ProductName"), m_settings.applicationName()); + m_vars.insert(QLatin1String("ProductVersion"), m_settings.applicationVersion()); + m_vars.insert(scTitle, m_settings.title()); + m_vars.insert(scPublisher, m_settings.publisher()); + m_vars.insert(QLatin1String("Url"), m_settings.url()); + m_vars.insert(scStartMenuDir, m_settings.startMenuDir()); + m_vars.insert(scTargetConfigurationFile, m_settings.configurationFileName()); + m_vars.insert(QLatin1String("LogoPixmap"), m_settings.logo()); + m_vars.insert(QLatin1String("LogoSmallPixmap"), m_settings.logoSmall()); + m_vars.insert(QLatin1String("WatermarkPixmap"), m_settings.watermark()); + + m_vars.insert(scRunProgram, replaceVariables(m_settings.runProgram())); + const QString desc = m_settings.runProgramDescription(); + if (!desc.isEmpty()) + m_vars.insert(scRunProgramDescription, desc); +#ifdef Q_WS_X11 + if (m_launchedAsRoot) + m_vars.insert(scTargetDir, replaceVariables(m_settings.adminTargetDir())); + else +#endif + m_vars.insert(scTargetDir, replaceVariables(m_settings.targetDir())); + m_vars.insert(scRemoveTargetDir, replaceVariables(m_settings.removeTargetDir())); + + QSettingsWrapper creatorSettings(QSettingsWrapper::IniFormat, QSettingsWrapper::UserScope, + QLatin1String("Nokia"), QLatin1String("QtCreator")); + QFileInfo info(creatorSettings.fileName()); + if (info.exists()) { + m_vars.insert(QLatin1String("QtCreatorSettingsFile"), info.absoluteFilePath()); + QDir settingsDirectory = info.absoluteDir(); + if (settingsDirectory.exists(QLatin1String("qtversion.xml"))) { + m_vars.insert(QLatin1String("QtCreatorSettingsQtVersionFile"), + settingsDirectory.absoluteFilePath(QLatin1String("qtversion.xml"))); + } + if (settingsDirectory.exists(QLatin1String("toolChains.xml"))) { + m_vars.insert(QLatin1String("QtCreatorSettingsToolchainsFile"), + settingsDirectory.absoluteFilePath(QLatin1String("toolChains.xml"))); + } + } + + if (!m_core->isInstaller()) { +#ifdef Q_WS_MAC + readMaintenanceConfigFiles(QCoreApplication::applicationDirPath() + QLatin1String("/../../..")); +#else + readMaintenanceConfigFiles(QCoreApplication::applicationDirPath()); +#endif + } + + foreach (Operation *currentOperation, m_performedOperationsOld) + currentOperation->setValue(QLatin1String("installer"), QVariant::fromValue(m_core)); + + disconnect(this, SIGNAL(installationStarted()), ProgressCoordinator::instance(), SLOT(reset())); + connect(this, SIGNAL(installationStarted()), ProgressCoordinator::instance(), SLOT(reset())); + disconnect(this, SIGNAL(uninstallationStarted()), ProgressCoordinator::instance(), SLOT(reset())); + connect(this, SIGNAL(uninstallationStarted()), ProgressCoordinator::instance(), SLOT(reset())); + + m_updaterApplication.updateSourcesInfo()->setFileName(QString()); + KDUpdater::PackagesInfo &packagesInfo = *m_updaterApplication.packagesInfo(); + packagesInfo.setFileName(componentsXmlPath()); + if (packagesInfo.applicationName().isEmpty()) + packagesInfo.setApplicationName(m_settings.applicationName()); + if (packagesInfo.applicationVersion().isEmpty()) + packagesInfo.setApplicationVersion(m_settings.applicationVersion()); + + if (isInstaller()) { + m_updaterApplication.addUpdateSource(m_settings.applicationName(), m_settings.applicationName(), + QString(), QUrl(QLatin1String("resource://metadata/")), 0); + m_updaterApplication.updateSourcesInfo()->setModified(false); + } + + if (!m_repoMetaInfoJob) { + m_repoMetaInfoJob = new GetRepositoriesMetaInfoJob(this); + m_repoMetaInfoJob->setAutoDelete(false); + connect(m_repoMetaInfoJob, SIGNAL(infoMessage(KDJob*, QString)), this, SLOT(infoMessage(KDJob*, + QString))); + } + KDUpdater::FileDownloaderFactory::instance().setProxyFactory(m_core->proxyFactory()); +} + +bool PackageManagerCorePrivate::isOfflineOnly() const +{ + if (!isInstaller()) + return false; + + QSettingsWrapper confInternal(QLatin1String(":/config/config-internal.ini"), QSettingsWrapper::IniFormat); + return confInternal.value(QLatin1String("offlineOnly"), false).toBool(); +} + +QString PackageManagerCorePrivate::installerBinaryPath() const +{ + return qApp->applicationFilePath(); +} + +bool PackageManagerCorePrivate::isInstaller() const +{ + return m_magicBinaryMarker == MagicInstallerMarker; +} + +bool PackageManagerCorePrivate::isUninstaller() const +{ + return m_magicBinaryMarker == MagicUninstallerMarker; +} + +bool PackageManagerCorePrivate::isUpdater() const +{ + return m_magicBinaryMarker == MagicUpdaterMarker; +} + +bool PackageManagerCorePrivate::isPackageManager() const +{ + return m_magicBinaryMarker == MagicPackageManagerMarker; +} + +bool PackageManagerCorePrivate::statusCanceledOrFailed() const +{ + return m_status == PackageManagerCore::Canceled || m_status == PackageManagerCore::Failure; +} + +void PackageManagerCorePrivate::setStatus(int status, const QString &error) +{ + m_error = error; + if (!error.isEmpty()) + qDebug() << m_error; + if (m_status != status) { + m_status = status; + emit m_core->statusChanged(PackageManagerCore::Status(m_status)); + } +} + +QString PackageManagerCorePrivate::replaceVariables(const QString &str) const +{ + static const QChar at = QLatin1Char('@'); + QString res; + int pos = 0; + while (true) { + const int pos1 = str.indexOf(at, pos); + if (pos1 == -1) + break; + const int pos2 = str.indexOf(at, pos1 + 1); + if (pos2 == -1) + break; + res += str.mid(pos, pos1 - pos); + const QString name = str.mid(pos1 + 1, pos2 - pos1 - 1); + res += m_core->value(name); + pos = pos2 + 1; + } + res += str.mid(pos); + return res; +} + +QByteArray PackageManagerCorePrivate::replaceVariables(const QByteArray &ba) const +{ + static const QChar at = QLatin1Char('@'); + QByteArray res; + int pos = 0; + while (true) { + const int pos1 = ba.indexOf(at, pos); + if (pos1 == -1) + break; + const int pos2 = ba.indexOf(at, pos1 + 1); + if (pos2 == -1) + break; + res += ba.mid(pos, pos1 - pos); + const QString name = QString::fromLocal8Bit(ba.mid(pos1 + 1, pos2 - pos1 - 1)); + res += m_core->value(name).toLocal8Bit(); + pos = pos2 + 1; + } + res += ba.mid(pos); + return res; +} + +/*! + \internal + Creates an update operation owned by the installer, not by any component. + */ +Operation *PackageManagerCorePrivate::createOwnedOperation(const QString &type) +{ + m_ownedOperations.append(KDUpdater::UpdateOperationFactory::instance().create(type)); + return m_ownedOperations.last(); +} + +/*! + \internal + Removes \a operation from the operations owned by the installer, returns the very same operation if the + operation was found, otherwise 0. + */ +Operation *PackageManagerCorePrivate::takeOwnedOperation(Operation *operation) +{ + if (!m_ownedOperations.contains(operation)) + return 0; + + m_ownedOperations.removeAll(operation); + return operation; +} + +QString PackageManagerCorePrivate::uninstallerName() const +{ + QString filename = m_settings.uninstallerName(); +#if defined(Q_WS_MAC) + if (QFileInfo(QCoreApplication::applicationDirPath() + QLatin1String("/../..")).isBundle()) + filename += QLatin1String(".app/Contents/MacOS/") + filename; +#elif defined(Q_OS_WIN) + filename += QLatin1String(".exe"); +#endif + return QString::fromLatin1("%1/%2").arg(targetDir()).arg(filename); +} + +static QNetworkProxy readProxy(QXmlStreamReader &reader) +{ + QNetworkProxy proxy(QNetworkProxy::HttpProxy); + while (reader.readNextStartElement()) { + if (reader.name() == QLatin1String("Host")) + proxy.setHostName(reader.readElementText()); + else if (reader.name() == QLatin1String("Port")) + proxy.setPort(reader.readElementText().toInt()); + else if (reader.name() == QLatin1String("Username")) + proxy.setUser(reader.readElementText()); + else if (reader.name() == QLatin1String("Password")) + proxy.setPassword(reader.readElementText()); + else + reader.skipCurrentElement(); + } + return proxy; +} + +static QSet<Repository> readRepositories(QXmlStreamReader &reader, bool isDefault) +{ + QSet<Repository> set; + while (reader.readNextStartElement()) { + if (reader.name() == QLatin1String("Repository")) { + Repository repo(QString(), isDefault); + while (reader.readNextStartElement()) { + if (reader.name() == QLatin1String("Host")) + repo.setUrl(reader.readElementText()); + else if (reader.name() == QLatin1String("Username")) + repo.setUsername(reader.readElementText()); + else if (reader.name() == QLatin1String("Password")) + repo.setPassword(reader.readElementText()); + else if (reader.name() == QLatin1String("Enabled")) + repo.setEnabled(bool(reader.readElementText().toInt())); + else + reader.skipCurrentElement(); + } + set.insert(repo); + } else { + reader.skipCurrentElement(); + } + } + return set; +} + +void PackageManagerCorePrivate::writeMaintenanceConfigFiles() +{ + // write current state (variables) to the uninstaller ini file + const QString iniPath = targetDir() + QLatin1Char('/') + m_settings.uninstallerIniFile(); + + QVariantHash vars; + QSettingsWrapper cfg(iniPath, QSettingsWrapper::IniFormat); + foreach (const QString &key, m_vars.keys()) { + if (key != scRunProgramDescription && key != scRunProgram) + vars.insert(key, m_vars.value(key)); + } + cfg.setValue(QLatin1String("Variables"), vars); + + QVariantList repos; + foreach (const Repository &repo, m_settings.defaultRepositories()) + repos.append(QVariant().fromValue(repo)); + cfg.setValue(QLatin1String("DefaultRepositories"), repos); + + cfg.sync(); + if (cfg.status() != QSettingsWrapper::NoError) { + const QString reason = cfg.status() == QSettingsWrapper::AccessError ? tr("Access error") + : tr("Format error"); + throw Error(tr("Could not write installer configuration to %1: %2").arg(iniPath, reason)); + } + + QFile file(targetDir() + QLatin1Char('/') + QLatin1String("network.xml")); + if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + QXmlStreamWriter writer(&file); + writer.setCodec("UTF-8"); + writer.setAutoFormatting(true); + writer.writeStartDocument(); + + writer.writeStartElement(QLatin1String("Network")); + writer.writeTextElement(QLatin1String("ProxyType"), QString::number(m_settings.proxyType())); + writer.writeStartElement(QLatin1String("Ftp")); + const QNetworkProxy &ftpProxy = m_settings.ftpProxy(); + writer.writeTextElement(QLatin1String("Host"), ftpProxy.hostName()); + writer.writeTextElement(QLatin1String("Port"), QString::number(ftpProxy.port())); + writer.writeTextElement(QLatin1String("Username"), ftpProxy.user()); + writer.writeTextElement(QLatin1String("Password"), ftpProxy.password()); + writer.writeEndElement(); + writer.writeStartElement(QLatin1String("Http")); + const QNetworkProxy &httpProxy = m_settings.httpProxy(); + writer.writeTextElement(QLatin1String("Host"), httpProxy.hostName()); + writer.writeTextElement(QLatin1String("Port"), QString::number(httpProxy.port())); + writer.writeTextElement(QLatin1String("Username"), httpProxy.user()); + writer.writeTextElement(QLatin1String("Password"), httpProxy.password()); + writer.writeEndElement(); + + writer.writeStartElement(QLatin1String("Repositories")); + foreach (const Repository &repo, m_settings.userRepositories()) { + writer.writeStartElement(QLatin1String("Repository")); + writer.writeTextElement(QLatin1String("Host"), repo.url().toString()); + writer.writeTextElement(QLatin1String("Username"), repo.username()); + writer.writeTextElement(QLatin1String("Password"), repo.password()); + writer.writeTextElement(QLatin1String("Enabled"), QString::number(repo.isEnabled())); + writer.writeEndElement(); + } + writer.writeEndElement(); + writer.writeEndElement(); + } +} + +void PackageManagerCorePrivate::readMaintenanceConfigFiles(const QString &targetDir) +{ + QSettingsWrapper cfg(targetDir + QLatin1Char('/') + m_settings.uninstallerIniFile(), + QSettingsWrapper::IniFormat); + const QVariantHash vars = cfg.value(QLatin1String("Variables")).toHash(); + for (QHash<QString, QVariant>::ConstIterator it = vars.constBegin(); it != vars.constEnd(); ++it) + m_vars.insert(it.key(), it.value().toString()); + + QSet<Repository> repos; + const QVariantList variants = cfg.value(QLatin1String("DefaultRepositories")).toList(); + foreach (const QVariant &variant, variants) + repos.insert(variant.value<Repository>()); + if (!repos.isEmpty()) + m_settings.setDefaultRepositories(repos); + + QFile file(targetDir + QLatin1String("/network.xml")); + if (!file.open(QIODevice::ReadOnly)) + return; + + QXmlStreamReader reader(&file); + while (!reader.atEnd()) { + switch (reader.readNext()) { + case QXmlStreamReader::StartElement: { + if (reader.name() == QLatin1String("Network")) { + while (reader.readNextStartElement()) { + const QStringRef name = reader.name(); + if (name == QLatin1String("Ftp")) { + m_settings.setFtpProxy(readProxy(reader)); + } else if (name == QLatin1String("Http")) { + m_settings.setHttpProxy(readProxy(reader)); + } else if (reader.name() == QLatin1String("Repositories")) { + m_settings.addUserRepositories(readRepositories(reader, false)); + } else if (name == QLatin1String("ProxyType")) { + m_settings.setProxyType(Settings::ProxyType(reader.readElementText().toInt())); + } else { + reader.skipCurrentElement(); + } + } + } + } break; + + case QXmlStreamReader::Invalid: { + qDebug() << reader.errorString(); + } break; + + default: + break; + } + } +} + +void PackageManagerCorePrivate::callBeginInstallation(const QList<Component*> &componentList) +{ + foreach (Component *component, componentList) + component->beginInstallation(); +} + +void PackageManagerCorePrivate::stopProcessesForUpdates(const QList<Component*> &components) +{ + QStringList processList; + foreach (const Component *component, components) + processList << m_core->replaceVariables(component->stopProcessForUpdateRequests()); + + qSort(processList); + processList.erase(std::unique(processList.begin(), processList.end()), processList.end()); + if (processList.isEmpty()) + return; + + while (true) { + const QList<ProcessInfo> allProcesses = runningProcesses(); // FIXME: Unused? + const QStringList processes = checkRunningProcessesFromList(processList); + if (processes.isEmpty()) + return; + + const QMessageBox::StandardButton button = + MessageBoxHandler::warning(MessageBoxHandler::currentBestSuitParent(), + QLatin1String("stopProcessesForUpdates"), tr("Stop Processes"), tr("These processes " + "should be stopped to continue:\n\n%1").arg(QDir::toNativeSeparators(processes + .join(QLatin1String("\n")))), QMessageBox::Retry | QMessageBox::Ignore + | QMessageBox::Cancel, QMessageBox::Retry); + if (button == QMessageBox::Ignore) + return; + if (button == QMessageBox::Cancel) { + m_core->setCanceled(); + throw Error(tr("Installation canceled by user")); + } + } +} + +int PackageManagerCorePrivate::countProgressOperations(const OperationList &operations) +{ + int operationCount = 0; + foreach (Operation *operation, operations) { + if (QObject *operationObject = dynamic_cast<QObject*> (operation)) { + const QMetaObject *const mo = operationObject->metaObject(); + if (mo->indexOfSignal(QMetaObject::normalizedSignature("progressChanged(double)")) > -1) + operationCount++; + } + } + return operationCount; +} + +int PackageManagerCorePrivate::countProgressOperations(const QList<Component*> &components) +{ + int operationCount = 0; + foreach (Component *component, components) + operationCount += countProgressOperations(component->operations()); + + return operationCount; +} + +void PackageManagerCorePrivate::connectOperationToInstaller(Operation *const operation, double operationPartSize) +{ + Q_ASSERT(operationPartSize); + QObject *const operationObject = dynamic_cast< QObject*> (operation); + if (operationObject != 0) { + const QMetaObject *const mo = operationObject->metaObject(); + if (mo->indexOfSignal(QMetaObject::normalizedSignature("outputTextChanged(QString)")) > -1) { + connect(operationObject, SIGNAL(outputTextChanged(QString)), ProgressCoordinator::instance(), + SLOT(emitDetailTextChanged(QString))); + } + + if (mo->indexOfSlot(QMetaObject::normalizedSignature("cancelOperation()")) > -1) + connect(m_core, SIGNAL(installationInterrupted()), operationObject, SLOT(cancelOperation())); + + if (mo->indexOfSignal(QMetaObject::normalizedSignature("progressChanged(double)")) > -1) { + ProgressCoordinator::instance()->registerPartProgress(operationObject, + SIGNAL(progressChanged(double)), operationPartSize); + } + } +} + +Operation *PackageManagerCorePrivate::createPathOperation(const QFileInfo &fileInfo, + const QString &componentName) +{ + const bool isDir = fileInfo.isDir(); + // create an operation with the dir/ file as target, it will get deleted on undo + Operation *operation = createOwnedOperation(QLatin1String(isDir ? "Mkdir" : "Copy")); + if (isDir) + operation->setValue(QLatin1String("createddir"), fileInfo.absoluteFilePath()); + operation->setValue(QLatin1String("component"), componentName); + operation->setArguments(isDir ? QStringList() << fileInfo.absoluteFilePath() + : QStringList() << QString() << fileInfo.absoluteFilePath()); + return operation; +} + +/*! + This creates fake operations which remove stuff which was registered for uninstallation afterwards +*/ +void PackageManagerCorePrivate::registerPathesForUninstallation( + const QList<QPair<QString, bool> > &pathesForUninstallation, const QString &componentName) +{ + if (pathesForUninstallation.isEmpty()) + return; + + QList<QPair<QString, bool> >::const_iterator it; + for (it = pathesForUninstallation.begin(); it != pathesForUninstallation.end(); ++it) { + const bool wipe = it->second; + const QString path = replaceVariables(it->first); + + const QFileInfo fi(path); + // create a copy operation with the file as target -> it will get deleted on undo + Operation *op = createPathOperation(fi, componentName); + if (fi.isDir()) + op->setValue(QLatin1String("forceremoval"), wipe ? scTrue : scFalse); + addPerformed(takeOwnedOperation(op)); + + // get recursive afterwards + if (fi.isDir() && !wipe) { + QDirIterator dirIt(path, QDir::Hidden | QDir::AllEntries | QDir::System + | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); + while (dirIt.hasNext()) { + dirIt.next(); + op = createPathOperation(dirIt.fileInfo(), componentName); + addPerformed(takeOwnedOperation(op)); + } + } + } +} + +void PackageManagerCorePrivate::writeUninstallerBinary(QFile *const input, qint64 size, bool writeBinaryLayout) +{ + QString uninstallerRenamedName = uninstallerName() + QLatin1String(".new"); + qDebug() << "Writing uninstaller:" << uninstallerRenamedName; + + KDSaveFile out(uninstallerRenamedName); + openForWrite(&out, out.fileName()); // throws an exception in case of error + + if (!input->seek(0)) + throw Error(QObject::tr("Failed to seek in file %1: %2").arg(input->fileName(), input->errorString())); + + appendData(&out, input, size); + if (writeBinaryLayout) { + appendInt64(&out, 0); // resource count + appendInt64(&out, 4 * sizeof(qint64)); // data block size + appendInt64(&out, QInstaller::MagicUninstallerMarker); + appendInt64(&out, QInstaller::MagicCookie); + } + out.setPermissions(out.permissions() | QFile::WriteUser | QFile::ReadGroup | QFile::ReadOther + | QFile::ExeOther | QFile::ExeGroup | QFile::ExeUser); + + if (!out.commit(KDSaveFile::OverwriteExistingFile)) + throw Error(tr("Could not write uninstaller to %1: %2").arg(out.fileName(), out.errorString())); +} + +void PackageManagerCorePrivate::writeUninstallerBinaryData(QIODevice *output, QFile *const input, + const OperationList &performedOperations, const BinaryLayout &layout) +{ + const qint64 dataBlockStart = output->pos(); + + QVector<Range<qint64> >resourceSegments; + foreach (const Range<qint64> &segment, layout.metadataResourceSegments) { + input->seek(segment.start()); + resourceSegments.append(Range<qint64>::fromStartAndLength(output->pos(), segment.length())); + appendData(output, input, segment.length()); + } + + const qint64 operationsStart = output->pos(); + appendInt64(output, performedOperations.count()); + foreach (Operation *operation, performedOperations) { + // the installer can't be put into XML, remove it first + operation->clearValue(QLatin1String("installer")); + + appendString(output, operation->name()); + appendString(output, operation->toXml().toString()); + + // for the ui not to get blocked + qApp->processEvents(); + } + appendInt64(output, performedOperations.count()); + const qint64 operationsEnd = output->pos(); + + // we don't save any component-indexes. + const qint64 numComponents = 0; + appendInt64(output, numComponents); // for the indexes + // we don't save any components. + const qint64 compIndexStart = output->pos(); + appendInt64(output, numComponents); // and 2 times number of components, + appendInt64(output, numComponents); // one before and one after the components + const qint64 compIndexEnd = output->pos(); + + appendInt64Range(output, Range<qint64>::fromStartAndEnd(compIndexStart, compIndexEnd) + .moved(-dataBlockStart)); + foreach (const Range<qint64> segment, resourceSegments) + appendInt64Range(output, segment.moved(-dataBlockStart)); + appendInt64Range(output, Range<qint64>::fromStartAndEnd(operationsStart, operationsEnd) + .moved(-dataBlockStart)); + appendInt64(output, layout.resourceCount); + //data block size, from end of .exe to end of file + appendInt64(output, output->pos() + 3 * sizeof(qint64) - dataBlockStart); + appendInt64(output, MagicUninstallerMarker); +} + +void PackageManagerCorePrivate::writeUninstaller(OperationList performedOperations) +{ + bool gainedAdminRights = false; + QTemporaryFile tempAdminFile(targetDir() + QLatin1String("/testjsfdjlkdsjflkdsjfldsjlfds") + + QString::number(qrand() % 1000)); + if (!tempAdminFile.open() || !tempAdminFile.isWritable()) { + m_core->gainAdminRights(); + gainedAdminRights = true; + } + + const QString targetAppDirPath = QFileInfo(uninstallerName()).path(); + if (!QDir().exists(targetAppDirPath)) { + // create the directory containing the uninstaller (like a bundle structor, on Mac...) + Operation *op = createOwnedOperation(QLatin1String("Mkdir")); + op->setArguments(QStringList() << targetAppDirPath); + performOperationThreaded(op, Backup); + performOperationThreaded(op); + performedOperations.append(takeOwnedOperation(op)); + } + + writeMaintenanceConfigFiles(); + +#ifdef Q_WS_MAC + // if it is a bundle, we need some stuff in it... + const QString sourceAppDirPath = QCoreApplication::applicationDirPath(); + if (isInstaller() && QFileInfo(sourceAppDirPath + QLatin1String("/../..")).isBundle()) { + Operation *op = createOwnedOperation(QLatin1String("Copy")); + op->setArguments(QStringList() << (sourceAppDirPath + QLatin1String("/../PkgInfo")) + << (targetAppDirPath + QLatin1String("/../PkgInfo"))); + performOperationThreaded(op, Backup); + performOperationThreaded(op); + + // copy Info.plist to target directory + op = createOwnedOperation(QLatin1String("Copy")); + op->setArguments(QStringList() << (sourceAppDirPath + QLatin1String("/../Info.plist")) + << (targetAppDirPath + QLatin1String("/../Info.plist"))); + performOperationThreaded(op, Backup); + performOperationThreaded(op); + + // patch the Info.plist after copying it + QFile sourcePlist(sourceAppDirPath + QLatin1String("/../Info.plist")); + openForRead(&sourcePlist, sourcePlist.fileName()); + QFile targetPlist(targetAppDirPath + QLatin1String("/../Info.plist")); + openForWrite(&targetPlist, targetPlist.fileName()); + + QTextStream in(&sourcePlist); + QTextStream out(&targetPlist); + const QString before = QLatin1String("<string>") + QFileInfo(QCoreApplication::applicationFilePath()) + .baseName() + QLatin1String("</string>"); + const QString after = QLatin1String("<string>") + QFileInfo(uninstallerName()).baseName() + + QLatin1String("</string>"); + while (!in.atEnd()) + out << in.readLine().replace(before, after) << endl; + + // copy qt_menu.nib if it exists + op = createOwnedOperation(QLatin1String("Mkdir")); + op->setArguments(QStringList() << (targetAppDirPath + QLatin1String("/../Resources/qt_menu.nib"))); + if (!op->performOperation()) { + qDebug() << "ERROR in Mkdir operation:" << op->errorString(); + } + + op = createOwnedOperation(QLatin1String("CopyDirectory")); + op->setArguments(QStringList() << (sourceAppDirPath + QLatin1String("/../Resources/qt_menu.nib")) + << (targetAppDirPath + QLatin1String("/../Resources/qt_menu.nib"))); + performOperationThreaded(op); + + op = createOwnedOperation(QLatin1String("Mkdir")); + op->setArguments(QStringList() << (QFileInfo(targetAppDirPath).path() + QLatin1String("/Resources"))); + performOperationThreaded(op, Backup); + performOperationThreaded(op); + + // copy application icons if it exists + const QString icon = QFileInfo(QCoreApplication::applicationFilePath()).baseName() + + QLatin1String(".icns"); + op = createOwnedOperation(QLatin1String("Copy")); + op->setArguments(QStringList() << (sourceAppDirPath + QLatin1String("/../Resources/") + icon) + << (targetAppDirPath + QLatin1String("/../Resources/") + icon)); + performOperationThreaded(op, Backup); + performOperationThreaded(op); + + // finally, copy everything within Frameworks and plugins + op = createOwnedOperation(QLatin1String("CopyDirectory")); + op->setArguments(QStringList() << (sourceAppDirPath + QLatin1String("/../Frameworks")) + << (targetAppDirPath + QLatin1String("/../Frameworks"))); + performOperationThreaded(op); + + op = createOwnedOperation(QLatin1String("CopyDirectory")); + op->setArguments(QStringList() << (sourceAppDirPath + QLatin1String("/../plugins")) + << (targetAppDirPath + QLatin1String("/../plugins"))); + performOperationThreaded(op); + } +#endif + + try { + // 1 - check if we have a installer base replacement + // |--- if so, write out the new tool and remove the replacement + // |--- remember to restart and that we need to replace the original binary + // + // 2 - if we do not have a replacement, try to open the binary data file as input + // |--- try to read the binary layout + // |--- on error (see 2.1) + // |--- remember we might to append uncompressed resource data (see 3) + // |--- set the installer or maintenance binary as input to take over binary data + // |--- in case we did not have a replacement, write out an new maintenance tool binary + // |--- remember that we need to replace the original binary + // + // 3 - open a new binary data file + // |--- try to write out the binary data based on the loaded input file (see 2) + // |--- on error (see 3.1) + // |--- if we wrote a new maintenance tool, take this as output - if not, write out a new + // one and set it as output file, remember we did this + // |--- append the binary data based on the loaded input file (see 2), make sure we force + // uncompressing the resource section if we read from a binary data file (see 4.1). + // + // 4 - force a deferred rename on the .dat file (see 4.1) + // 5 - force a deferred rename on the maintenance file (see 5.1) + + // 2.1 - Error cases are: no data file (in fact we are the installer or an old installation), + // could not find the data file magic cookie (unknown .dat file), failed to read binary + // layout (mostly likely the resource section or we couldn't seek inside the file) + // + // 3.1 - most likely the commit operation will fail + // 4.1 - if 3 failed, this makes sure the .dat file will get removed and on the next run all + // binary data is read from the maintenance tool, otherwise it get replaced be the new one + // 5.1 - this will only happen -if- we wrote out a new binary + + bool newBinaryWritten = false; + bool replacementExists = false; + const QString installerBaseBinary = m_core->replaceVariables(m_installerBaseBinaryUnreplaced); + if (!installerBaseBinary.isEmpty() && QFileInfo(installerBaseBinary).exists()) { + qDebug() << "Got a replacement installer base binary:" << installerBaseBinary; + + QFile replacementBinary(installerBaseBinary); + try { + openForRead(&replacementBinary, replacementBinary.fileName()); + writeUninstallerBinary(&replacementBinary, replacementBinary.size(), true); + + m_forceRestart = true; + newBinaryWritten = true; + replacementExists = true; + } catch (const Error &error) { + qDebug() << error.message(); + } + + if (!replacementBinary.remove()) { + // Is there anything more sensible we can do with this error? I think not. It's not serious + // enough for throwing/ aborting the process. + qDebug() << QString::fromLatin1("Could not remove installer base binary (%1) after updating " + "the uninstaller: %2").arg(installerBaseBinary, replacementBinary.errorString()); + } + m_installerBaseBinaryUnreplaced.clear(); + } + + QFile input; + BinaryLayout layout; + const QString dataFile = targetDir() + QLatin1Char('/') + m_settings.uninstallerName() + + QLatin1String(".dat"); + try { + if (isInstaller()) { + throw Error(tr("Found a binary data file, but we are the installer and we should read the " + "binary resource from our very own binary!")); + } + input.setFileName(dataFile); + openForRead(&input, input.fileName()); + layout = BinaryContent::readBinaryLayout(&input, findMagicCookie(&input, MagicCookieDat)); + } catch (const Error &/*error*/) { + input.setFileName(isInstaller() ? installerBinaryPath() : uninstallerName()); + openForRead(&input, input.fileName()); + layout = BinaryContent::readBinaryLayout(&input, findMagicCookie(&input, MagicCookie)); + if (!newBinaryWritten) { + newBinaryWritten = true; + writeUninstallerBinary(&input, layout.endOfData - layout.dataBlockSize, true); + } + } + + try { + KDSaveFile file(dataFile + QLatin1String(".new")); + openForWrite(&file, file.fileName()); + writeUninstallerBinaryData(&file, &input, performedOperations, layout); + appendInt64(&file, MagicCookieDat); + file.setPermissions(file.permissions() | QFile::WriteUser | QFile::ReadGroup + | QFile::ReadOther); + if (!file.commit(KDSaveFile::OverwriteExistingFile)) { + throw Error(tr("Could not write uninstaller binary data to %1: %2").arg(file.fileName(), + file.errorString())); + } + } catch (const Error &/*error*/) { + if (!newBinaryWritten) { + newBinaryWritten = true; + QFile tmp(isInstaller() ? installerBinaryPath() : uninstallerName()); + openForRead(&tmp, tmp.fileName()); + BinaryLayout tmpLayout = BinaryContent::readBinaryLayout(&tmp, findMagicCookie(&tmp, MagicCookie)); + writeUninstallerBinary(&tmp, tmpLayout.endOfData - tmpLayout.dataBlockSize, false); + } + + QFile file(uninstallerName() + QLatin1String(".new")); + openForAppend(&file, file.fileName()); + file.seek(file.size()); + writeUninstallerBinaryData(&file, &input, performedOperations, layout); + appendInt64(&file, MagicCookie); + } + input.close(); + deferredRename(dataFile + QLatin1String(".new"), dataFile, false); + + if (newBinaryWritten) { + const bool restart = replacementExists && isUpdater() && (!statusCanceledOrFailed()); + deferredRename(uninstallerName() + QLatin1String(".new"), uninstallerName(), restart); + qDebug() << "Maintenance tool restart:" << (restart ? "true." : "false."); + } + } catch (const Error &err) { + setStatus(PackageManagerCore::Failure); + if (gainedAdminRights) + m_core->dropAdminRights(); + m_needToWriteUninstaller = false; + throw err; + } + + if (gainedAdminRights) + m_core->dropAdminRights(); + + commitSessionOperations(); + + m_needToWriteUninstaller = false; +} + +QString PackageManagerCorePrivate::registerPath() const +{ +#ifdef Q_OS_WIN + QString productName = m_vars.value(QLatin1String("ProductName")); + if (productName.isEmpty()) + throw Error(tr("ProductName should be set")); + + QString path = QLatin1String("HKEY_CURRENT_USER"); + if (m_vars.value(QLatin1String("AllUsers")) == scTrue) + path = QLatin1String("HKEY_LOCAL_MACHINE"); + + return path + QLatin1String("\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\") + + productName; +#endif + return QString(); +} + +void PackageManagerCorePrivate::runInstaller() +{ + bool adminRightsGained = false; + try { + setStatus(PackageManagerCore::Running); + emit installationStarted(); //resets also the ProgressCoordninator + + //to have some progress for writeUninstaller + ProgressCoordinator::instance()->addReservePercentagePoints(1); + + static const QLatin1String sep("/"); + const QString target = QDir::cleanPath(targetDir().replace(QRegExp(QLatin1String("\\\\|/")), sep)); + if (target.isEmpty()) + throw Error(tr("Variable 'TargetDir' not set.")); + + if (!QDir(target).exists()) { + const QString &pathToTarget = target.mid(0, target.lastIndexOf(sep)); + if (!QDir(pathToTarget).exists()) { + Operation *pathToTargetOp = createOwnedOperation(QLatin1String("Mkdir")); + pathToTargetOp->setArguments(QStringList() << pathToTarget); + if (!performOperationThreaded(pathToTargetOp)) { + adminRightsGained = m_core->gainAdminRights(); + if (!performOperationThreaded(pathToTargetOp)) + throw Error(pathToTargetOp->errorString()); + } + } + } else if (QDir(target).exists()) { + QTemporaryFile tempAdminFile(target + QLatin1String("/adminrights")); + if (!tempAdminFile.open() || !tempAdminFile.isWritable()) + adminRightsGained = m_core->gainAdminRights(); + } + + // add the operation to create the target directory + Operation *mkdirOp = createOwnedOperation(QLatin1String("Mkdir")); + mkdirOp->setArguments(QStringList() << target); + mkdirOp->setValue(QLatin1String("forceremoval"), true); + mkdirOp->setValue(QLatin1String("uninstall-only"), true); + + performOperationThreaded(mkdirOp, Backup); + if (!performOperationThreaded(mkdirOp)) { + // if we cannot create the target dir, we try to activate the admin rights + adminRightsGained = m_core->gainAdminRights(); + if (!performOperationThreaded(mkdirOp)) + throw Error(mkdirOp->errorString()); + } + const QString remove = m_core->value(scRemoveTargetDir); + if (QVariant(remove).toBool()) + addPerformed(takeOwnedOperation(mkdirOp)); + + // to show that there was some work + ProgressCoordinator::instance()->addManualPercentagePoints(1); + ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("Preparing the installation...")); + + const QList<Component*> componentsToInstall = m_core->orderedComponentsToInstall(); + qDebug() << "Install size:" << componentsToInstall.size() << "components"; + + if (!adminRightsGained) { + foreach (Component *component, m_core->orderedComponentsToInstall()) { + if (component->value(scRequiresAdminRights, scFalse) == scFalse) + continue; + + m_core->gainAdminRights(); + m_core->dropAdminRights(); + break; + } + } + + const double downloadPartProgressSize = double(1) / double(3); + double componentsInstallPartProgressSize = double(2) / double(3); + const int downloadedArchivesCount = m_core->downloadNeededArchives(downloadPartProgressSize); + + // if there was no download we have the whole progress for installing components + if (!downloadedArchivesCount) + componentsInstallPartProgressSize = double(1); + + // Force an update on the components xml as the install dir might have changed. + KDUpdater::PackagesInfo &info = *m_updaterApplication.packagesInfo(); + info.setFileName(componentsXmlPath()); + // Clear the packages as we might install into an already existing installation folder. + info.clearPackageInfoList(); + // also update the application name and version, might be set from a script as well + info.setApplicationName(m_core->value(QLatin1String("ProductName"), m_settings.applicationName())); + info.setApplicationVersion(m_core->value(QLatin1String("ProductVersion"), + m_settings.applicationVersion())); + + callBeginInstallation(componentsToInstall); + stopProcessesForUpdates(componentsToInstall); + + const int progressOperationCount = countProgressOperations(componentsToInstall) + + (m_createLocalRepositoryFromBinary ? 1 : 0); // add one more operation as we support progress + double progressOperationSize = componentsInstallPartProgressSize / progressOperationCount; + + foreach (Component *component, componentsToInstall) + installComponent(component, progressOperationSize, adminRightsGained); + + if (m_createLocalRepositoryFromBinary) { + emit m_core->titleMessageChanged(tr("Creating local repository")); + ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(QString()); + ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("Creating local repository")); + + Operation *createRepo = createOwnedOperation(QLatin1String("CreateLocalRepository")); + if (createRepo) { + createRepo->setValue(QLatin1String("uninstall-only"), true); + createRepo->setValue(QLatin1String("installer"), QVariant::fromValue(m_core)); + createRepo->setArguments(QStringList() << QCoreApplication::applicationFilePath() << target); + + connectOperationToInstaller(createRepo, progressOperationSize); + + bool success = performOperationThreaded(createRepo); + if (!success) { + adminRightsGained = m_core->gainAdminRights(); + success = performOperationThreaded(createRepo); + } + + if (success) { + QSet<Repository> repos; + foreach (Repository repo, m_settings.defaultRepositories()) { + repo.setEnabled(false); + repos.insert(repo); + } + repos.insert(Repository(QUrl::fromUserInput(createRepo + ->value(QLatin1String("local-repo")).toString()), true)); + m_settings.setDefaultRepositories(repos); + addPerformed(takeOwnedOperation(createRepo)); + } else { + MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), + QLatin1String("installationError"), tr("Error"), createRepo->errorString()); + createRepo->undoOperation(); + } + } + } + + emit m_core->titleMessageChanged(tr("Creating Uninstaller")); + + writeUninstaller(m_performedOperationsOld + m_performedOperationsCurrentSession); + registerUninstaller(); + + // fake a possible wrong value to show a full progress bar + const int progress = ProgressCoordinator::instance()->progressInPercentage(); + if (progress < 100) + ProgressCoordinator::instance()->addManualPercentagePoints(100 - progress); + ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("\nInstallation finished!")); + + if (adminRightsGained) + m_core->dropAdminRights(); + setStatus(PackageManagerCore::Success); + emit installationFinished(); + } catch (const Error &err) { + if (m_core->status() != PackageManagerCore::Canceled) { + setStatus(PackageManagerCore::Failure); + MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), + QLatin1String("installationError"), tr("Error"), err.message()); + qDebug() << "ROLLING BACK operations=" << m_performedOperationsCurrentSession.count(); + } + + m_core->rollBackInstallation(); + + ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("\nInstallation aborted!")); + if (adminRightsGained) + m_core->dropAdminRights(); + emit installationFinished(); + + throw; + } +} + +void PackageManagerCorePrivate::runPackageUpdater() +{ + bool adminRightsGained = false; + try { + if (m_completeUninstall) { + runUninstaller(); + return; + } + + setStatus(PackageManagerCore::Running); + emit installationStarted(); //resets also the ProgressCoordninator + + //to have some progress for the cleanup/write component.xml step + ProgressCoordinator::instance()->addReservePercentagePoints(1); + + const QString packagesXml = componentsXmlPath(); + // check if we need admin rights and ask before the action happens + if (!QFileInfo(installerBinaryPath()).isWritable() || !QFileInfo(packagesXml).isWritable()) + adminRightsGained = m_core->gainAdminRights(); + + const QList<Component *> componentsToInstall = m_core->orderedComponentsToInstall(); + qDebug() << "Install size:" << componentsToInstall.size() << "components"; + + bool updateAdminRights = false; + if (!adminRightsGained) { + foreach (Component *component, componentsToInstall) { + if (component->value(scRequiresAdminRights, scFalse) == scFalse) + continue; + + updateAdminRights = true; + break; + } + } + + OperationList undoOperations; + OperationList nonRevertedOperations; + QHash<QString, Component *> componentsByName; + + // build a list of undo operations based on the checked state of the component + foreach (Operation *operation, m_performedOperationsOld) { + const QString &name = operation->value(QLatin1String("component")).toString(); + Component *component = componentsByName.value(name, 0); + if (!component) + component = m_core->componentByName(name); + if (component) + componentsByName.insert(name, component); + + if (isUpdater()) { + // We found the component, the component is not scheduled for update, the dependency solver + // did not add the component as install dependency and there is no replacement, keep it. + if ((component && !component->updateRequested() && !componentsToInstall.contains(component) + && !m_componentsToReplaceUpdaterMode.contains(name))) { + nonRevertedOperations.append(operation); + continue; + } + + // There is a replacement, but the replacement is not scheduled for update, keep it as well. + if (m_componentsToReplaceUpdaterMode.contains(name) + && !m_componentsToReplaceUpdaterMode.value(name).first->updateRequested()) { + nonRevertedOperations.append(operation); + continue; + } + } else if (isPackageManager()) { + // We found the component, the component is still checked and the dependency solver did not + // add the component as install dependency, keep it. + if (component && component->isSelected() && !componentsToInstall.contains(component)) { + nonRevertedOperations.append(operation); + continue; + } + + // There is a replacement, but the replacement is not scheduled for update, keep it as well. + if (m_componentsToReplaceAllMode.contains(name) + && !m_componentsToReplaceAllMode.value(name).first->installationRequested()) { + nonRevertedOperations.append(operation); + continue; + } + } else { + Q_ASSERT_X(false, Q_FUNC_INFO, "Invalid package manager mode!"); + } + + // Filter out the create target dir undo operation, it's only needed for full uninstall. + // Note: We filter for unnamed operations as well, since old installations had the remove target + // dir operation without the "uninstall-only", which will result in an complete uninstallation + // during an update for the maintenance tool. + if (operation->value(QLatin1String("uninstall-only")).toBool() + || operation->value(QLatin1String("component")).toString().isEmpty()) { + nonRevertedOperations.append(operation); + continue; + } + + undoOperations.prepend(operation); + updateAdminRights |= operation->value(QLatin1String("admin")).toBool(); + } + + // we did not request admin rights till we found out that a component/ undo needs admin rights + if (updateAdminRights && !adminRightsGained) { + m_core->gainAdminRights(); + m_core->dropAdminRights(); + } + + double undoOperationProgressSize = 0; + const double downloadPartProgressSize = double(2) / double(5); + double componentsInstallPartProgressSize = double(3) / double(5); + if (undoOperations.count() > 0) { + undoOperationProgressSize = double(1) / double(5); + componentsInstallPartProgressSize = downloadPartProgressSize; + undoOperationProgressSize /= countProgressOperations(undoOperations); + } + + ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("Preparing the installation...")); + + // following, we download the needed archives + m_core->downloadNeededArchives(downloadPartProgressSize); + + if (undoOperations.count() > 0) { + ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("Removing deselected components...")); + runUndoOperations(undoOperations, undoOperationProgressSize, adminRightsGained, true); + } + m_performedOperationsOld = nonRevertedOperations; // these are all operations left: those not reverted + + callBeginInstallation(componentsToInstall); + stopProcessesForUpdates(componentsToInstall); + + const double progressOperationCount = countProgressOperations(componentsToInstall); + const double progressOperationSize = componentsInstallPartProgressSize / progressOperationCount; + + foreach (Component *component, componentsToInstall) + installComponent(component, progressOperationSize, adminRightsGained); + + emit m_core->titleMessageChanged(tr("Creating Uninstaller")); + + commitSessionOperations(); //end session, move ops to "old" + m_needToWriteUninstaller = true; + + // fake a possible wrong value to show a full progress bar + const int progress = ProgressCoordinator::instance()->progressInPercentage(); + if (progress < 100) + ProgressCoordinator::instance()->addManualPercentagePoints(100 - progress); + ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("\nUpdate finished!")); + + if (adminRightsGained) + m_core->dropAdminRights(); + setStatus(PackageManagerCore::Success); + emit installationFinished(); + } catch (const Error &err) { + if (m_core->status() != PackageManagerCore::Canceled) { + setStatus(PackageManagerCore::Failure); + MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), + QLatin1String("installationError"), tr("Error"), err.message()); + qDebug() << "ROLLING BACK operations=" << m_performedOperationsCurrentSession.count(); + } + + m_core->rollBackInstallation(); + + ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("\nUpdate aborted!")); + if (adminRightsGained) + m_core->dropAdminRights(); + emit installationFinished(); + + throw; + } +} + +void PackageManagerCorePrivate::runUninstaller() +{ + bool adminRightsGained = false; + try { + setStatus(PackageManagerCore::Running); + emit uninstallationStarted(); + + // check if we need administration rights and ask before the action happens + if (!QFileInfo(installerBinaryPath()).isWritable()) + adminRightsGained = m_core->gainAdminRights(); + + OperationList undoOperations; + bool updateAdminRights = false; + foreach (Operation *op, m_performedOperationsOld) { + undoOperations.prepend(op); + updateAdminRights |= op->value(QLatin1String("admin")).toBool(); + } + + // we did not request administration rights till we found out that a undo needs administration rights + if (updateAdminRights && !adminRightsGained) { + m_core->gainAdminRights(); + m_core->dropAdminRights(); + } + + const int uninstallOperationCount = countProgressOperations(undoOperations); + const double undoOperationProgressSize = double(1) / double(uninstallOperationCount); + + //yes uninstallation is like an update on the component so please inform the user to stop processes + callBeginInstallation(m_core->availableComponents()); + stopProcessesForUpdates(m_core->availableComponents()); + + runUndoOperations(undoOperations, undoOperationProgressSize, adminRightsGained, false); + // No operation delete here, as all old undo operations are deleted in the destructor. + + const QString startMenuDir = m_vars.value(scStartMenuDir); + if (!startMenuDir.isEmpty()) { + try { + QInstaller::removeDirectory(startMenuDir); + } catch (const Error &error) { + qDebug() << QString::fromLatin1("Could not remove %1: %2").arg(startMenuDir, error.message()); + } + } else { + qDebug() << "Start menu dir not set."; + } + + // this will also delete the TargetDir on Windows + deleteUninstaller(); + + if (QVariant(m_core->value(scRemoveTargetDir)).toBool()) { + // on !Windows, we need to remove TargetDir manually + qDebug() << "Complete uninstallation is chosen"; + const QString target = targetDir(); + if (!target.isEmpty()) { + if (updateAdminRights && !adminRightsGained) { + // we were root at least once, so we remove the target dir as root + m_core->gainAdminRights(); + removeDirectoryThreaded(target, true); + m_core->dropAdminRights(); + } else { + removeDirectoryThreaded(target, true); + } + } + } + + unregisterUninstaller(); + m_needToWriteUninstaller = false; + + setStatus(PackageManagerCore::Success); + ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("\nDeinstallation finished!")); + if (adminRightsGained) + m_core->dropAdminRights(); + emit uninstallationFinished(); + } catch (const Error &err) { + if (m_core->status() != PackageManagerCore::Canceled) { + setStatus(PackageManagerCore::Failure); + MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), + QLatin1String("installationError"), tr("Error"), err.message()); + } + + ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("\nDeinstallation aborted!")); + if (adminRightsGained) + m_core->dropAdminRights(); + emit uninstallationFinished(); + + throw; + } +} + +void PackageManagerCorePrivate::installComponent(Component *component, double progressOperationSize, + bool adminRightsGained) +{ + const OperationList operations = component->operations(); + if (!component->operationsCreatedSuccessfully()) + m_core->setCanceled(); + + const int opCount = operations.count(); + // show only components which do something, MinimumProgress is only for progress calculation safeness + if (opCount > 1 || (opCount == 1 && operations.at(0)->name() != QLatin1String("MinimumProgress"))) { + ProgressCoordinator::instance()->emitLabelAndDetailTextChanged(tr("\nInstalling component %1") + .arg(component->displayName())); + } + + foreach (Operation *operation, operations) { + if (statusCanceledOrFailed()) + throw Error(tr("Installation canceled by user")); + + // maybe this operations wants us to be admin... + bool becameAdmin = false; + if (!adminRightsGained && operation->value(QLatin1String("admin")).toBool()) { + becameAdmin = m_core->gainAdminRights(); + qDebug() << operation->name() << "as admin:" << becameAdmin; + } + + connectOperationToInstaller(operation, progressOperationSize); + connectOperationCallMethodRequest(operation); + + // allow the operation to backup stuff before performing the operation + PackageManagerCorePrivate::performOperationThreaded(operation, PackageManagerCorePrivate::Backup); + + bool ignoreError = false; + bool ok = PackageManagerCorePrivate::performOperationThreaded(operation); + while (!ok && !ignoreError && m_core->status() != PackageManagerCore::Canceled) { + qDebug() << QString::fromLatin1("Operation '%1' with arguments: '%2' failed: %3") + .arg(operation->name(), operation->arguments().join(QLatin1String("; ")), + operation->errorString()); + const QMessageBox::StandardButton button = + MessageBoxHandler::warning(MessageBoxHandler::currentBestSuitParent(), + QLatin1String("installationErrorWithRetry"), tr("Installer Error"), + tr("Error during installation process (%1):\n%2").arg(component->name(), + operation->errorString()), + QMessageBox::Retry | QMessageBox::Ignore | QMessageBox::Cancel, QMessageBox::Retry); + + if (button == QMessageBox::Retry) + ok = PackageManagerCorePrivate::performOperationThreaded(operation); + else if (button == QMessageBox::Ignore) + ignoreError = true; + else if (button == QMessageBox::Cancel) + m_core->interrupt(); + } + + if (ok || operation->error() > Operation::InvalidArguments) { + // Remember that the operation was performed, what allows us to undo it if a following operation + // fails or if this operation failed but still needs an undo call to cleanup. + addPerformed(operation); + operation->setValue(QLatin1String("component"), component->name()); + } + + if (becameAdmin) + m_core->dropAdminRights(); + + if (!ok && !ignoreError) + throw Error(operation->errorString()); + + if (component->value(scEssential, scFalse) == scTrue) + m_forceRestart = true; + } + + registerPathesForUninstallation(component->pathesForUninstallation(), component->name()); + + if (!component->stopProcessForUpdateRequests().isEmpty()) { + Operation *stopProcessForUpdatesOp = KDUpdater::UpdateOperationFactory::instance() + .create(QLatin1String("FakeStopProcessForUpdate")); + const QStringList arguments(component->stopProcessForUpdateRequests().join(QLatin1String(","))); + stopProcessForUpdatesOp->setArguments(arguments); + addPerformed(stopProcessForUpdatesOp); + stopProcessForUpdatesOp->setValue(QLatin1String("component"), component->name()); + } + + // now mark the component as installed + KDUpdater::PackagesInfo &packages = *m_updaterApplication.packagesInfo(); + packages.installPackage(component->name(), component->value(scVersion), component->value(scDisplayName), + component->value(scDescription), component->dependencies(), component->forcedInstallation(), + component->isVirtual(), component->value(scUncompressedSize).toULongLong(), + component->value(scInheritVersion)); + packages.writeToDisk(); + + component->setInstalled(); + component->markAsPerformedInstallation(); +} + +// -- private + +void PackageManagerCorePrivate::deleteUninstaller() +{ +#ifdef Q_OS_WIN + // Since Windows does not support that the uninstaller deletes itself we have to go with a rather dirty + // hack. What we do is to create a batchfile that will try to remove the uninstaller once per second. Then + // we start that batchfile detached, finished our job and close ourselves. Once that's done the batchfile + // will succeed in deleting our uninstall.exe and, if the installation directory was created but us and if + // it's empty after the uninstall, deletes the installation-directory. + const QString batchfile = QDir::toNativeSeparators(QFileInfo(QDir::tempPath(), + QLatin1String("uninstall.vbs")).absoluteFilePath()); + QFile f(batchfile); + if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) + throw Error(tr("Cannot prepare uninstall")); + + QTextStream batch(&f); + batch << "Set fso = WScript.CreateObject(\"Scripting.FileSystemObject\")\n"; + batch << "file = WScript.Arguments.Item(0)\n"; + batch << "folderpath = WScript.Arguments.Item(1)\n"; + batch << "Set folder = fso.GetFolder(folderpath)\n"; + batch << "on error resume next\n"; + + batch << "while fso.FileExists(file)\n"; + batch << " fso.DeleteFile(file)\n"; + batch << " WScript.Sleep(1000)\n"; + batch << "wend\n"; +// batch << "if folder.SubFolders.Count = 0 and folder.Files.Count = 0 then\n"; + batch << " Set folder = Nothing\n"; + batch << " fso.DeleteFolder folderpath, true\n"; +// batch << "end if\n"; + batch << "fso.DeleteFile(WScript.ScriptFullName)\n"; + + f.close(); + + QStringList arguments; + arguments << QLatin1String("//Nologo") << batchfile; // execute the batchfile + arguments << QDir::toNativeSeparators(QFileInfo(installerBinaryPath()).absoluteFilePath()); + if (!m_performedOperationsOld.isEmpty()) { + const Operation *const op = m_performedOperationsOld.first(); + if (op->name() == QLatin1String("Mkdir")) // the target directory name + arguments << QDir::toNativeSeparators(QFileInfo(op->arguments().first()).absoluteFilePath()); + } + + if (!QProcessWrapper::startDetached(QLatin1String("cscript"), arguments, QDir::rootPath())) + throw Error(tr("Cannot start uninstall")); +#else + // every other platform has no problem if we just delete ourselves now + QFile uninstaller(QFileInfo(installerBinaryPath()).absoluteFilePath()); + uninstaller.remove(); +# ifdef Q_WS_MAC + const QLatin1String cdUp("/../../.."); + if (QFileInfo(QFileInfo(installerBinaryPath() + cdUp).absoluteFilePath()).isBundle()) { + removeDirectoryThreaded(QFileInfo(installerBinaryPath() + cdUp).absoluteFilePath()); + QFile::remove(QFileInfo(installerBinaryPath() + cdUp).absolutePath() + + QLatin1String("/") + configurationFileName()); + } else +# endif +#endif + { + // finally remove the components.xml, since it still exists now + QFile::remove(QFileInfo(installerBinaryPath()).absolutePath() + QLatin1String("/") + + configurationFileName()); + } +} + +void PackageManagerCorePrivate::registerUninstaller() +{ +#ifdef Q_OS_WIN + QSettingsWrapper settings(registerPath(), QSettingsWrapper::NativeFormat); + settings.setValue(scDisplayName, m_vars.value(QLatin1String("ProductName"))); + settings.setValue(QLatin1String("DisplayVersion"), m_vars.value(QLatin1String("ProductVersion"))); + const QString uninstaller = QDir::toNativeSeparators(uninstallerName()); + settings.setValue(QLatin1String("DisplayIcon"), uninstaller); + settings.setValue(scPublisher, m_vars.value(scPublisher)); + settings.setValue(QLatin1String("UrlInfoAbout"), m_vars.value(QLatin1String("Url"))); + settings.setValue(QLatin1String("Comments"), m_vars.value(scTitle)); + settings.setValue(QLatin1String("InstallDate"), QDateTime::currentDateTime().toString()); + settings.setValue(QLatin1String("InstallLocation"), QDir::toNativeSeparators(targetDir())); + settings.setValue(QLatin1String("UninstallString"), uninstaller); + settings.setValue(QLatin1String("ModifyPath"), uninstaller + QLatin1String(" --manage-packages")); + settings.setValue(QLatin1String("EstimatedSize"), QFileInfo(installerBinaryPath()).size()); + settings.setValue(QLatin1String("NoModify"), 0); + settings.setValue(QLatin1String("NoRepair"), 1); +#endif +} + +void PackageManagerCorePrivate::unregisterUninstaller() +{ +#ifdef Q_OS_WIN + QSettingsWrapper settings(registerPath(), QSettingsWrapper::NativeFormat); + settings.remove(QString()); +#endif +} + +void PackageManagerCorePrivate::runUndoOperations(const OperationList &undoOperations, double progressSize, + bool adminRightsGained, bool deleteOperation) +{ + KDUpdater::PackagesInfo &packages = *m_updaterApplication.packagesInfo(); + try { + foreach (Operation *undoOperation, undoOperations) { + if (statusCanceledOrFailed()) + throw Error(tr("Installation canceled by user")); + + bool becameAdmin = false; + if (!adminRightsGained && undoOperation->value(QLatin1String("admin")).toBool()) + becameAdmin = m_core->gainAdminRights(); + + connectOperationToInstaller(undoOperation, progressSize); + qDebug() << "undo operation=" << undoOperation->name(); + performOperationThreaded(undoOperation, PackageManagerCorePrivate::Undo); + + const QString componentName = undoOperation->value(QLatin1String("component")).toString(); + if (undoOperation->error() != Operation::NoError) { + if (!componentName.isEmpty()) { + bool run = true; + while (run && m_core->status() != PackageManagerCore::Canceled) { + const QMessageBox::StandardButton button = + MessageBoxHandler::warning(MessageBoxHandler::currentBestSuitParent(), + QLatin1String("installationErrorWithRetry"), tr("Installer Error"), + tr("Error during uninstallation process:\n%1").arg(undoOperation->errorString()), + QMessageBox::Retry | QMessageBox::Ignore, QMessageBox::Retry); + + if (button == QMessageBox::Retry) { + performOperationThreaded(undoOperation, Undo); + if (undoOperation->error() == Operation::NoError) + run = false; + } else if (button == QMessageBox::Ignore) { + run = false; + } + } + } + } + + if (!componentName.isEmpty()) { + Component *component = m_core->componentByName(componentName); + if (!component) + component = componentsToReplace(m_core->runMode()).value(componentName).second; + if (component) { + component->setUninstalled(); + packages.removePackage(component->name()); + } + } + + if (becameAdmin) + m_core->dropAdminRights(); + + if (deleteOperation) + delete undoOperation; + } + } catch (const Error &error) { + packages.writeToDisk(); + throw Error(error.message()); + } catch (...) { + packages.writeToDisk(); + throw Error(tr("Unknown error")); + } + packages.writeToDisk(); +} + +PackagesList PackageManagerCorePrivate::remotePackages() +{ + if (m_updates && m_updateFinder) + return m_updateFinder->updates(); + + m_updates = false; + delete m_updateFinder; + + m_updateFinder = new KDUpdater::UpdateFinder(&m_updaterApplication); + m_updateFinder->setAutoDelete(false); + m_updateFinder->setUpdateType(KDUpdater::PackageUpdate | KDUpdater::NewPackage); + m_updateFinder->run(); + + if (m_updateFinder->updates().isEmpty()) { + setStatus(PackageManagerCore::Failure, tr("Could not retrieve remote tree: %1.") + .arg(m_updateFinder->errorString())); + return PackagesList(); + } + + m_updates = true; + return m_updateFinder->updates(); +} + +/*! + Returns a hash containing the installed package name and it's associated package information. If + the application is running in installer mode or the local components file could not be parsed, the + hash is empty. +*/ +LocalPackagesHash PackageManagerCorePrivate::localInstalledPackages() +{ + LocalPackagesHash installedPackages; + + if (!isInstaller()) { + KDUpdater::PackagesInfo &packagesInfo = *m_updaterApplication.packagesInfo(); + if (!packagesInfo.isValid()) { + packagesInfo.setFileName(componentsXmlPath()); + if (packagesInfo.applicationName().isEmpty()) + packagesInfo.setApplicationName(m_settings.applicationName()); + if (packagesInfo.applicationVersion().isEmpty()) + packagesInfo.setApplicationVersion(m_settings.applicationVersion()); + } + + if (packagesInfo.error() != KDUpdater::PackagesInfo::NoError) + setStatus(PackageManagerCore::Failure, tr("Failure to read packages from: %1.").arg(componentsXmlPath())); + + foreach (const LocalPackage &package, packagesInfo.packageInfos()) { + if (statusCanceledOrFailed()) + break; + installedPackages.insert(package.name, package); + } + } + + return installedPackages; +} + +bool PackageManagerCorePrivate::fetchMetaInformationFromRepositories() +{ + if (m_repoFetched) + return m_repoFetched; + + m_updates = false; + m_repoFetched = false; + m_updateSourcesAdded = false; + + m_repoMetaInfoJob->reset(); + try { + m_repoMetaInfoJob->start(); + m_repoMetaInfoJob->waitForFinished(); + } catch (Error &error) { + setStatus(PackageManagerCore::Failure, tr("Could not retrieve meta information: %1").arg(error.message())); + return m_repoFetched; + } + + if (m_repoMetaInfoJob->isCanceled() || m_repoMetaInfoJob->error() != KDJob::NoError) { + switch (m_repoMetaInfoJob->error()) { + case QInstaller::UserIgnoreError: + break; // we can simply ignore this error, the user knows about it + default: + setStatus(PackageManagerCore::Failure, m_repoMetaInfoJob->errorString()); + return m_repoFetched; + } + } + + m_repoFetched = true; + return m_repoFetched; +} + +bool PackageManagerCorePrivate::addUpdateResourcesFromRepositories(bool parseChecksum) +{ + if (m_updateSourcesAdded) + return m_updateSourcesAdded; + + if (m_repoMetaInfoJob->temporaryDirectories().isEmpty()) { + m_updateSourcesAdded = true; + return m_updateSourcesAdded; + } + + // forces an refresh/ clear on all update sources + m_updaterApplication.updateSourcesInfo()->refresh(); + if (isInstaller()) { + m_updaterApplication.addUpdateSource(m_settings.applicationName(), m_settings.applicationName(), + QString(), QUrl(QLatin1String("resource://metadata/")), 0); + m_updaterApplication.updateSourcesInfo()->setModified(false); + } + + m_updates = false; + m_updateSourcesAdded = false; + + const QString &appName = m_settings.applicationName(); + const QStringList tempDirs = m_repoMetaInfoJob->temporaryDirectories(); + foreach (const QString &tmpDir, tempDirs) { + if (statusCanceledOrFailed()) + return false; + + if (tmpDir.isEmpty()) + continue; + + if (parseChecksum) { + const QString updatesXmlPath = tmpDir + QLatin1String("/Updates.xml"); + QFile updatesFile(updatesXmlPath); + try { + openForRead(&updatesFile, updatesFile.fileName()); + } catch(const Error &e) { + qDebug() << "Error opening Updates.xml:" << e.message(); + setStatus(PackageManagerCore::Failure, tr("Could not add temporary update source information.")); + return false; + } + + int line = 0; + int column = 0; + QString error; + QDomDocument doc; + if (!doc.setContent(&updatesFile, &error, &line, &column)) { + qDebug() << QString::fromLatin1("Parse error in File %4 : %1 at line %2 col %3").arg(error, + QString::number(line), QString::number(column), updatesFile.fileName()); + setStatus(PackageManagerCore::Failure, tr("Could not add temporary update source information.")); + return false; + } + + const QDomNode checksum = doc.documentElement().firstChildElement(QLatin1String("Checksum")); + if (!checksum.isNull()) + m_core->setTestChecksum(checksum.toElement().text().toLower() == scTrue); + } + m_updaterApplication.addUpdateSource(appName, appName, QString(), QUrl::fromLocalFile(tmpDir), 1); + } + m_updaterApplication.updateSourcesInfo()->setModified(false); + + if (m_updaterApplication.updateSourcesInfo()->updateSourceInfoCount() == 0) { + setStatus(PackageManagerCore::Failure, tr("Could not find any update source information.")); + return false; + } + + m_updateSourcesAdded = true; + return m_updateSourcesAdded; +} + +void PackageManagerCorePrivate::realAppendToInstallComponents(Component *component) +{ + if (!component->isInstalled() || component->updateRequested()) { + //remove the checkState method if we don't use selected in scripts + setCheckedState(component, Qt::Checked); + + m_orderedComponentsToInstall.append(component); + m_toInstallComponentIds.insert(component->name()); + } +} + +void PackageManagerCorePrivate::insertInstallReason(Component *component, const QString &reason) +{ + //keep the first reason + if (m_toInstallComponentIdReasonHash.value(component->name()).isEmpty()) + m_toInstallComponentIdReasonHash.insert(component->name(), reason); +} + +bool PackageManagerCorePrivate::appendComponentToUninstall(Component *component) +{ + // remove all already resolved dependees + QSet<Component *> dependees = m_core->dependees(component).toSet().subtract(m_componentsToUninstall); + if (dependees.isEmpty()) { + setCheckedState(component, Qt::Unchecked); + m_componentsToUninstall.insert(component); + return true; + } + + QSet<Component *> dependeesToResolve; + foreach (Component *dependee, dependees) { + if (dependee->isInstalled()) { + // keep them as already resolved + setCheckedState(dependee, Qt::Unchecked); + m_componentsToUninstall.insert(dependee); + // gather possible dependees, keep them to resolve it later + dependeesToResolve.unite(m_core->dependees(dependee).toSet()); + } + } + + bool allResolved = true; + foreach (Component *dependee, dependeesToResolve) + allResolved &= appendComponentToUninstall(dependee); + + return allResolved; +} + +bool PackageManagerCorePrivate::appendComponentsToUninstall(const QList<Component*> &components) +{ + if (components.isEmpty()) { + qDebug() << "components list is empty in" << Q_FUNC_INFO; + return true; + } + + bool allResolved = true; + foreach (Component *component, components) { + if (component->isInstalled()) { + setCheckedState(component, Qt::Unchecked); + m_componentsToUninstall.insert(component); + allResolved &= appendComponentToUninstall(component); + } + } + + QSet<Component*> installedComponents; + foreach (const QString &name, localInstalledPackages().keys()) { + if (Component *component = m_core->componentByName(name)) { + if (!component->uninstallationRequested()) + installedComponents.insert(component); + } + } + + QList<Component*> autoDependOnList; + if (allResolved) { + // All regular dependees are resolved. Now we are looking for auto depend on components. + foreach (Component *component, installedComponents) { + // If a components is installed and not yet scheduled for un-installation, check for auto depend. + if (component->isInstalled() && !m_componentsToUninstall.contains(component)) { + QStringList autoDependencies = component->autoDependencies(); + if (autoDependencies.isEmpty()) + continue; + + // This code needs to be enabled once the scripts use isInstalled, installationRequested and + // uninstallationRequested... + if (autoDependencies.first().compare(QLatin1String("script"), Qt::CaseInsensitive) == 0) { + //QScriptValue valueFromScript; + //try { + // valueFromScript = callScriptMethod(QLatin1String("isAutoDependOn")); + //} catch (const Error &error) { + // // keep the component, should do no harm + // continue; + //} + + //if (valueFromScript.isValid() && !valueFromScript.toBool()) + // autoDependOnList.append(component); + continue; + } + + foreach (Component *c, installedComponents) { + const QString replaces = c->value(scReplaces); + QStringList possibleNames = replaces.split(QRegExp(QLatin1String("\\b(,|, )\\b")), + QString::SkipEmptyParts); + possibleNames.append(c->name()); + foreach (const QString &possibleName, possibleNames) + autoDependencies.removeAll(possibleName); + } + + // A component requested auto installation, keep it to resolve their dependencies as well. + if (!autoDependencies.isEmpty()) + autoDependOnList.append(component); + } + } + } + + if (!autoDependOnList.isEmpty()) + return appendComponentsToUninstall(autoDependOnList); + return allResolved; +} + +void PackageManagerCorePrivate::resetComponentsToUserCheckedState() +{ + if (m_coreCheckedHash.isEmpty()) + return; + + foreach (Component *component, m_coreCheckedHash.keys()) + component->setCheckState(m_coreCheckedHash.value(component)); + + m_coreCheckedHash.clear(); + m_componentsToInstallCalculated = false; +} + +void PackageManagerCorePrivate::setCheckedState(Component *component, Qt::CheckState state) +{ + m_coreCheckedHash.insert(component, component->checkState()); + component->setCheckState(state); +} + +void PackageManagerCorePrivate::connectOperationCallMethodRequest(Operation *const operation) +{ + QObject *const operationObject = dynamic_cast<QObject *> (operation); + if (operationObject != 0) { + const QMetaObject *const mo = operationObject->metaObject(); + if (mo->indexOfSignal(QMetaObject::normalizedSignature("requestBlockingExecution(QString)")) > -1) { + connect(operationObject, SIGNAL(requestBlockingExecution(QString)), + this, SLOT(handleMethodInvocationRequest(QString)), Qt::BlockingQueuedConnection); + } + } +} + +void PackageManagerCorePrivate::handleMethodInvocationRequest(const QString &invokableMethodName) +{ + QObject *obj = QObject::sender(); + if (obj != 0) + QMetaObject::invokeMethod(obj, qPrintable(invokableMethodName)); +} + +} // namespace QInstaller diff --git a/src/libs/installer/packagemanagercore_p.h b/src/libs/installer/packagemanagercore_p.h new file mode 100644 index 000000000..1a585cf96 --- /dev/null +++ b/src/libs/installer/packagemanagercore_p.h @@ -0,0 +1,260 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef PACKAGEMANAGERCORE_P_H +#define PACKAGEMANAGERCORE_P_H + +#include "getrepositoriesmetainfojob.h" +#include "settings.h" +#include "packagemanagercore.h" + +#include <kdsysinfo.h> +#include <kdupdaterapplication.h> +#include <kdupdaterupdatefinder.h> + +#include <QtCore/QHash> +#include <QtCore/QObject> +#include <QtCore/QPair> +#include <QtCore/QPointer> + +class FSEngineClientHandler; +class KDJob; + +QT_FORWARD_DECLARE_CLASS(QFile) +QT_FORWARD_DECLARE_CLASS(QFileInfo) + +using namespace KDUpdater; + +namespace QInstaller { + +struct BinaryLayout; +class Component; +class TempDirDeleter; + +class PackageManagerCorePrivate : public QObject +{ + Q_OBJECT + friend class PackageManagerCore; + +public: + enum OperationType { + Backup, + Perform, + Undo + }; + + explicit PackageManagerCorePrivate(PackageManagerCore *core); + explicit PackageManagerCorePrivate(PackageManagerCore *core, qint64 magicInstallerMaker, + const OperationList &performedOperations); + ~PackageManagerCorePrivate(); + + static bool isProcessRunning(const QString &name, const QList<ProcessInfo> &processes); + + static bool performOperationThreaded(Operation *op, PackageManagerCorePrivate::OperationType type + = PackageManagerCorePrivate::Perform); + + void initialize(); + bool isOfflineOnly() const; + + bool statusCanceledOrFailed() const; + void setStatus(int status, const QString &error = QString()); + + QString targetDir() const; + QString registerPath() const; + + QString uninstallerName() const; + QString installerBinaryPath() const; + + void writeMaintenanceConfigFiles(); + void readMaintenanceConfigFiles(const QString &targetDir); + + void writeUninstaller(OperationList performedOperations); + + QString componentsXmlPath() const; + QString configurationFileName() const; + + bool buildComponentTree(QHash<QString, Component*> &components, bool loadScript); + + void clearAllComponentLists(); + void clearUpdaterComponentLists(); + QList<Component*> &replacementDependencyComponents(RunMode mode); + QHash<QString, QPair<Component*, Component*> > &componentsToReplace(RunMode mode); + + void clearComponentsToInstall(); + bool appendComponentsToInstall(const QList<Component*> &components); + bool appendComponentToInstall(Component *components); + QString installReason(Component *component); + + void runInstaller(); + bool isInstaller() const; + + void runUninstaller(); + bool isUninstaller() const; + + void runUpdater(); + bool isUpdater() const; + + void runPackageUpdater(); + bool isPackageManager() const; + + QString replaceVariables(const QString &str) const; + QByteArray replaceVariables(const QByteArray &str) const; + + void callBeginInstallation(const QList<Component*> &componentList); + void stopProcessesForUpdates(const QList<Component*> &components); + int countProgressOperations(const QList<Component*> &components); + int countProgressOperations(const OperationList &operations); + void connectOperationToInstaller(Operation *const operation, double progressOperationPartSize); + void connectOperationCallMethodRequest(Operation *const operation); + + Operation *createOwnedOperation(const QString &type); + Operation *takeOwnedOperation(Operation *operation); + + Operation *createPathOperation(const QFileInfo &fileInfo, const QString &componentName); + void registerPathesForUninstallation(const QList<QPair<QString, bool> > &pathesForUninstallation, + const QString &componentName); + + void addPerformed(Operation *op) { + m_performedOperationsCurrentSession.append(op); + } + + void commitSessionOperations() { + m_performedOperationsOld += m_performedOperationsCurrentSession; + m_performedOperationsCurrentSession.clear(); + } + + void installComponent(Component *component, double progressOperationSize, + bool adminRightsGained = false); + + bool appendComponentToUninstall(Component *component); + bool appendComponentsToUninstall(const QList<Component*> &components); + +signals: + void installationStarted(); + void installationFinished(); + void uninstallationStarted(); + void uninstallationFinished(); + +public: + UpdateFinder *m_updateFinder; + Application m_updaterApplication; + FSEngineClientHandler *m_FSEngineClientHandler; + + int m_status; + QString m_error; + + Settings m_settings; + bool m_forceRestart; + bool m_testChecksum; + bool m_launchedAsRoot; + bool m_completeUninstall; + bool m_needToWriteUninstaller; + QHash<QString, QString> m_vars; + QHash<QString, bool> m_sharedFlags; + QString m_installerBaseBinaryUnreplaced; + + QList<Component*> m_rootComponents; + QList<Component*> m_rootDependencyReplacements; + + QList<Component*> m_updaterComponents; + QList<Component*> m_updaterComponentsDeps; + QList<Component*> m_updaterDependencyReplacements; + + OperationList m_ownedOperations; + OperationList m_performedOperationsOld; + OperationList m_performedOperationsCurrentSession; + +private slots: + void infoMessage(KDJob *, const QString &message) { + emit m_core->metaJobInfoMessage(message); + } + + void handleMethodInvocationRequest(const QString &invokableMethodName); + +private: + void deleteUninstaller(); + void registerUninstaller(); + void unregisterUninstaller(); + + void writeUninstallerBinary(QFile *const input, qint64 size, bool writeBinaryLayout); + void writeUninstallerBinaryData(QIODevice *output, QFile *const input, const OperationList &performed, + const BinaryLayout &layout); + + void runUndoOperations(const OperationList &undoOperations, double undoOperationProgressSize, + bool adminRightsGained, bool deleteOperation); + + PackagesList remotePackages(); + LocalPackagesHash localInstalledPackages(); + bool fetchMetaInformationFromRepositories(); + bool addUpdateResourcesFromRepositories(bool parseChecksum); + void realAppendToInstallComponents(Component *component); + void insertInstallReason(Component *component, const QString &reason); + +private: + PackageManagerCore *m_core; + GetRepositoriesMetaInfoJob *m_repoMetaInfoJob; + + bool m_updates; + bool m_repoFetched; + bool m_updateSourcesAdded; + qint64 m_magicBinaryMarker; + bool m_componentsToInstallCalculated; + + // < name (component to replace), < replacement component, component to replace > > + QHash<QString, QPair<Component*, Component*> > m_componentsToReplaceAllMode; + QHash<QString, QPair<Component*, Component*> > m_componentsToReplaceUpdaterMode; + + //calculate installation order variables + QList<Component*> m_orderedComponentsToInstall; + QHash<Component*, QSet<Component*> > m_visitedComponents; + + QSet<QString> m_toInstallComponentIds; //for faster lookups + + //we can't use this reason hash as component id hash, because some reasons are ready before + //the component is added + QHash<QString, QString> m_toInstallComponentIdReasonHash; + + QSet<Component*> m_componentsToUninstall; + QString m_componentsToInstallError; + FileDownloaderProxyFactory *m_proxyFactory; + bool m_createLocalRepositoryFromBinary; + +private: + // remove once we deprecate isSelected, setSelected etc... + void resetComponentsToUserCheckedState(); + QHash<Component*, Qt::CheckState> m_coreCheckedHash; + void setCheckedState(Component *component, Qt::CheckState state); +}; + +} // namespace QInstaller + +#endif // PACKAGEMANAGERCORE_P_H diff --git a/src/libs/installer/packagemanagergui.cpp b/src/libs/installer/packagemanagergui.cpp new file mode 100644 index 000000000..0b96e2d94 --- /dev/null +++ b/src/libs/installer/packagemanagergui.cpp @@ -0,0 +1,1983 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ +#include "packagemanagergui.h" + +#include "component.h" +#include "componentmodel.h" +#include "errors.h" +#include "fileutils.h" +#include "messageboxhandler.h" +#include "packagemanagercore.h" +#include "qinstallerglobal.h" +#include "progresscoordinator.h" +#include "performinstallationform.h" +#include "settings.h" +#include "utils.h" + +#include "kdsysinfo.h" + +#include <QtCore/QDir> +#include <QtCore/QDynamicPropertyChangeEvent> +#include <QtCore/QPair> +#include <QtCore/QProcess> +#include <QtCore/QRegExp> +#include <QtCore/QSettings> +#include <QtCore/QTimer> + +#include <QtGui/QApplication> +#include <QtGui/QCheckBox> +#include <QtGui/QDesktopServices> +#include <QtGui/QFileDialog> +#include <QtGui/QGridLayout> +#include <QtGui/QFormLayout> +#include <QtGui/QHBoxLayout> +#include <QtGui/QHeaderView> +#include <QtGui/QLabel> +#include <QtGui/QLineEdit> +#include <QtGui/QListWidget> +#include <QtGui/QListWidgetItem> +#include <QtGui/QMessageBox> +#include <QtGui/QProgressBar> +#include <QtGui/QPushButton> +#include <QtGui/QRadioButton> +#include <QtGui/QTextBrowser> +#include <QtGui/QTreeWidget> +#include <QtGui/QTreeView> +#include <QtGui/QVBoxLayout> +#include <QtGui/QScrollBar> +#include <QtGui/QShowEvent> + +#include <QtScript/QScriptEngine> + +using namespace KDUpdater; +using namespace QInstaller; + +/* +TRANSLATOR QInstaller::PackageManagerCore; +*/ +/* +TRANSLATOR QInstaller::PackageManagerGui +*/ +/* +TRANSLATOR QInstaller::PackageManagerPage +*/ +/* +TRANSLATOR QInstaller::IntroductionPage +*/ +/* +TRANSLATOR QInstaller::LicenseAgreementPage +*/ +/* +TRANSLATOR QInstaller::ComponentSelectionPage +*/ +/* +TRANSLATOR QInstaller::TargetDirectoryPage +*/ +/* +TRANSLATOR QInstaller::StartMenuDirectoryPage +*/ +/* +TRANSLATOR QInstaller::ReadyForInstallationPage +*/ +/* +TRANSLATOR QInstaller::PerformInstallationPage +*/ +/* +TRANSLATOR QInstaller::FinishedPage +*/ + + +static QString humanReadableSize(quint64 intSize) +{ + QString unit; + double size; + + if (intSize < 1024 * 1024) { + size = 1. + intSize / 1024.; + unit = QObject::tr("kB"); + } else if (intSize < 1024 * 1024 * 1024) { + size = 1. + intSize / 1024. / 1024.; + unit = QObject::tr("MB"); + } else { + size = 1. + intSize / 1024. / 1024. / 1024.; + unit = QObject::tr("GB"); + } + + size = qRound(size * 10) / 10.0; + return QString::fromLatin1("%L1 %2").arg(size, 0, 'g', 4).arg(unit); +} + + +class DynamicInstallerPage : public PackageManagerPage +{ +public: + explicit DynamicInstallerPage(QWidget *widget, PackageManagerCore *core = 0) + : PackageManagerPage(core) + , m_widget(widget) + { + setObjectName(QLatin1String("Dynamic") + widget->objectName()); + setPixmap(QWizard::LogoPixmap, logoPixmap()); + setPixmap(QWizard::WatermarkPixmap, QPixmap()); + + setLayout(new QVBoxLayout); + setSubTitle(QString()); + setTitle(widget->windowTitle()); + m_widget->setProperty("complete", true); + m_widget->setProperty("final", false); + widget->installEventFilter(this); + layout()->addWidget(widget); + } + + QWidget *widget() const + { + return m_widget; + } + + bool isComplete() const + { + return m_widget->property("complete").toBool(); + } + +protected: + bool eventFilter(QObject *obj, QEvent *event) + { + if (obj == m_widget) { + switch(event->type()) { + case QEvent::WindowTitleChange: + setTitle(m_widget->windowTitle()); + break; + + case QEvent::DynamicPropertyChange: + emit completeChanged(); + if (m_widget->property("final").toBool() != isFinalPage()) + setFinalPage(m_widget->property("final").toBool()); + break; + + default: + break; + } + } + return PackageManagerPage::eventFilter(obj, event); + } + +private: + QWidget *const m_widget; +}; + + +// -- PackageManagerGui::Private + +class PackageManagerGui::Private +{ +public: + Private() + : m_modified(false) + , m_autoSwitchPage(true) + , m_showSettingsButton(false) + { } + + bool m_modified; + bool m_autoSwitchPage; + bool m_showSettingsButton; + QMap<int, QWizardPage*> m_defaultPages; + QMap<int, QString> m_defaultButtonText; + + QScriptValue m_controlScript; + QScriptEngine m_controlScriptEngine; +}; + + +// -- PackageManagerGui + +QScriptEngine *PackageManagerGui::controlScriptEngine() const +{ + return &d->m_controlScriptEngine; +} + +/*! + \class QInstaller::PackageManagerGui + Is the "gui" object in a none interactive installation +*/ +PackageManagerGui::PackageManagerGui(PackageManagerCore *core, QWidget *parent) + : QWizard(parent) + , d(new Private) + , m_core(core) +{ + if (m_core->isInstaller()) + setWindowTitle(tr("%1 Setup").arg(m_core->value(scTitle))); + else + setWindowTitle(tr("Maintain %1").arg(m_core->value(scTitle))); + +#ifndef Q_WS_MAC + setWindowIcon(QIcon(m_core->settings().icon())); +#else + setPixmap(QWizard::BackgroundPixmap, m_core->settings().background()); +#endif +#ifdef Q_OS_LINUX + setWizardStyle(QWizard::ModernStyle); + setSizeGripEnabled(true); +#endif + setOption(QWizard::NoBackButtonOnStartPage); + setOption(QWizard::NoBackButtonOnLastPage); + setLayout(new QVBoxLayout(this)); + + connect(this, SIGNAL(rejected()), m_core, SLOT(setCanceled())); + connect(this, SIGNAL(interrupted()), m_core, SLOT(interrupt())); + + // both queued to show the finished page once everything is done + connect(m_core, SIGNAL(installationFinished()), this, SLOT(showFinishedPage()), Qt::QueuedConnection); + connect(m_core, SIGNAL(uninstallationFinished()), this, SLOT(showFinishedPage()), Qt::QueuedConnection); + + connect(this, SIGNAL(currentIdChanged(int)), this, SLOT(slotCurrentPageChanged(int))); + connect(this, SIGNAL(currentIdChanged(int)), m_core, SIGNAL(currentPageChanged(int))); + connect(button(QWizard::FinishButton), SIGNAL(clicked()), this, SIGNAL(finishButtonClicked())); + connect(button(QWizard::FinishButton), SIGNAL(clicked()), m_core, SIGNAL(finishButtonClicked())); + + // make sure the QUiLoader's retranslateUi is executed first, then the script + connect(this, SIGNAL(languageChanged()), m_core, SLOT(languageChanged()), Qt::QueuedConnection); + connect(this, SIGNAL(languageChanged()), this, SLOT(onLanguageChanged()), Qt::QueuedConnection); + + connect(m_core, SIGNAL(wizardPageInsertionRequested(QWidget*, QInstaller::PackageManagerCore::WizardPage)), + this, SLOT(wizardPageInsertionRequested(QWidget*, QInstaller::PackageManagerCore::WizardPage))); + connect(m_core, SIGNAL(wizardPageRemovalRequested(QWidget*)),this, + SLOT(wizardPageRemovalRequested(QWidget*))); + connect(m_core, SIGNAL(wizardWidgetInsertionRequested(QWidget*, QInstaller::PackageManagerCore::WizardPage)), + this, SLOT(wizardWidgetInsertionRequested(QWidget*, QInstaller::PackageManagerCore::WizardPage))); + connect(m_core, SIGNAL(wizardWidgetRemovalRequested(QWidget*)), this, + SLOT(wizardWidgetRemovalRequested(QWidget*))); + connect(m_core, SIGNAL(wizardPageVisibilityChangeRequested(bool, int)), this, + SLOT(wizardPageVisibilityChangeRequested(bool, int)), Qt::QueuedConnection); + + connect(m_core, SIGNAL(setAutomatedPageSwitchEnabled(bool)), this, + SLOT(setAutomatedPageSwitchEnabled(bool))); + + connect(this, SIGNAL(customButtonClicked(int)), this, SLOT(customButtonClicked(int))); + + for (int i = QWizard::BackButton; i < QWizard::CustomButton1; ++i) + d->m_defaultButtonText.insert(i, buttonText(QWizard::WizardButton(i))); + +#ifdef Q_WS_MAC + resize(sizeHint() * 1.25); +#else + resize(sizeHint()); +#endif +} + +PackageManagerGui::~PackageManagerGui() +{ + delete d; +} + +void PackageManagerGui::setAutomatedPageSwitchEnabled(bool request) +{ + d->m_autoSwitchPage = request; +} + +QString PackageManagerGui::defaultButtonText(int wizardButton) const +{ + return d->m_defaultButtonText.value(wizardButton); +} + +void PackageManagerGui::clickButton(int wb, int delay) +{ + if (QAbstractButton *b = button(static_cast<QWizard::WizardButton>(wb) )) { + QTimer::singleShot(delay, b, SLOT(click())); + } else { + // TODO: we should probably abort immediately here (faulty test script) + qDebug() << "Button" << wb << "not found!"; + } +} + +/*! + Loads a script to perform the installation non-interactively. + @throws QInstaller::Error if the script is not readable/cannot be parsed +*/ +void PackageManagerGui::loadControlScript(const QString &scriptPath) +{ + QFile file(scriptPath); + if (!file.open(QIODevice::ReadOnly)) { + throw Error(QObject::tr("Could not open the requested script file at %1: %2") + .arg(scriptPath, file.errorString())); + } + + QScriptValue installerObject = d->m_controlScriptEngine.newQObject(m_core); + installerObject.setProperty(QLatin1String("componentByName"), d->m_controlScriptEngine + .newFunction(qInstallerComponentByName, 1)); + + d->m_controlScriptEngine.globalObject().setProperty(QLatin1String("installer"), + installerObject); + d->m_controlScriptEngine.globalObject().setProperty(QLatin1String("gui"), + d->m_controlScriptEngine.newQObject(this)); + d->m_controlScriptEngine.globalObject().setProperty(QLatin1String("packagemanagergui"), + d->m_controlScriptEngine.newQObject(this)); + registerMessageBox(&d->m_controlScriptEngine); + +#undef REGISTER_BUTTON +#define REGISTER_BUTTON(x) buttons.setProperty(QLatin1String(#x), \ + d->m_controlScriptEngine.newVariant(static_cast<int>(QWizard::x))); + + QScriptValue buttons = d->m_controlScriptEngine.newArray(); + REGISTER_BUTTON(BackButton) + REGISTER_BUTTON(NextButton) + REGISTER_BUTTON(CommitButton) + REGISTER_BUTTON(FinishButton) + REGISTER_BUTTON(CancelButton) + REGISTER_BUTTON(HelpButton) + REGISTER_BUTTON(CustomButton1) + REGISTER_BUTTON(CustomButton2) + REGISTER_BUTTON(CustomButton3) + +#undef REGISTER_BUTTON + + d->m_controlScriptEngine.globalObject().setProperty(QLatin1String("buttons"), buttons); + + d->m_controlScriptEngine.evaluate(QLatin1String(file.readAll()), scriptPath); + if (d->m_controlScriptEngine.hasUncaughtException()) { + throw Error(QObject::tr("Exception while loading the control script %1") + .arg(uncaughtExceptionString(&(d->m_controlScriptEngine)/*, scriptPath*/))); + } + + QScriptValue comp = d->m_controlScriptEngine.evaluate(QLatin1String("Controller")); + if (d->m_controlScriptEngine.hasUncaughtException()) { + throw Error(QObject::tr("Exception while loading the control script %1") + .arg(uncaughtExceptionString(&(d->m_controlScriptEngine)/*, scriptPath*/))); + } + + d->m_controlScript = comp; + d->m_controlScript.construct(); + + qDebug() << "Loaded control script" << scriptPath; +} + +void PackageManagerGui::slotCurrentPageChanged(int id) +{ + QMetaObject::invokeMethod(this, "delayedControlScriptExecution", Qt::QueuedConnection, + Q_ARG(int, id)); +} + +void PackageManagerGui::callControlScriptMethod(const QString &methodName) +{ + if (!d->m_controlScript.isValid()) + return; + + QScriptValue method = d->m_controlScript.property(QLatin1String("prototype")).property(methodName); + + if (!method.isValid()) { + qDebug() << "Control script callback" << methodName << "does not exist."; + return; + } + + qDebug() << "Calling control script callback" << methodName; + + method.call(d->m_controlScript); + + if (d->m_controlScriptEngine.hasUncaughtException()) { + qCritical() + << uncaughtExceptionString(&(d->m_controlScriptEngine) /*, QLatin1String("control script")*/); + // TODO: handle error + } +} + +void PackageManagerGui::delayedControlScriptExecution(int id) +{ + if (PackageManagerPage *const p = qobject_cast<PackageManagerPage*> (page(id))) + callControlScriptMethod(p->objectName() + QLatin1String("Callback")); +} + +void PackageManagerGui::onLanguageChanged() +{ + d->m_defaultButtonText.clear(); + for (int i = QWizard::BackButton; i < QWizard::CustomButton1; ++i) + d->m_defaultButtonText.insert(i, buttonText(QWizard::WizardButton(i))); +} + +bool PackageManagerGui::event(QEvent *event) +{ + switch(event->type()) { + case QEvent::LanguageChange: + emit languageChanged(); + break; + default: + break; + } + return QWizard::event(event); +} + +void PackageManagerGui::showEvent(QShowEvent *event) +{ +#ifndef Q_OS_LINUX + if (!event->spontaneous()) { + foreach (int id, pageIds()) { + const QString subTitle = page(id)->subTitle(); + if (subTitle.isEmpty()) { + const QWizard::WizardStyle style = wizardStyle(); + if ((style == QWizard::ClassicStyle || style == QWizard::ModernStyle)) + page(id)->setSubTitle(QLatin1String(" ")); // otherwise the colors might screw up + } + } + } +#endif + QWizard::showEvent(event); +} + +void PackageManagerGui::wizardPageInsertionRequested(QWidget *widget, + QInstaller::PackageManagerCore::WizardPage page) +{ + // just in case it was already in there... + wizardPageRemovalRequested(widget); + + int pageId = static_cast<int>(page) - 1; + while (QWizard::page(pageId) != 0) + --pageId; + + // add it + setPage(pageId, new DynamicInstallerPage(widget, m_core)); +} + +void PackageManagerGui::wizardPageRemovalRequested(QWidget *widget) +{ + foreach (int pageId, pageIds()) { + DynamicInstallerPage *const dynamicPage = dynamic_cast<DynamicInstallerPage*>(page(pageId)); + if (dynamicPage == 0) + continue; + if (dynamicPage->widget() != widget) + continue; + removePage(pageId); + d->m_defaultPages.remove(pageId); + } +} + +void PackageManagerGui::wizardWidgetInsertionRequested(QWidget *widget, + QInstaller::PackageManagerCore::WizardPage page) +{ + Q_ASSERT(widget); + if (QWizardPage *const p = QWizard::page(page)) + p->layout()->addWidget(widget); +} + +void PackageManagerGui::wizardWidgetRemovalRequested(QWidget *widget) +{ + Q_ASSERT(widget); + widget->setParent(0); +} + +void PackageManagerGui::wizardPageVisibilityChangeRequested(bool visible, int p) +{ + if (visible && page(p) == 0) { + setPage(p, d->m_defaultPages[p]); + } else if (!visible && page(p) != 0) { + d->m_defaultPages[p] = page(p); + removePage(p); + } +} + +PackageManagerPage *PackageManagerGui::page(int pageId) const +{ + return qobject_cast<PackageManagerPage*> (QWizard::page(pageId)); +} + +QWidget *PackageManagerGui::pageWidgetByObjectName(const QString &name) const +{ + const QList<int> ids = pageIds(); + foreach (const int i, ids) { + PackageManagerPage *const p = qobject_cast<PackageManagerPage*> (page(i)); + if (p && p->objectName() == name) { + // For dynamic pages, return the contained widget (as read from the UI file), not the + // wrapper page + if (DynamicInstallerPage *dp = dynamic_cast<DynamicInstallerPage*>(p)) + return dp->widget(); + return p; + } + } + qDebug() << "No page found for object name" << name; + return 0; +} + +QWidget *PackageManagerGui::currentPageWidget() const +{ + return currentPage(); +} + +void PackageManagerGui::cancelButtonClicked() +{ + if (currentId() != PackageManagerCore::Introduction + && currentId() != PackageManagerCore::InstallationFinished) { + PackageManagerPage *const page = qobject_cast<PackageManagerPage*> (currentPage()); + if (page && page->isInterruptible() && m_core->status() != PackageManagerCore::Canceled + && m_core->status() != PackageManagerCore::Failure) { + const QMessageBox::StandardButton bt = + MessageBoxHandler::question(MessageBoxHandler::currentBestSuitParent(), + QLatin1String("cancelInstallation"), tr("Question"), + tr("Do you want to abort the %1 process?").arg(m_core->isUninstaller() ? tr("uninstallation") + : tr("installation")), QMessageBox::Yes | QMessageBox::No); + if (bt == QMessageBox::Yes) + emit interrupted(); + } else { + QString app = tr("installer"); + if (m_core->isUninstaller()) + app = tr("uninstaller"); + if (m_core->isUpdater() || m_core->isPackageManager()) + app = tr("maintenance"); + + const QMessageBox::StandardButton bt = + MessageBoxHandler::question(MessageBoxHandler::currentBestSuitParent(), + QLatin1String("cancelInstallation"), tr("Question"), + tr("Do you want to quit the %1 application?").arg(app), QMessageBox::Yes | QMessageBox::No); + if (bt == QMessageBox::Yes) + QDialog::reject(); + } + } else { + QDialog::reject(); + } +} + +void PackageManagerGui::rejectWithoutPrompt() +{ + m_core->setCanceled(); + QDialog::reject(); +} + +void PackageManagerGui::reject() +{ + cancelButtonClicked(); +} + +void PackageManagerGui::setModified(bool value) +{ + d->m_modified = value; +} + +void PackageManagerGui::showFinishedPage() +{ + qDebug() << "SHOW FINISHED PAGE"; + if (d->m_autoSwitchPage) + next(); + else + qobject_cast<QPushButton*>(button(QWizard::CancelButton))->setEnabled(false); +} + +void PackageManagerGui::showSettingsButton(bool show) +{ + if (d->m_showSettingsButton == show) + return; + + d->m_showSettingsButton = show; + setOption(QWizard::HaveCustomButton1, show); + setButtonText(QWizard::CustomButton1, tr("Settings")); + + updateButtonLayout(); +} + +/*! + Force an update of our own button layout, needs to be called whenever a button option has been set. +*/ +void PackageManagerGui::updateButtonLayout() +{ + QVector<QWizard::WizardButton> buttons(12, QWizard::NoButton); + if (options() & QWizard::HaveHelpButton) + buttons[(options() & QWizard::HelpButtonOnRight) ? 11 : 0] = QWizard::HelpButton; + + buttons[1] = QWizard::Stretch; + if (options() & QWizard::HaveCustomButton1) { + buttons[1] = QWizard::CustomButton1; + buttons[2] = QWizard::Stretch; + } + + if (options() & QWizard::HaveCustomButton2) + buttons[3] = QWizard::CustomButton2; + + if (options() & QWizard::HaveCustomButton3) + buttons[4] = QWizard::CustomButton3; + + if (!(options() & QWizard::NoCancelButton)) + buttons[(options() & QWizard::CancelButtonOnLeft) ? 5 : 10] = QWizard::CancelButton; + + buttons[6] = QWizard::BackButton; + buttons[7] = QWizard::NextButton; + buttons[8] = QWizard::CommitButton; + buttons[9] = QWizard::FinishButton; + + setOption(QWizard::NoBackButtonOnLastPage, true); + setOption(QWizard::NoBackButtonOnStartPage, true); + + setButtonLayout(buttons.toList()); +} + +void PackageManagerGui::setSettingsButtonEnabled(bool enabled) +{ + if (QAbstractButton *btn = button(QWizard::CustomButton1)) + btn->setEnabled(enabled); +} + +void PackageManagerGui::customButtonClicked(int which) +{ + if (QWizard::WizardButton(which) == QWizard::CustomButton1 && d->m_showSettingsButton) + emit settingsButtonClicked(); +} + + +// -- PackageManagerPage + +PackageManagerPage::PackageManagerPage(PackageManagerCore *core) + : m_fresh(true) + , m_complete(true) + , m_core(core) +{ +} + +PackageManagerCore *PackageManagerPage::packageManagerCore() const +{ + return m_core; +} + +QVariantHash PackageManagerPage::elementsForPage(const QString &pageName) const +{ + const QVariant variant = m_core->settings().value(pageName); + + QVariantHash hash; + if (variant.canConvert<QVariantHash>()) + hash = variant.value<QVariantHash>(); + return hash; +} + +QString PackageManagerPage::titleForPage(const QString &pageName, const QString &value) const +{ + return titleFromHash(m_core->settings().titlesForPage(pageName), value); +} + +QString PackageManagerPage::subTitleForPage(const QString &pageName, const QString &value) const +{ + return titleFromHash(m_core->settings().subTitlesForPage(pageName), value); +} + +QString PackageManagerPage::titleFromHash(const QVariantHash &hash, const QString &value) const +{ + QString defaultValue = hash.value(QLatin1String("Default")).toString(); + if (defaultValue.isEmpty()) + defaultValue = value; + + if (m_core->isUpdater()) + return hash.value(QLatin1String("Updater"), defaultValue).toString(); + if (m_core->isInstaller()) + return hash.value(QLatin1String("Installer"), defaultValue).toString(); + if (m_core->isPackageManager()) + return hash.value(QLatin1String("PackageManager"), defaultValue).toString(); + return hash.value(QLatin1String("Uninstaller"), defaultValue).toString(); +} + +QPixmap PackageManagerPage::watermarkPixmap() const +{ + return QPixmap(m_core->value(QLatin1String("WatermarkPixmap"))); +} + +QPixmap PackageManagerPage::logoPixmap() const +{ + return QPixmap(m_core->value(QLatin1String("LogoPixmap"))); +} + +QString PackageManagerPage::productName() const +{ + return m_core->value(QLatin1String("ProductName")); +} + +bool PackageManagerPage::isComplete() const +{ + return m_complete; +} + +void PackageManagerPage::setComplete(bool complete) +{ + m_complete = complete; + if (QWizard *w = wizard()) { + if (QAbstractButton *cancel = w->button(QWizard::CancelButton)) { + if (cancel->hasFocus()) { + if (QAbstractButton *next = w->button(QWizard::NextButton)) + next->setFocus(); + } + } + } + emit completeChanged(); +} + +void PackageManagerPage::insertWidget(QWidget *widget, const QString &siblingName, int offset) +{ + QWidget *sibling = findChild<QWidget *>(siblingName); + QWidget *parent = sibling ? sibling->parentWidget() : 0; + QLayout *layout = parent ? parent->layout() : 0; + QBoxLayout *blayout = qobject_cast<QBoxLayout *>(layout); + + if (blayout) { + const int index = blayout->indexOf(sibling) + offset; + blayout->insertWidget(index, widget); + } +} + +QWidget *PackageManagerPage::findWidget(const QString &objectName) const +{ + return findChild<QWidget*> (objectName); +} + +/*! + \reimp + \Overwritten to support some kind of initializePage() in the case the wizard has been set + to QWizard::IndependentPages. If that option has been set, initializePage() would be only called + once. So we provide entering() and leaving() based on this overwritten function. +*/ +void PackageManagerPage::setVisible(bool visible) +{ + QWizardPage::setVisible(visible); + qApp->processEvents(); + + if (m_fresh && !visible) { + // this is only hit once when the page gets added to the wizard + m_fresh = false; + return; + } + + if (visible) + entering(); + else + leaving(); +} + +int PackageManagerPage::nextId() const +{ + return QWizardPage::nextId(); +} + + +// -- IntroductionPage + +IntroductionPage::IntroductionPage(PackageManagerCore *core) + : PackageManagerPage(core) + , m_widget(0) +{ + setObjectName(QLatin1String("IntroductionPage")); + setPixmap(QWizard::WatermarkPixmap, watermarkPixmap()); + setSubTitle(subTitleForPage(QLatin1String("IntroductionPage"))); + setTitle(titleForPage(QLatin1String("IntroductionPage"), tr("Setup - %1")).arg(productName())); + + m_msgLabel = new QLabel(this); + m_msgLabel->setWordWrap(true); + m_msgLabel->setObjectName(QLatin1String("MessageLabel")); + const QVariantHash hash = elementsForPage(QLatin1String("IntroductionPage")); + m_msgLabel->setText(hash.value(QLatin1String("MessageLabel"), tr("Welcome to the %1 " + "Setup Wizard.")).toString().arg(productName())); + + QVBoxLayout *layout = new QVBoxLayout(this); + setLayout(layout); + layout->addWidget(m_msgLabel); + layout->addItem(new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding)); +} + +void IntroductionPage::setWidget(QWidget *widget) +{ + if (m_widget) { + layout()->removeWidget(m_widget); + delete m_widget; + } + m_widget = widget; + if (m_widget) + static_cast<QVBoxLayout*>(layout())->addWidget(m_widget, 1); +} + +void IntroductionPage::setText(const QString &text) +{ + m_msgLabel->setText(text); +} + + +// -- LicenseAgreementPage::ClickForwarder + +class LicenseAgreementPage::ClickForwarder : public QObject +{ + Q_OBJECT + +public: + explicit ClickForwarder(QAbstractButton *button) + : QObject(button) + , m_abstractButton(button) {} + +protected: + bool eventFilter(QObject *object, QEvent *event) + { + if (event->type() == QEvent::MouseButtonRelease) { + m_abstractButton->click(); + return true; + } + // standard event processing + return QObject::eventFilter(object, event); + } +private: + QAbstractButton *m_abstractButton; +}; + + +// -- LicenseAgreementPage + +LicenseAgreementPage::LicenseAgreementPage(PackageManagerCore *core) + : PackageManagerPage(core) +{ + setPixmap(QWizard::LogoPixmap, logoPixmap()); + setPixmap(QWizard::WatermarkPixmap, QPixmap()); + setObjectName(QLatin1String("LicenseAgreementPage")); + setTitle(titleForPage(QLatin1String("LicenseAgreementPage"), tr("License Agreement"))); + setSubTitle(subTitleForPage(QLatin1String("LicenseAgreementPage"), tr("Please read the following license " + "agreement(s). You must accept the terms contained in these agreement(s) before continuing with the " + "installation."))); + + m_licenseListWidget = new QListWidget(this); + m_licenseListWidget->setObjectName(QLatin1String("LicenseListWidget")); + m_licenseListWidget->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding); + connect(m_licenseListWidget, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)), + this, SLOT(currentItemChanged(QListWidgetItem *))); + + m_textBrowser = new QTextBrowser(this); + m_textBrowser->setReadOnly(true); + m_textBrowser->setOpenLinks(false); + m_textBrowser->setOpenExternalLinks(true); + m_textBrowser->setObjectName(QLatin1String("LicenseTextBrowser")); + m_textBrowser->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding); + connect(m_textBrowser, SIGNAL(anchorClicked(QUrl)), this, SLOT(openLicenseUrl(QUrl))); + + QHBoxLayout *licenseBoxLayout = new QHBoxLayout(); + licenseBoxLayout->addWidget(m_licenseListWidget); + licenseBoxLayout->addWidget(m_textBrowser); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->addLayout(licenseBoxLayout); + + m_acceptRadioButton = new QRadioButton(this); + m_acceptRadioButton->setShortcut(QKeySequence(tr("Alt+A", "agree license"))); + m_acceptRadioButton->setObjectName(QLatin1String("AcceptLicenseRadioButton")); + ClickForwarder *acceptClickForwarder = new ClickForwarder(m_acceptRadioButton); + + QLabel *acceptLabel = new QLabel; + acceptLabel->setWordWrap(true); + acceptLabel->installEventFilter(acceptClickForwarder); + acceptLabel->setObjectName(QLatin1String("AcceptLicenseLabel")); + acceptLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); + const QVariantHash hash = elementsForPage(QLatin1String("LicenseAgreementPage")); + acceptLabel->setText(hash.value(QLatin1String("AcceptLicenseLabel"), tr("I accept the licenses.")).toString()); + + m_rejectRadioButton = new QRadioButton(this); + ClickForwarder *rejectClickForwarder = new ClickForwarder(m_rejectRadioButton); + m_rejectRadioButton->setObjectName(QString::fromUtf8("RejectLicenseRadioButton")); + m_rejectRadioButton->setShortcut(QKeySequence(tr("Alt+D", "do not agree license"))); + + QLabel *rejectLabel = new QLabel; + rejectLabel->setWordWrap(true); + rejectLabel->installEventFilter(rejectClickForwarder); + rejectLabel->setObjectName(QLatin1String("RejectLicenseLabel")); + rejectLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); + rejectLabel->setText(hash.value(QLatin1String("RejectLicenseLabel"), tr("I do not accept the licenses.")).toString()); + +#if defined(Q_WS_X11) || defined(Q_WS_MAC) + QFont labelFont(font()); + labelFont.setPixelSize(9); + acceptLabel->setFont(labelFont); + rejectLabel->setFont(labelFont); +#endif + + QGridLayout *gridLayout = new QGridLayout; + gridLayout->setColumnStretch(1, 1); + gridLayout->addWidget(m_acceptRadioButton, 0, 0); + gridLayout->addWidget(acceptLabel, 0, 1); + gridLayout->addWidget(m_rejectRadioButton, 1, 0); + gridLayout->addWidget(rejectLabel, 1, 1); + layout->addLayout(gridLayout); + + connect(m_acceptRadioButton, SIGNAL(toggled(bool)), this, SIGNAL(completeChanged())); + connect(m_rejectRadioButton, SIGNAL(toggled(bool)), this, SIGNAL(completeChanged())); + + m_rejectRadioButton->setChecked(true); +} + +void LicenseAgreementPage::entering() +{ + m_licenseListWidget->clear(); + m_textBrowser->setText(QString()); + m_licenseListWidget->setVisible(false); + + packageManagerCore()->calculateComponentsToInstall(); + foreach (QInstaller::Component *component, packageManagerCore()->orderedComponentsToInstall()) + addLicenseItem(component->licenses()); + + const int licenseCount = m_licenseListWidget->count(); + if (licenseCount > 0) { + m_licenseListWidget->setVisible(licenseCount > 1); + m_licenseListWidget->setCurrentItem(m_licenseListWidget->item(0)); + } +} + +bool LicenseAgreementPage::isComplete() const +{ + return m_acceptRadioButton->isChecked(); +} + +void LicenseAgreementPage::openLicenseUrl(const QUrl &url) +{ + QDesktopServices::openUrl(url); +} + +void LicenseAgreementPage::currentItemChanged(QListWidgetItem *current) +{ + if (current) + m_textBrowser->setText(current->data(Qt::UserRole).toString()); +} + +void LicenseAgreementPage::addLicenseItem(const QHash<QString, QPair<QString, QString> > &hash) +{ + for (QHash<QString, QPair<QString, QString> >::const_iterator it = hash.begin(); + it != hash.end(); ++it) { + QListWidgetItem *item = new QListWidgetItem(it.key(), m_licenseListWidget); + item->setData(Qt::UserRole, it.value().second); + } +} + + +// -- ComponentSelectionPage::Private + +class ComponentSelectionPage::Private : public QObject +{ + Q_OBJECT + +public: + Private(ComponentSelectionPage *qq, PackageManagerCore *core) + : q(qq) + , m_core(core) + , m_treeView(new QTreeView(q)) + , m_allModel(new ComponentModel(4, m_core)) + , m_updaterModel(new ComponentModel(4, m_core)) + { + m_treeView->setObjectName(QLatin1String("ComponentsTreeView")); + m_allModel->setObjectName(QLatin1String("AllComponentsModel")); + m_updaterModel->setObjectName(QLatin1String("UpdaterComponentsModel")); + + int i = 0; + m_currentModel = m_allModel; + ComponentModel *list[] = { m_allModel, m_updaterModel, 0 }; + while (ComponentModel *model = list[i++]) { + connect(model, SIGNAL(defaultCheckStateChanged(bool)), q, SLOT(setModified(bool))); + connect(model, SIGNAL(defaultCheckStateChanged(bool)), m_core, + SLOT(componentsToInstallNeedsRecalculation())); + + model->setHeaderData(ComponentModelHelper::NameColumn, Qt::Horizontal, ComponentSelectionPage::tr("Component Name")); + model->setHeaderData(ComponentModelHelper::InstalledVersionColumn, Qt::Horizontal, + ComponentSelectionPage::tr("Installed Version")); + model->setHeaderData(ComponentModelHelper::NewVersionColumn, Qt::Horizontal, ComponentSelectionPage::tr("New Version")); + model->setHeaderData(ComponentModelHelper::UncompressedSizeColumn, Qt::Horizontal, ComponentSelectionPage::tr("Size")); + } + + QHBoxLayout *hlayout = new QHBoxLayout; + hlayout->addWidget(m_treeView, 3); + + m_descriptionLabel = new QLabel(q); + m_descriptionLabel->setWordWrap(true); + m_descriptionLabel->setObjectName(QLatin1String("ComponentDescriptionLabel")); + + QVBoxLayout *vlayout = new QVBoxLayout; + vlayout->addWidget(m_descriptionLabel); + + m_sizeLabel = new QLabel(q); + m_sizeLabel->setWordWrap(true); + vlayout->addWidget(m_sizeLabel); + m_sizeLabel->setObjectName(QLatin1String("ComponentSizeLabel")); + + vlayout->addSpacerItem(new QSpacerItem(1, 1, QSizePolicy::MinimumExpanding, + QSizePolicy::MinimumExpanding)); + hlayout->addLayout(vlayout, 2); + + QVBoxLayout *layout = new QVBoxLayout(q); + layout->addLayout(hlayout, 1); + + m_checkDefault = new QPushButton; + connect(m_checkDefault, SIGNAL(clicked()), this, SLOT(selectDefault())); + connect(m_allModel, SIGNAL(defaultCheckStateChanged(bool)), m_checkDefault, SLOT(setEnabled(bool))); + const QVariantHash hash = q->elementsForPage(QLatin1String("ComponentSelectionPage")); + if (m_core->isInstaller()) { + m_checkDefault->setObjectName(QLatin1String("SelectDefaultComponentsButton")); + m_checkDefault->setShortcut(QKeySequence(ComponentSelectionPage::tr("Alt+A", "select default components"))); + m_checkDefault->setText(hash.value(QLatin1String("SelectDefaultComponentsButton"), ComponentSelectionPage::tr("Def&ault")) + .toString()); + } else { + m_checkDefault->setEnabled(false); + m_checkDefault->setObjectName(QLatin1String("ResetComponentsButton")); + m_checkDefault->setShortcut(QKeySequence(ComponentSelectionPage::tr("Alt+R", "reset to already installed components"))); + m_checkDefault->setText(hash.value(QLatin1String("ResetComponentsButton"), ComponentSelectionPage::tr("&Reset")).toString()); + } + hlayout = new QHBoxLayout; + hlayout->addWidget(m_checkDefault); + + m_checkAll = new QPushButton; + hlayout->addWidget(m_checkAll); + connect(m_checkAll, SIGNAL(clicked()), this, SLOT(selectAll())); + m_checkAll->setObjectName(QLatin1String("SelectAllComponentsButton")); + m_checkAll->setShortcut(QKeySequence(ComponentSelectionPage::tr("Alt+S", "select all components"))); + m_checkAll->setText(hash.value(QLatin1String("SelectAllComponentsButton"), ComponentSelectionPage::tr("&Select All")).toString()); + + m_uncheckAll = new QPushButton; + hlayout->addWidget(m_uncheckAll); + connect(m_uncheckAll, SIGNAL(clicked()), this, SLOT(deselectAll())); + m_uncheckAll->setObjectName(QLatin1String("DeselectAllComponentsButton")); + m_uncheckAll->setShortcut(QKeySequence(ComponentSelectionPage::tr("Alt+D", "deselect all components"))); + m_uncheckAll->setText(hash.value(QLatin1String("DeselectAllComponentsButton"), ComponentSelectionPage::tr("&Deselect All")) + .toString()); + + hlayout->addSpacerItem(new QSpacerItem(1, 1, QSizePolicy::MinimumExpanding, + QSizePolicy::MinimumExpanding)); + layout->addLayout(hlayout); + + connect(m_core, SIGNAL(finishAllComponentsReset()), this, SLOT(allComponentsChanged()), + Qt::QueuedConnection); + connect(m_core, SIGNAL(finishUpdaterComponentsReset()), this, SLOT(updaterComponentsChanged()), + Qt::QueuedConnection); + } + + void updateTreeView() + { + m_checkDefault->setVisible(m_core->isInstaller() || m_core->isPackageManager()); + if (m_treeView->selectionModel()) { + disconnect(m_treeView->selectionModel(), SIGNAL(currentChanged(QModelIndex, QModelIndex)), + this, SLOT(currentChanged(QModelIndex))); + disconnect(m_currentModel, SIGNAL(checkStateChanged(QModelIndex)), this, + SLOT(currentChanged(QModelIndex))); + } + + m_currentModel = m_core->isUpdater() ? m_updaterModel : m_allModel; + m_treeView->setModel(m_currentModel); + m_treeView->setExpanded(m_currentModel->index(0, 0), true); + + if (m_core->isInstaller()) { + m_treeView->setHeaderHidden(true); + for (int i = 1; i < m_currentModel->columnCount(); ++i) + m_treeView->hideColumn(i); + } else { + m_treeView->header()->setStretchLastSection(true); + for (int i = 0; i < m_currentModel->columnCount(); ++i) + m_treeView->resizeColumnToContents(i); + } + + bool hasChildren = false; + const int rowCount = m_currentModel->rowCount(); + for (int row = 0; row < rowCount && !hasChildren; ++row) + hasChildren = m_currentModel->hasChildren(m_currentModel->index(row, 0)); + m_treeView->setRootIsDecorated(hasChildren); + + connect(m_treeView->selectionModel(), SIGNAL(currentChanged(QModelIndex, QModelIndex)), + this, SLOT(currentChanged(QModelIndex))); + connect(m_currentModel, SIGNAL(checkStateChanged(QModelIndex)), this, + SLOT(currentChanged(QModelIndex))); + + m_treeView->setCurrentIndex(m_currentModel->index(0, 0)); + } + +public slots: + void currentChanged(const QModelIndex ¤t) + { + // if there is not selection or the current selected node didn't change, return + if (!current.isValid() || current != m_treeView->selectionModel()->currentIndex()) + return; + + m_descriptionLabel->setText(m_currentModel->data(m_currentModel->index(current.row(), + ComponentModelHelper::NameColumn, current.parent()), Qt::ToolTipRole).toString()); + + m_sizeLabel->clear(); + if (!m_core->isUninstaller()) { + Component *component = m_currentModel->componentFromIndex(current); + if (component && component->updateUncompressedSize() > 0) { + const QVariantHash hash = q->elementsForPage(QLatin1String("ComponentSelectionPage")); + m_sizeLabel->setText(hash.value(QLatin1String("ComponentSizeLabel"), + ComponentSelectionPage::tr("This component will occupy approximately %1 on your hard disk drive.")).toString() + .arg(m_currentModel->data(m_currentModel->index(current.row(), + ComponentModelHelper::UncompressedSizeColumn, current.parent())).toString())); + } + } + } + + void selectAll() + { + m_currentModel->selectAll(); + + m_checkAll->setEnabled(!m_currentModel->hasCheckedComponents()); + m_uncheckAll->setEnabled(m_currentModel->hasCheckedComponents()); + } + + void deselectAll() + { + m_currentModel->deselectAll(); + + m_checkAll->setEnabled(m_currentModel->hasCheckedComponents()); + m_uncheckAll->setEnabled(!m_currentModel->hasCheckedComponents()); + } + + void selectDefault() + { + m_currentModel->selectDefault(); + + // Do not apply special magic here to keep the enabled/ disabled state in sync with the checked + // components. We would need to implement the counter in the model, which has an unnecessary impact + // on the complexity and amount of code compared to what we gain in functionality. + m_checkAll->setEnabled(true); + m_uncheckAll->setEnabled(true); + } + +private slots: + void allComponentsChanged() + { + m_allModel->setRootComponents(m_core->rootComponents()); + } + + void updaterComponentsChanged() + { + m_updaterModel->setRootComponents(m_core->updaterComponents()); + } + +public: + ComponentSelectionPage *q; + PackageManagerCore *m_core; + QTreeView *m_treeView; + ComponentModel *m_allModel; + ComponentModel *m_updaterModel; + ComponentModel *m_currentModel; + QLabel *m_sizeLabel; + QLabel *m_descriptionLabel; + QPushButton *m_checkAll; + QPushButton *m_uncheckAll; + QPushButton *m_checkDefault; +}; + + +// -- ComponentSelectionPage + +/*! + \class QInstaller::ComponentSelectionPage + On this page the user can select and deselect what he wants to be installed. +*/ +ComponentSelectionPage::ComponentSelectionPage(PackageManagerCore *core) + : PackageManagerPage(core) + , d(new Private(this, core)) +{ + setPixmap(QWizard::LogoPixmap, logoPixmap()); + setPixmap(QWizard::WatermarkPixmap, QPixmap()); + setObjectName(QLatin1String("ComponentSelectionPage")); + setTitle(titleForPage(QLatin1String("ComponentSelectionPage"), tr("Select Components"))); +} + +ComponentSelectionPage::~ComponentSelectionPage() +{ + delete d; +} + +void ComponentSelectionPage::entering() +{ + static const char *strings[] = { + QT_TR_NOOP("Please select the components you want to update."), + QT_TR_NOOP("Please select the components you want to install."), + QT_TR_NOOP("Please select the components you want to uninstall."), + QT_TR_NOOP("Select the components to install. Deselect installed components to uninstall them.") + }; + + int index = 0; + PackageManagerCore *core = packageManagerCore(); + if (core->isInstaller()) index = 1; + if (core->isUninstaller()) index = 2; + if (core->isPackageManager()) index = 3; + setSubTitle(subTitleForPage(QLatin1String("ComponentSelectionPage"), tr(strings[index]))); + + d->updateTreeView(); + setModified(isComplete()); +} + +void ComponentSelectionPage::showEvent(QShowEvent *event) +{ + // remove once we deprecate isSelected, setSelected etc... + if (!event->spontaneous()) + packageManagerCore()->resetComponentsToUserCheckedState(); + QWizardPage::showEvent(event); +} + +void ComponentSelectionPage::selectAll() +{ + d->selectAll(); +} + +void ComponentSelectionPage::deselectAll() +{ + d->deselectAll(); +} + +void ComponentSelectionPage::selectDefault() +{ + if (packageManagerCore()->isInstaller()) + d->selectDefault(); +} + +/*! + Selects the component with /a id in the component tree. +*/ +void ComponentSelectionPage::selectComponent(const QString &id) +{ + const QModelIndex &idx = d->m_currentModel->indexFromComponentName(id); + if (idx.isValid()) + d->m_currentModel->setData(idx, Qt::Checked, Qt::CheckStateRole); +} + +/*! + Deselects the component with /a id in the component tree. +*/ +void ComponentSelectionPage::deselectComponent(const QString &id) +{ + const QModelIndex &idx = d->m_currentModel->indexFromComponentName(id); + if (idx.isValid()) + d->m_currentModel->setData(idx, Qt::Unchecked, Qt::CheckStateRole); +} + +void ComponentSelectionPage::setModified(bool modified) +{ + setComplete(modified); +} + +bool ComponentSelectionPage::isComplete() const +{ + if (packageManagerCore()->isInstaller() || packageManagerCore()->isUpdater()) + return d->m_currentModel->hasCheckedComponents(); + return !d->m_currentModel->defaultCheckState(); +} + + +// -- TargetDirectoryPage + +TargetDirectoryPage::TargetDirectoryPage(PackageManagerCore *core) + : PackageManagerPage(core) +{ + setPixmap(QWizard::LogoPixmap, logoPixmap()); + setPixmap(QWizard::WatermarkPixmap, QPixmap()); + setObjectName(QLatin1String("TargetDirectoryPage")); + setSubTitle(subTitleForPage(QLatin1String("TargetDirectoryPage"))); + setTitle(titleForPage(QLatin1String("TargetDirectoryPage"), tr("Installation Folder"))); + + QVBoxLayout *layout = new QVBoxLayout(this); + + QLabel *msgLabel = new QLabel(this); + msgLabel->setWordWrap(true); + msgLabel->setObjectName(QLatin1String("MessageLabel")); + const QVariantHash hash = elementsForPage(QLatin1String("TargetDirectoryPage")); + msgLabel->setText(hash.value(QLatin1String("MessageLabel"), tr("Please specify the folder " + "where %1 will be installed.")).toString().arg(productName())); + layout->addWidget(msgLabel); + + QHBoxLayout *hlayout = new QHBoxLayout; + + m_lineEdit = new QLineEdit(this); + m_lineEdit->setObjectName(QLatin1String("TargetDirectoryLineEdit")); + connect(m_lineEdit, SIGNAL(textChanged(QString)), this, SIGNAL(completeChanged())); + hlayout->addWidget(m_lineEdit); + + QPushButton *browseButton = new QPushButton(this); + browseButton->setObjectName(QLatin1String("BrowseDirectoryButton")); + connect(browseButton, SIGNAL(clicked()), this, SLOT(dirRequested())); + browseButton->setShortcut(QKeySequence(tr("Alt+R", "browse file system to choose a file"))); + browseButton->setText(hash.value(QLatin1String("BrowseDirectoryButton"), tr("B&rowse...")) + .toString()); + hlayout->addWidget(browseButton); + + layout->addLayout(hlayout); + setLayout(layout); +} + +QString TargetDirectoryPage::targetDir() const +{ + return m_lineEdit->text(); +} + +void TargetDirectoryPage::setTargetDir(const QString &dirName) +{ + m_lineEdit->setText(dirName); +} + +void TargetDirectoryPage::initializePage() +{ + QString targetDir = packageManagerCore()->value(scTargetDir); + if (targetDir.isEmpty()) { + targetDir = QDir::homePath() + QDir::separator(); + // prevent spaces in the default target directory + if (targetDir.contains(QLatin1Char(' '))) + targetDir = QDir::rootPath(); + targetDir += productName().remove(QLatin1Char(' ')); + } + m_lineEdit->setText(QDir::toNativeSeparators(QDir(targetDir).absolutePath())); + + PackageManagerPage::initializePage(); +} + +bool TargetDirectoryPage::validatePage() +{ + const QVariantHash hash = elementsForPage(QLatin1String("TargetDirectoryPage")); + if (targetDir().isEmpty()) { + MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), + QLatin1String("EmptyTargetDirectoryMessage"), tr("Error"), hash + .value(QLatin1String("EmptyTargetDirectoryMessage"), tr("The install directory cannot be " + "empty, please specify a valid folder.")).toString(), QMessageBox::Ok); + return false; + } + + const QDir dir(targetDir()); + // it exists, but is empty (might be created by the Browse button (getExistingDirectory) + if (dir.exists() && dir.entryList(QDir::NoDotAndDotDot).isEmpty()) + return true; + + if (dir.exists() && dir.isReadable()) { + // it exists, but is not empty + if (dir == QDir::root()) { + MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), + QLatin1String("ForbiddenTargetDirectoryMessage"), tr("Error"), hash + .value(QLatin1String("ForbiddenTargetDirectoryMessage"), tr("As the install directory is " + "completely deleted, installing in %1 is forbidden.")).toString().arg(QDir::rootPath()), + QMessageBox::Ok); + return false; + } + + if (!QVariant(packageManagerCore()->value(scRemoveTargetDir)).toBool()) + return true; + + return MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(), + QLatin1String("OverwriteTargetDirectoryMessage"), tr("Warning"), hash + .value(QLatin1String("OverwriteTargetDirectoryMessage"), tr("You have selected an existing, " + "non-empty folder for installation. Note that it will be completely wiped on uninstallation of " + "this application. It is not advisable to install into this folder as installation might fail. " + "Do you want to continue?")).toString(), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes; + } + return true; +} + +void TargetDirectoryPage::entering() +{ +} + +void TargetDirectoryPage::leaving() +{ + packageManagerCore()->setValue(scTargetDir, targetDir()); +} + +void TargetDirectoryPage::targetDirSelected() +{ +} + +void TargetDirectoryPage::dirRequested() +{ + const QVariantHash hash = elementsForPage(QLatin1String("TargetDirectoryPage")); + const QString newDirName = QFileDialog::getExistingDirectory(this, hash + .value(QLatin1String("SelectInstallationFolderCaption"), tr("Select Installation Folder")).toString(), + targetDir()); + if (newDirName.isEmpty() || newDirName == targetDir()) + return; + m_lineEdit->setText(QDir::toNativeSeparators(newDirName)); +} + + +// -- StartMenuDirectoryPage + +StartMenuDirectoryPage::StartMenuDirectoryPage(PackageManagerCore *core) + : PackageManagerPage(core) +{ + setPixmap(QWizard::LogoPixmap, logoPixmap()); + setPixmap(QWizard::WatermarkPixmap, QPixmap()); + setObjectName(QLatin1String("StartMenuDirectoryPage")); + setTitle(titleForPage(QLatin1String("StartMenuDirectoryPage"), tr("Start Menu shortcuts"))); + setSubTitle(subTitleForPage(QLatin1String("StartMenuDirectoryPage"), tr("Select the Start Menu in which " + "you would like to create the program's shortcuts. You can also enter a name to create a new folder."))); + + m_lineEdit = new QLineEdit(this); + m_lineEdit->setObjectName(QLatin1String("LineEdit")); + + QString startMenuDir = core->value(scStartMenuDir); + if (startMenuDir.isEmpty()) + startMenuDir = productName(); + m_lineEdit->setText(startMenuDir); + + // grab existing start menu folders + QSettings user(QLatin1String("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\" + "Explorer\\User Shell Folders"), QSettings::NativeFormat); + // User Shell Folders uses %USERPROFILE% + startMenuPath = replaceWindowsEnvironmentVariables(user.value(QLatin1String("Programs"), QString()) + .toString()); + core->setValue(QLatin1String("DesktopDir"), replaceWindowsEnvironmentVariables(user + .value(QLatin1String("Desktop")).toString())); + + QDir dir(startMenuPath); // user only dirs + QStringList dirs = dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot); + + if (core->value(QLatin1String("AllUsers")) == QLatin1String("true")) { + qDebug() << "AllUsers set. Using HKEY_LOCAL_MACHINE"; + QSettings system(QLatin1String("HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\" + "Explorer\\Shell Folders"), QSettings::NativeFormat); + startMenuPath = system.value(QLatin1String("Common Programs"), QString()).toString(); + core->setValue(QLatin1String("DesktopDir"),system.value(QLatin1String("Desktop")).toString()); + + dir.setPath(startMenuPath); // system only dirs + dirs += dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot); + } + + qDebug() << "StartMenuPath: \t" << startMenuPath; + qDebug() << "DesktopDir: \t" << core->value(QLatin1String("DesktopDir")); + + m_listWidget = new QListWidget(this); + if (!dirs.isEmpty()) { + dirs.removeDuplicates(); + foreach (const QString &dir, dirs) + new QListWidgetItem(dir, m_listWidget); + } + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->addWidget(m_lineEdit); + layout->addWidget(m_listWidget); + + setLayout(layout); + + connect(m_listWidget, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, + SLOT(currentItemChanged(QListWidgetItem*))); +} + +QString StartMenuDirectoryPage::startMenuDir() const +{ + return m_lineEdit->text(); +} + +void StartMenuDirectoryPage::setStartMenuDir(const QString &startMenuDir) +{ + m_lineEdit->setText(startMenuDir); +} + +void StartMenuDirectoryPage::leaving() +{ + packageManagerCore()->setValue(scStartMenuDir, startMenuPath + QDir::separator() + startMenuDir()); +} + +void StartMenuDirectoryPage::currentItemChanged(QListWidgetItem *current) +{ + if (current) { + QString dir = current->data(Qt::DisplayRole).toString(); + if (!dir.isEmpty()) + dir += QDir::separator(); + setStartMenuDir(dir + packageManagerCore()->value(scStartMenuDir)); + } +} + + +// -- ReadyForInstallationPage + +ReadyForInstallationPage::ReadyForInstallationPage(PackageManagerCore *core) + : PackageManagerPage(core) + , m_msgLabel(new QLabel) +{ + setPixmap(QWizard::LogoPixmap, logoPixmap()); + setPixmap(QWizard::WatermarkPixmap, QPixmap()); + setObjectName(QLatin1String("ReadyForInstallationPage")); + setSubTitle(subTitleForPage(QLatin1String("ReadyForInstallationPage"))); + + QVBoxLayout *baseLayout = new QVBoxLayout(); + baseLayout->setObjectName(QLatin1String("BaseLayout")); + + QVBoxLayout *topLayout = new QVBoxLayout(); + topLayout->setObjectName(QLatin1String("TopLayout")); + + m_msgLabel->setWordWrap(true); + m_msgLabel->setObjectName(QLatin1String("MessageLabel")); + m_msgLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + topLayout->addWidget(m_msgLabel); + baseLayout->addLayout(topLayout); + + m_taskDetailsButton = new QPushButton(tr("&Show Details"), this); + m_taskDetailsButton->setObjectName(QLatin1String("TaskDetailsButton")); + m_taskDetailsButton->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); + connect(m_taskDetailsButton, SIGNAL(clicked()), this, SLOT(toggleDetails())); + topLayout->addWidget(m_taskDetailsButton); + + QVBoxLayout *bottomLayout = new QVBoxLayout(); + bottomLayout->setObjectName(QLatin1String("BottomLayout")); + bottomLayout->addStretch(); + + m_taskDetailsBrowser = new QTextBrowser(this); + m_taskDetailsBrowser->setReadOnly(true); + m_taskDetailsBrowser->setObjectName(QLatin1String("TaskDetailsBrowser")); + m_taskDetailsBrowser->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_taskDetailsBrowser->setVisible(false); + bottomLayout->addWidget(m_taskDetailsBrowser); + bottomLayout->setStretch(1, 10); + baseLayout->addLayout(bottomLayout); + + setLayout(baseLayout); +} + + +/*! + \reimp +*/ +void ReadyForInstallationPage::entering() +{ + setCommitPage(false); + + if (packageManagerCore()->isUninstaller()) { + m_taskDetailsButton->setVisible(false); + m_taskDetailsBrowser->setVisible(false); + setButtonText(QWizard::CommitButton, tr("U&ninstall")); + setTitle(titleForPage(objectName(), tr("Ready to Uninstall"))); + m_msgLabel->setText(tr("Setup is now ready to begin removing %1 from your computer.<br>" + "<font color=\"red\">The program dir %2 will be deleted completely</font>, " + "including all content in that directory!") + .arg(productName(), QDir::toNativeSeparators(QDir(packageManagerCore()->value(scTargetDir)) + .absolutePath()))); + setCommitPage(true); + return; + } else if (packageManagerCore()->isPackageManager() || packageManagerCore()->isUpdater()) { + setButtonText(QWizard::CommitButton, tr("U&pdate")); + setTitle(titleForPage(objectName(), tr("Ready to Update Packages"))); + m_msgLabel->setText(tr("Setup is now ready to begin updating your installation.")); + } else { + Q_ASSERT(packageManagerCore()->isInstaller()); + setButtonText(QWizard::CommitButton, tr("&Install")); + setTitle(titleForPage(objectName(), tr("Ready to Install"))); + m_msgLabel->setText(tr("Setup is now ready to begin installing %1 on your computer.") + .arg(productName())); + } + + refreshTaskDetailsBrowser(); + + const VolumeInfo tempVolume = VolumeInfo::fromPath(QDir::tempPath()); + const VolumeInfo targetVolume = VolumeInfo::fromPath(packageManagerCore()->value(scTargetDir)); + + const quint64 tempVolumeAvailableSize = tempVolume.availableSize(); + const quint64 installVolumeAvailableSize = targetVolume.availableSize(); + + // at the moment there is no better way to check this + if (targetVolume.size() == 0 && installVolumeAvailableSize == 0) { + qDebug() << QString::fromLatin1("Could not determine available space on device. Volume descriptor: %1," + "Mount path: %2. Continue silently.").arg(targetVolume.volumeDescriptor(), targetVolume.mountPath()); + setCommitPage(true); + return; // TODO: Shouldn't this also disable the "Next" button? + } + + const bool tempOnSameVolume = (targetVolume == tempVolume); + if (tempOnSameVolume) { + qDebug() << "Tmp and install folder are on the same volume. Volume mount point:" << targetVolume + .mountPath() << "Free space available:" << humanReadableSize(installVolumeAvailableSize); + } else { + qDebug() << "Tmp is on a different volume than the install folder. Tmp volume mount point:" + << tempVolume.mountPath() << "Free space available:" << humanReadableSize(tempVolumeAvailableSize) + << "Install volume mount point:" << targetVolume.mountPath() << "Free space " + "available:" << humanReadableSize(installVolumeAvailableSize); + } + + const quint64 extraSpace = 256 * 1024 * 1024LL; + quint64 required(packageManagerCore()->requiredDiskSpace()); + quint64 tempRequired(packageManagerCore()->requiredTemporaryDiskSpace()); + if (required < extraSpace) { + required += 0.1 * required; + tempRequired += 0.1 * tempRequired; + } else { + required += extraSpace; + tempRequired += extraSpace; + } + + quint64 repositorySize = 0; + const bool createLocalRepository = packageManagerCore()->createLocalRepositoryFromBinary(); + if (createLocalRepository) { + repositorySize = QFile(QCoreApplication::applicationFilePath()).size(); + required += repositorySize; // if we create a local repository, take that space into account as well + } + + qDebug() << "Installation space required:" << humanReadableSize(required) << "Temporary space required:" + << humanReadableSize(tempRequired) << "Local repository size:" << humanReadableSize(repositorySize); + + if (tempOnSameVolume && (installVolumeAvailableSize <= (required + tempRequired))) { + m_msgLabel->setText(tr("Not enough disk space to store temporary files and the installation! " + "Available space: %1, at least required %2.").arg(humanReadableSize(installVolumeAvailableSize), + humanReadableSize(required + tempRequired))); + return; + } + + if (installVolumeAvailableSize < required) { + m_msgLabel->setText(tr("Not enough disk space to store all selected components! Available space: %1, " + "at least required: %2.").arg(humanReadableSize(installVolumeAvailableSize), + humanReadableSize(required))); + return; + } + + if (tempVolumeAvailableSize < tempRequired) { + m_msgLabel->setText(tr("Not enough disk space to store temporary files! Available space: %1, at " + "least required: %2.").arg(humanReadableSize(tempVolumeAvailableSize), + humanReadableSize(tempRequired))); + return; + } + + if (installVolumeAvailableSize - required < 0.01 * targetVolume.size()) { + // warn for less than 1% of the volume's space being free + m_msgLabel->setText(tr("The volume you selected for installation seems to have sufficient space for " + "installation, but there will be less than 1% of the volume's space available afterwards. %1") + .arg(m_msgLabel->text())); + } else if (installVolumeAvailableSize - required < 100 * 1024 * 1024LL) { + // warn for less than 100MB being free + m_msgLabel->setText(tr("The volume you selected for installation seems to have sufficient space for " + "installation, but there will be less than 100 MB available afterwards. %1") + .arg(m_msgLabel->text())); + } + setCommitPage(true); +} + +void ReadyForInstallationPage::refreshTaskDetailsBrowser() +{ + QString htmlOutput; + QString lastInstallReason; + if (!packageManagerCore()->calculateComponentsToUninstall() || + !packageManagerCore()->calculateComponentsToInstall()) { + htmlOutput.append(QString::fromLatin1("<h2><font color=\"red\">%1</font></h2><ul>") + .arg(tr("Can not resolve all dependencies!"))); + //if we have a missing dependency or a recursion we can display it + if (!packageManagerCore()->componentsToInstallError().isEmpty()) { + htmlOutput.append(QString::fromLatin1("<li> %1 </li>").arg( + packageManagerCore()->componentsToInstallError())); + } + htmlOutput.append(QLatin1String("</ul>")); + m_taskDetailsBrowser->setHtml(htmlOutput); + if (!m_taskDetailsBrowser->isVisible()) + toggleDetails(); + setCommitPage(false); + return; + } + + // In case of updater mode we don't uninstall components. + if (!packageManagerCore()->isUpdater()) { + QList<Component*> componentsToRemove = packageManagerCore()->componentsToUninstall(); + if (!componentsToRemove.isEmpty()) { + htmlOutput.append(QString::fromLatin1("<h3>%1</h3><ul>").arg(tr("Components about to be removed."))); + foreach (Component *component, componentsToRemove) + htmlOutput.append(QString::fromLatin1("<li> %1 </li>").arg(component->name())); + htmlOutput.append(QLatin1String("</ul>")); + } + } + + foreach (Component *component, packageManagerCore()->orderedComponentsToInstall()) { + const QString installReason = packageManagerCore()->installReason(component); + if (lastInstallReason != installReason) { + if (!lastInstallReason.isEmpty()) // means we had to close the previous list + htmlOutput.append(QLatin1String("</ul>")); + htmlOutput.append(QString::fromLatin1("<h3>%1</h3><ul>").arg(installReason)); + lastInstallReason = installReason; + } + htmlOutput.append(QString::fromLatin1("<li> %1 </li>").arg(component->name())); + } + m_taskDetailsBrowser->setHtml(htmlOutput); +} + +void ReadyForInstallationPage::toggleDetails() +{ + const bool visible = !m_taskDetailsBrowser->isVisible(); + m_taskDetailsBrowser->setVisible(visible); + m_taskDetailsButton->setText(visible ? tr("&Hide Details") : tr("&Show Details")); +} + +void ReadyForInstallationPage::leaving() +{ + setButtonText(QWizard::CommitButton, gui()->defaultButtonText(QWizard::CommitButton)); +} + +/*! + \reimp +*/ +bool ReadyForInstallationPage::isComplete() const +{ + return isCommitPage(); +} + + +// -- PerformInstallationPage + +/*! + \class QInstaller::PerformInstallationPage + On this page the user can see on a progress bar how far the current installation is. +*/ +PerformInstallationPage::PerformInstallationPage(PackageManagerCore *core) + : PackageManagerPage(core) + , m_performInstallationForm(new PerformInstallationForm(this)) +{ + setPixmap(QWizard::LogoPixmap, logoPixmap()); + setPixmap(QWizard::WatermarkPixmap, QPixmap()); + setObjectName(QLatin1String("PerformInstallationPage")); + setSubTitle(subTitleForPage(QLatin1String("PerformInstallationPage"))); + + m_performInstallationForm->setupUi(this); + + connect(ProgressCoordinator::instance(), SIGNAL(detailTextChanged(QString)), m_performInstallationForm, + SLOT(appendProgressDetails(QString))); + connect(ProgressCoordinator::instance(), SIGNAL(detailTextResetNeeded()), m_performInstallationForm, + SLOT(clearDetailsBrowser())); + connect(m_performInstallationForm, SIGNAL(showDetailsChanged()), this, SLOT(toggleDetailsWereChanged())); + + connect(core, SIGNAL(installationStarted()), this, SLOT(installationStarted())); + connect(core, SIGNAL(uninstallationStarted()), this, SLOT(installationStarted())); + connect(core, SIGNAL(installationFinished()), this, SLOT(installationFinished())); + connect(core, SIGNAL(uninstallationFinished()), this, SLOT(installationFinished())); + connect(core, SIGNAL(titleMessageChanged(QString)), this, SLOT(setTitleMessage(QString))); + connect(this, SIGNAL(setAutomatedPageSwitchEnabled(bool)), core, + SIGNAL(setAutomatedPageSwitchEnabled(bool))); + + m_performInstallationForm->setDetailsWidgetVisible(true); +} + +PerformInstallationPage::~PerformInstallationPage() +{ + delete m_performInstallationForm; +} + +bool PerformInstallationPage::isAutoSwitching() const +{ + return !m_performInstallationForm->isShowingDetails(); +} + +// -- protected + +void PerformInstallationPage::entering() +{ + setComplete(false); + setCommitPage(true); + + const QString productName = packageManagerCore()->value(QLatin1String("ProductName")); + if (packageManagerCore()->isUninstaller()) { + setButtonText(QWizard::CommitButton, tr("&Uninstall")); + setTitle(titleForPage(objectName(), tr("Uninstalling %1")).arg(productName)); + + QTimer::singleShot(30, packageManagerCore(), SLOT(runUninstaller())); + } else if (packageManagerCore()->isPackageManager() || packageManagerCore()->isUpdater()) { + setButtonText(QWizard::CommitButton, tr("&Update")); + setTitle(titleForPage(objectName(), tr("Updating components of %1")).arg(productName)); + + QTimer::singleShot(30, packageManagerCore(), SLOT(runPackageUpdater())); + } else { + setButtonText(QWizard::CommitButton, tr("&Install")); + setTitle(titleForPage(objectName(), tr("Installing %1")).arg(productName)); + + QTimer::singleShot(30, packageManagerCore(), SLOT(runInstaller())); + } + + m_performInstallationForm->enableDetails(); + emit setAutomatedPageSwitchEnabled(true); +} + +void PerformInstallationPage::leaving() +{ + setButtonText(QWizard::CommitButton, gui()->defaultButtonText(QWizard::CommitButton)); +} + +// -- public slots + +void PerformInstallationPage::setTitleMessage(const QString &title) +{ + setTitle(title); +} + +// -- private slots + +void PerformInstallationPage::installationStarted() +{ + m_performInstallationForm->startUpdateProgress(); +} + +void PerformInstallationPage::installationFinished() +{ + m_performInstallationForm->stopUpdateProgress(); + if (!isAutoSwitching()) { + m_performInstallationForm->scrollDetailsToTheEnd(); + m_performInstallationForm->setDetailsButtonEnabled(false); + + setComplete(true); + setButtonText(QWizard::CommitButton, gui()->defaultButtonText(QWizard::NextButton)); + } +} + +void PerformInstallationPage::toggleDetailsWereChanged() +{ + emit setAutomatedPageSwitchEnabled(isAutoSwitching()); +} + + +// -- FinishedPage + +FinishedPage::FinishedPage(PackageManagerCore *core) + : PackageManagerPage(core) + , m_commitButton(0) +{ + setObjectName(QLatin1String("FinishedPage")); + setPixmap(QWizard::WatermarkPixmap, watermarkPixmap()); + setSubTitle(subTitleForPage(QLatin1String("FinishedPage"))); + setTitle(titleForPage(QLatin1String("FinishedPage"), tr("Completing the %1 Wizard")).arg(productName())); + + m_msgLabel = new QLabel(this); + m_msgLabel->setWordWrap(true); + m_msgLabel->setObjectName(QLatin1String("MessageLabel")); + + const QVariantHash hash = elementsForPage(QLatin1String("FinishedPage")); +#ifdef Q_WS_MAC + m_msgLabel->setText(hash.value(QLatin1String("MessageLabel"), tr("Click Done to exit the %1 " + "Wizard.")).toString().arg(productName())); +#else + m_msgLabel->setText(hash.value(QLatin1String("MessageLabel"), tr("Click Finish to exit the " + "%1 Wizard.")).toString().arg(productName())); +#endif + + m_runItCheckBox = new QCheckBox(this); + m_runItCheckBox->setObjectName(QLatin1String("RunItCheckBox")); + m_runItCheckBox->setChecked(true); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->addWidget(m_msgLabel); + layout->addWidget(m_runItCheckBox); + setLayout(layout); +} + +void FinishedPage::entering() +{ + if (m_commitButton) { + disconnect(m_commitButton, SIGNAL(clicked()), this, SLOT(handleFinishClicked())); + m_commitButton = 0; + } + + setCommitPage(true); + if (packageManagerCore()->isUpdater() || packageManagerCore()->isPackageManager()) { +#ifdef Q_WS_MAC + gui()->setOption(QWizard::NoCancelButton, false); +#endif + if (QAbstractButton *cancel = gui()->button(QWizard::CancelButton)) { + m_commitButton = cancel; + cancel->setEnabled(true); + cancel->setVisible(true); + } + setButtonText(QWizard::CommitButton, tr("Restart")); + setButtonText(QWizard::CancelButton, gui()->defaultButtonText(QWizard::FinishButton)); + } else { + if (packageManagerCore()->isInstaller()) + m_commitButton = wizard()->button(QWizard::FinishButton); + + gui()->setOption(QWizard::NoCancelButton, true); + if (QAbstractButton *cancel = gui()->button(QWizard::CancelButton)) + cancel->setVisible(false); + } + + gui()->updateButtonLayout(); + + if (m_commitButton) { + disconnect(m_commitButton, SIGNAL(clicked()), this, SLOT(handleFinishClicked())); + connect(m_commitButton, SIGNAL(clicked()), this, SLOT(handleFinishClicked())); + } + + if (packageManagerCore()->status() == PackageManagerCore::Success) { + const QString finishedText = packageManagerCore()->value(QLatin1String("FinishedText")); + if (!finishedText.isEmpty()) + m_msgLabel->setText(finishedText); + + if (!packageManagerCore()->value(scRunProgram).isEmpty()) { + m_runItCheckBox->show(); + m_runItCheckBox->setText(packageManagerCore()->value(scRunProgramDescription, tr("Run %1 now.")) + .arg(productName())); + return; // job done + } + } else { + // TODO: how to handle this using the config.xml + setTitle(tr("The %1 Wizard failed.").arg(productName())); + } + + m_runItCheckBox->hide(); + m_runItCheckBox->setChecked(false); +} + +void FinishedPage::leaving() +{ +#ifdef Q_WS_MAC + gui()->setOption(QWizard::NoCancelButton, true); +#endif + + if (QAbstractButton *cancel = gui()->button(QWizard::CancelButton)) + cancel->setVisible(false); + gui()->updateButtonLayout(); + + setButtonText(QWizard::CommitButton, gui()->defaultButtonText(QWizard::CommitButton)); + setButtonText(QWizard::CancelButton, gui()->defaultButtonText(QWizard::CancelButton)); +} + +void FinishedPage::handleFinishClicked() +{ + const QString program = packageManagerCore()->replaceVariables(packageManagerCore()->value(scRunProgram)); + if (!m_runItCheckBox->isChecked() || program.isEmpty()) + return; + + qDebug() << "starting" << program; + QProcess::startDetached(program); +} + + +// -- RestartPage + +RestartPage::RestartPage(PackageManagerCore *core) + : PackageManagerPage(core) +{ + setObjectName(QLatin1String("RestartPage")); + setPixmap(QWizard::WatermarkPixmap, watermarkPixmap()); + setSubTitle(subTitleForPage(QLatin1String("RestartPage"))); + setTitle(titleForPage(QLatin1String("RestartPage"), tr("Completing the %1 Setup Wizard")) + .arg(productName())); + + setFinalPage(false); + setCommitPage(false); +} + +int RestartPage::nextId() const +{ + return PackageManagerCore::Introduction; +} + +void RestartPage::entering() +{ + if (!packageManagerCore()->needsRestart()) { + if (QAbstractButton *finish = wizard()->button(QWizard::FinishButton)) + finish->setVisible(false); + QMetaObject::invokeMethod(this, "restart", Qt::QueuedConnection); + } else { + gui()->accept(); + } +} + +void RestartPage::leaving() +{ +} + +#include "packagemanagergui.moc" +#include "moc_packagemanagergui.cpp" diff --git a/src/libs/installer/packagemanagergui.h b/src/libs/installer/packagemanagergui.h new file mode 100644 index 000000000..b3f1bdf88 --- /dev/null +++ b/src/libs/installer/packagemanagergui.h @@ -0,0 +1,418 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef PACKAGEMANAGERGUI_H +#define PACKAGEMANAGERGUI_H + +#include "packagemanagercore.h" + +#include <QtCore/QEvent> +#include <QtCore/QMetaType> + +#include <QtGui/QWizard> +#include <QtGui/QWizardPage> + +// FIXME: move to private classes +QT_BEGIN_NAMESPACE +class QAbstractButton; +class QCheckBox; +class QLabel; +class QLineEdit; +class QListWidget; +class QListWidgetItem; +class QRadioButton; +class QTextBrowser; +class QScriptEngine; +QT_END_NAMESPACE + +namespace QInstaller { + +class PackageManagerCore; +class PackageManagerPage; +class PerformInstallationForm; + + +// -- PackageManagerGui + +class INSTALLER_EXPORT PackageManagerGui : public QWizard +{ + Q_OBJECT + +public: + explicit PackageManagerGui(PackageManagerCore *core, QWidget *parent = 0); + virtual ~PackageManagerGui(); + virtual void init() = 0; + + void loadControlScript(const QString& scriptPath); + void callControlScriptMethod(const QString& methodName); + + QScriptEngine *controlScriptEngine() const; + + Q_INVOKABLE PackageManagerPage* page(int pageId) const; + Q_INVOKABLE QWidget* pageWidgetByObjectName(const QString& name) const; + Q_INVOKABLE QWidget* currentPageWidget() const; + Q_INVOKABLE QString defaultButtonText(int wizardButton) const; + Q_INVOKABLE void clickButton(int wizardButton, int delayInMs = 0); + + Q_INVOKABLE void showSettingsButton(bool show); + Q_INVOKABLE void setSettingsButtonEnabled(bool enable); + + void updateButtonLayout(); + +Q_SIGNALS: + void interrupted(); + void languageChanged(); + void finishButtonClicked(); + void gotRestarted(); + void settingsButtonClicked(); + +public Q_SLOTS: + void cancelButtonClicked(); + void reject(); + void rejectWithoutPrompt(); + void showFinishedPage(); + void setModified(bool value); + +protected Q_SLOTS: + void wizardPageInsertionRequested(QWidget *widget, QInstaller::PackageManagerCore::WizardPage page); + void wizardPageRemovalRequested(QWidget *widget); + void wizardWidgetInsertionRequested(QWidget *widget, QInstaller::PackageManagerCore::WizardPage page); + void wizardWidgetRemovalRequested(QWidget *widget); + void wizardPageVisibilityChangeRequested(bool visible, int page); + void slotCurrentPageChanged(int id); + void delayedControlScriptExecution(int id); + + void setAutomatedPageSwitchEnabled(bool request); + +private Q_SLOTS: + void onLanguageChanged(); + void customButtonClicked(int which); + +protected: + bool event(QEvent *event); + void showEvent(QShowEvent *event); + PackageManagerCore *packageManagerCore() const { return m_core; } + +private: + class Private; + Private *const d; + PackageManagerCore *m_core; +}; + + +// -- PackageManagerPage + +class INSTALLER_EXPORT PackageManagerPage : public QWizardPage +{ + Q_OBJECT + +public: + explicit PackageManagerPage(PackageManagerCore *core); + virtual ~PackageManagerPage() {} + + virtual QPixmap logoPixmap() const; + virtual QString productName() const; + virtual QPixmap watermarkPixmap() const; + + virtual bool isComplete() const; + void setComplete(bool complete); + + virtual bool isInterruptible() const { return false; } + PackageManagerGui* gui() const { return qobject_cast<PackageManagerGui*>(wizard()); } + +protected: + PackageManagerCore *packageManagerCore() const; + QVariantHash elementsForPage(const QString &pageName) const; + + QString titleForPage(const QString &pageName, const QString &value = QString()) const; + QString subTitleForPage(const QString &pageName, const QString &value = QString()) const; + + // Inserts widget into the same layout like a sibling identified + // by its name. Default position is just behind the sibling. + virtual void insertWidget(QWidget *widget, const QString &siblingName, int offset = 1); + virtual QWidget *findWidget(const QString &objectName) const; + + virtual void setVisible(bool visible); // reimp + virtual int nextId() const; // reimp + + virtual void entering() {} // called on entering + virtual void leaving() {} // called on leaving + + bool isConstructing() const { return m_fresh; } + +private: + QString titleFromHash(const QVariantHash &hash, const QString &value = QString()) const; + +private: + bool m_fresh; + bool m_complete; + + PackageManagerCore *m_core; +}; + + +// -- IntroductionPage + +class INSTALLER_EXPORT IntroductionPage : public PackageManagerPage +{ + Q_OBJECT + +public: + explicit IntroductionPage(PackageManagerCore *core); + + void setWidget(QWidget *widget); + void setText(const QString &text); + +private: + QLabel *m_msgLabel; + QWidget *m_widget; +}; + + +// -- LicenseAgreementPage + +class INSTALLER_EXPORT LicenseAgreementPage : public PackageManagerPage +{ + Q_OBJECT + class ClickForwarder; + +public: + explicit LicenseAgreementPage(PackageManagerCore *core); + + void entering(); + bool isComplete() const; + +private Q_SLOTS: + void openLicenseUrl(const QUrl &url); + void currentItemChanged(QListWidgetItem *current); + +private: + void addLicenseItem(const QHash<QString, QPair<QString, QString> > &hash); + +private: + QTextBrowser *m_textBrowser; + QListWidget *m_licenseListWidget; + + QRadioButton *m_acceptRadioButton; + QRadioButton *m_rejectRadioButton; +}; + + +// -- ComponentSelectionPage + +class INSTALLER_EXPORT ComponentSelectionPage : public PackageManagerPage +{ + Q_OBJECT + +public: + explicit ComponentSelectionPage(PackageManagerCore *core); + ~ComponentSelectionPage(); + + bool isComplete() const; + + Q_INVOKABLE void selectAll(); + Q_INVOKABLE void deselectAll(); + Q_INVOKABLE void selectDefault(); + Q_INVOKABLE void selectComponent(const QString &id); + Q_INVOKABLE void deselectComponent(const QString &id); + +protected: + void entering(); + void showEvent(QShowEvent *event); + +private Q_SLOTS: + void setModified(bool modified); + +private: + class Private; + Private *d; +}; + + +// -- TargetDirectoryPage + +class INSTALLER_EXPORT TargetDirectoryPage : public PackageManagerPage +{ + Q_OBJECT + +public: + explicit TargetDirectoryPage(PackageManagerCore *core); + QString targetDir() const; + void setTargetDir(const QString &dirName); + + void initializePage(); + bool validatePage(); + +protected: + void entering(); + void leaving(); + +private Q_SLOTS: + void targetDirSelected(); + void dirRequested(); + +private: + QLineEdit *m_lineEdit; +}; + + +// -- StartMenuDirectoryPage + +class INSTALLER_EXPORT StartMenuDirectoryPage : public PackageManagerPage +{ + Q_OBJECT + +public: + explicit StartMenuDirectoryPage(PackageManagerCore *core); + + QString startMenuDir() const; + void setStartMenuDir(const QString &startMenuDir); + +protected: + void leaving(); + +private Q_SLOTS: + void currentItemChanged(QListWidgetItem* current); + +private: + QString startMenuPath; + QLineEdit *m_lineEdit; + QListWidget *m_listWidget; +}; + + +// -- ReadyForInstallationPage + +class INSTALLER_EXPORT ReadyForInstallationPage : public PackageManagerPage +{ + Q_OBJECT + +public: + explicit ReadyForInstallationPage(PackageManagerCore *core); + + bool isComplete() const; + +private slots: + void toggleDetails(); + +protected: + void entering(); + void leaving(); + +private: + void refreshTaskDetailsBrowser(); + +private: + QLabel *m_msgLabel; + QPushButton *m_taskDetailsButton; + QTextBrowser* m_taskDetailsBrowser; +}; + + +// -- PerformInstallationPage + +class INSTALLER_EXPORT PerformInstallationPage : public PackageManagerPage +{ + Q_OBJECT + +public: + explicit PerformInstallationPage(PackageManagerCore *core); + ~PerformInstallationPage(); + bool isAutoSwitching() const; + +protected: + void entering(); + void leaving(); + bool isInterruptible() const { return true; } + +public Q_SLOTS: + void setTitleMessage(const QString& title); + +Q_SIGNALS: + void installationRequested(); + void setAutomatedPageSwitchEnabled(bool request); + +private Q_SLOTS: + void installationStarted(); + void installationFinished(); + void toggleDetailsWereChanged(); + +private: + PerformInstallationForm *m_performInstallationForm; +}; + + +// -- FinishedPage + +class INSTALLER_EXPORT FinishedPage : public PackageManagerPage +{ + Q_OBJECT + +public: + explicit FinishedPage(PackageManagerCore *core); + +public Q_SLOTS: + void handleFinishClicked(); + +protected: + void entering(); + void leaving(); + +private: + QLabel *m_msgLabel; + QCheckBox *m_runItCheckBox; + QAbstractButton *m_commitButton; +}; + + +// -- RestartPage + +class INSTALLER_EXPORT RestartPage : public PackageManagerPage +{ + Q_OBJECT + +public: + explicit RestartPage(PackageManagerCore *core); + + virtual int nextId() const; + +protected: + void entering(); + void leaving(); + +Q_SIGNALS: + void restart(); +}; + +} //namespace QInstaller + +#endif // PACKAGEMANAGERGUI_H diff --git a/src/libs/installer/packagemanagerproxyfactory.cpp b/src/libs/installer/packagemanagerproxyfactory.cpp new file mode 100644 index 000000000..bd1d2c268 --- /dev/null +++ b/src/libs/installer/packagemanagerproxyfactory.cpp @@ -0,0 +1,79 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "packagemanagerproxyfactory.h" + +#include "packagemanagercore.h" +#include "settings.h" + +namespace QInstaller { + +PackageManagerProxyFactory::PackageManagerProxyFactory(const PackageManagerCore *const core) + : m_core(core) +{ +} + +PackageManagerProxyFactory *PackageManagerProxyFactory::clone() const +{ + return new PackageManagerProxyFactory(m_core); +} + +QList<QNetworkProxy> PackageManagerProxyFactory::queryProxy(const QNetworkProxyQuery &query) +{ + const Settings &settings = m_core->settings(); + QList<QNetworkProxy> list; + + if (settings.proxyType() == Settings::SystemProxy) { +#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) + QUrl proxyUrl = QUrl::fromUserInput(QString::fromUtf8(qgetenv("http_proxy"))); + if (proxyUrl.isValid()) { + return list << QNetworkProxy(QNetworkProxy::HttpProxy, proxyUrl.host(), proxyUrl.port(), + proxyUrl.userName(), proxyUrl.password()); + } +#endif + return QNetworkProxyFactory::systemProxyForQuery(query); + } + + if ((settings.proxyType() == Settings::NoProxy)) + return list << QNetworkProxy(QNetworkProxy::NoProxy); + + if (query.queryType() == QNetworkProxyQuery::UrlRequest) { + if (query.url().scheme() == QLatin1String("ftp")) + return list << settings.ftpProxy(); + + if (query.url().scheme() == QLatin1String("http")) + return list << settings.httpProxy(); + } + return list << QNetworkProxy(QNetworkProxy::DefaultProxy); +} + +} // QInstaller diff --git a/src/libs/installer/packagemanagerproxyfactory.h b/src/libs/installer/packagemanagerproxyfactory.h new file mode 100644 index 000000000..18a72e2c8 --- /dev/null +++ b/src/libs/installer/packagemanagerproxyfactory.h @@ -0,0 +1,56 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef PACKAGEMANAGERPROXYFACTORY_H +#define PACKAGEMANAGERPROXYFACTORY_H + +#include "kdupdaterfiledownloaderfactory.h" + +namespace QInstaller { + +class PackageManagerCore; + +class PackageManagerProxyFactory : public KDUpdater::FileDownloaderProxyFactory +{ +public: + PackageManagerProxyFactory(const PackageManagerCore *const core); + + PackageManagerProxyFactory *clone() const; + QList<QNetworkProxy> queryProxy(const QNetworkProxyQuery &query = QNetworkProxyQuery()); + +private: + const PackageManagerCore *const m_core; +}; + +} // QInstaller + +#endif // PACKAGEMANAGERPROXYFACTORY_H diff --git a/src/libs/installer/performinstallationform.cpp b/src/libs/installer/performinstallationform.cpp new file mode 100644 index 000000000..37cf993fe --- /dev/null +++ b/src/libs/installer/performinstallationform.cpp @@ -0,0 +1,189 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "performinstallationform.h" + +#include "lazyplaintextedit.h" +#include "progresscoordinator.h" + + +#include <QtGui/QLabel> +#include <QtGui/QProgressBar> +#include <QtGui/QPushButton> +#include <QtGui/QScrollBar> +#include <QtGui/QVBoxLayout> + +#include <QtCore/QTimer> + +using namespace QInstaller; + +// -- PerformInstallationForm + +PerformInstallationForm::PerformInstallationForm(QObject *parent) + : QObject(parent), + m_progressBar(0), + m_progressLabel(0), + m_detailsButton(0), + m_detailsBrowser(0), + m_updateTimer(0) +{ +} + +void PerformInstallationForm::setupUi(QWidget *widget) +{ + QVBoxLayout *baseLayout = new QVBoxLayout(widget); + baseLayout->setObjectName(QLatin1String("BaseLayout")); + + QVBoxLayout *topLayout = new QVBoxLayout(); + topLayout->setObjectName(QLatin1String("TopLayout")); + + m_progressBar = new QProgressBar(widget); + m_progressBar->setRange(1, 100); + m_progressBar->setObjectName(QLatin1String("ProgressBar")); + topLayout->addWidget(m_progressBar); + + m_progressLabel = new QLabel(widget); + m_progressLabel->setObjectName(QLatin1String("ProgressLabel")); + m_progressLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); + topLayout->addWidget(m_progressLabel); + + m_downloadStatus = new QLabel(widget); + m_downloadStatus->setObjectName(QLatin1String("DownloadStatus")); + m_downloadStatus->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); + topLayout->addWidget(m_downloadStatus); + connect(ProgressCoordinator::instance(), SIGNAL(downloadStatusChanged(QString)), this, + SLOT(onDownloadStatusChanged(QString))); + + m_detailsButton = new QPushButton(tr("&Show Details"), widget); + m_detailsButton->setObjectName(QLatin1String("DetailsButton")); + m_detailsButton->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); + connect(m_detailsButton, SIGNAL(clicked()), this, SLOT(toggleDetails())); + topLayout->addWidget(m_detailsButton); + + QVBoxLayout *bottomLayout = new QVBoxLayout(); + bottomLayout->setObjectName(QLatin1String("BottomLayout")); + bottomLayout->addStretch(); + + m_detailsBrowser = new LazyPlainTextEdit(widget); + m_detailsBrowser->setReadOnly(true); + m_detailsBrowser->setWordWrapMode(QTextOption::NoWrap); + m_detailsBrowser->setObjectName(QLatin1String("DetailsBrowser")); + m_detailsBrowser->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + bottomLayout->addWidget(m_detailsBrowser); + + bottomLayout->setStretch(1, 10); + baseLayout->addLayout(topLayout); + baseLayout->addLayout(bottomLayout); + + m_updateTimer = new QTimer(widget); + connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(updateProgress())); //updateProgress includes label + m_updateTimer->setInterval(30); +} + +void PerformInstallationForm::setDetailsWidgetVisible(bool visible) +{ + m_detailsButton->setVisible(visible); +} + +void PerformInstallationForm::appendProgressDetails(const QString &details) +{ + m_detailsBrowser->append(details); +} + +void PerformInstallationForm::updateProgress() +{ + QInstaller::ProgressCoordinator *progressCoordninator = QInstaller::ProgressCoordinator::instance(); + const int progressPercentage = progressCoordninator->progressInPercentage(); + + m_progressBar->setRange(0, 100); + m_progressBar->setValue(progressPercentage); + m_progressLabel->setText(m_progressLabel->fontMetrics().elidedText(progressCoordninator->labelText(), + Qt::ElideRight, m_progressLabel->width())); +} + +void PerformInstallationForm::toggleDetails() +{ + const bool willShow = !isShowingDetails(); + m_detailsButton->setText(willShow ? tr("&Hide Details") : tr("&Show Details")); + + if (willShow) + scrollDetailsToTheEnd(); + + m_detailsBrowser->setVisible(willShow); + emit showDetailsChanged(); +} + +void PerformInstallationForm::clearDetailsBrowser() +{ + m_detailsBrowser->clear(); +} + +void PerformInstallationForm::enableDetails() +{ + m_detailsButton->setEnabled(true); + m_detailsButton->setText(tr("&Show Details")); + m_detailsBrowser->setVisible(false); +} + +void PerformInstallationForm::startUpdateProgress() +{ + m_updateTimer->start(); + updateProgress(); +} + +void PerformInstallationForm::stopUpdateProgress() +{ + m_updateTimer->stop(); + updateProgress(); +} + +void PerformInstallationForm::setDetailsButtonEnabled(bool enable) +{ + m_detailsButton->setEnabled(enable); +} + +void PerformInstallationForm::scrollDetailsToTheEnd() +{ + m_detailsBrowser->horizontalScrollBar()->setValue(0); + m_detailsBrowser->verticalScrollBar()->setValue(m_detailsBrowser->verticalScrollBar()->maximum()); +} + +bool PerformInstallationForm::isShowingDetails() const +{ + return m_detailsBrowser->isVisible(); +} + +void PerformInstallationForm::onDownloadStatusChanged(const QString &status) +{ + m_downloadStatus->setText(m_downloadStatus->fontMetrics().elidedText(status, Qt::ElideRight, + m_downloadStatus->width())); +} diff --git a/src/libs/installer/performinstallationform.h b/src/libs/installer/performinstallationform.h new file mode 100644 index 000000000..f13869b13 --- /dev/null +++ b/src/libs/installer/performinstallationform.h @@ -0,0 +1,87 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef PERFORMINSTALLATIONFORM_H +#define PERFORMINSTALLATIONFORM_H + +#include <QObject> + +QT_BEGIN_NAMESPACE +class QLabel; +class QProgressBar; +class QPushButton; +class QTimer; +class QWidget; +QT_END_NAMESPACE + +class LazyPlainTextEdit; + +namespace QInstaller { + +class PerformInstallationForm : public QObject +{ + Q_OBJECT + +public: + PerformInstallationForm(QObject *parent); + + void setupUi(QWidget *widget); + void setDetailsWidgetVisible(bool visible); + void enableDetails(); + void startUpdateProgress(); + void stopUpdateProgress(); + void setDetailsButtonEnabled(bool enable); + void scrollDetailsToTheEnd(); + bool isShowingDetails() const; + +signals: + void showDetailsChanged(); + +public slots: + void appendProgressDetails(const QString &details); + void updateProgress(); + void toggleDetails(); + void clearDetailsBrowser(); + void onDownloadStatusChanged(const QString &status); + +private: + QProgressBar *m_progressBar; + QLabel *m_progressLabel; + QLabel *m_downloadStatus; + QPushButton *m_detailsButton; + LazyPlainTextEdit *m_detailsBrowser; + QTimer *m_updateTimer; +}; + +} // namespace QInstaller + +#endif // PERFORMINSTALLATIONFORM_H diff --git a/src/libs/installer/persistentsettings.cpp b/src/libs/installer/persistentsettings.cpp new file mode 100644 index 000000000..8bbfdb62b --- /dev/null +++ b/src/libs/installer/persistentsettings.cpp @@ -0,0 +1,212 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "persistentsettings.h" + +#include <QtCore/QDebug> +#include <QtCore/QFile> +#include <QtCore/QVariant> +#include <QtXml/QDomDocument> +#include <QtXml/QDomCDATASection> +#include <QtXml/QDomElement> + + +using namespace ProjectExplorer; + +PersistentSettingsReader::PersistentSettingsReader() +{ +} + +QVariant PersistentSettingsReader::restoreValue(const QString &variable) const +{ + if (m_valueMap.contains(variable)) + return m_valueMap.value(variable); + return QVariant(); +} + +QVariantMap PersistentSettingsReader::restoreValues() const +{ + return m_valueMap; +} + +bool PersistentSettingsReader::load(const QString &fileName) +{ + m_valueMap.clear(); + + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) + return false; + + QDomDocument doc; + if (!doc.setContent(&file)) + return false; + + QDomElement root = doc.documentElement(); + if (root.nodeName() != QLatin1String("qtcreator")) + return false; + + QDomElement child = root.firstChildElement(); + for (; !child.isNull(); child = child.nextSiblingElement()) { + if (child.nodeName() == QLatin1String("data")) + readValues(child); + } + + file.close(); + return true; +} + +QVariant PersistentSettingsReader::readValue(const QDomElement &valElement) const +{ + QString name = valElement.nodeName(); + QString type = valElement.attribute(QLatin1String("type")); + QVariant v; + + if (name == QLatin1String("value")) { + if(type == QLatin1String("QChar")) { + //Workaround: QTBUG-12345 + v.setValue(QChar(valElement.text().at(0))); + } else { + v.setValue(valElement.text()); + v.convert(QVariant::nameToType(type.toLatin1().data())); + } + } else if (name == QLatin1String("valuelist")) { + QDomElement child = valElement.firstChildElement(); + QList<QVariant> valList; + for (; !child.isNull(); child = child.nextSiblingElement()) { + valList << readValue(child); + } + v.setValue(valList); + } else if (name == QLatin1String("valuemap")) { + QDomElement child = valElement.firstChildElement(); + QMap<QString, QVariant> valMap; + for (; !child.isNull(); child = child.nextSiblingElement()) { + QString key = child.attribute(QLatin1String("key")); + valMap.insert(key, readValue(child)); + } + v.setValue(valMap); + } + + return v; +} + +void PersistentSettingsReader::readValues(const QDomElement &data) +{ + QString variable; + QVariant v; + + QDomElement child = data.firstChildElement(); + for (; !child.isNull(); child = child.nextSiblingElement()) { + if (child.nodeName() == QLatin1String("variable")) + variable = child.text(); + else + v = readValue(child); + } + + m_valueMap.insert(variable, v); +} + +/// +/// PersistentSettingsWriter +/// + +PersistentSettingsWriter::PersistentSettingsWriter() +{ +} + +void PersistentSettingsWriter::writeValue(QDomElement &ps, const QVariant &variant) +{ + if (variant.type() == QVariant::StringList || variant.type() == QVariant::List) { + QDomElement values = ps.ownerDocument().createElement(QLatin1String("valuelist")); + values.setAttribute(QLatin1String("type"), QLatin1String(QVariant::typeToName(QVariant::List))); + QList<QVariant> varList = variant.toList(); + foreach (const QVariant &var, varList) { + writeValue(values, var); + } + ps.appendChild(values); + } else if (variant.type() == QVariant::Map) { + QDomElement values = ps.ownerDocument().createElement(QLatin1String("valuemap")); + values.setAttribute(QLatin1String("type"), QLatin1String(QVariant::typeToName(QVariant::Map))); + + QMap<QString, QVariant> varMap = variant.toMap(); + QMap<QString, QVariant>::const_iterator i = varMap.constBegin(); + while (i != varMap.constEnd()) { + writeValue(values, i.value()); + values.lastChild().toElement(). + setAttribute(QLatin1String("key"), i.key()); + ++i; + } + + ps.appendChild(values); + } else { + QDomElement value = ps.ownerDocument().createElement(QLatin1String("value")); + ps.appendChild(value); + QDomText valueText = ps.ownerDocument().createTextNode(variant.toString()); + value.appendChild(valueText); + value.setAttribute(QLatin1String("type"), QLatin1String(variant.typeName())); + ps.appendChild(value); + } +} + +void PersistentSettingsWriter::saveValue(const QString &variable, const QVariant &value) +{ + m_valueMap[variable] = value; +} + +bool PersistentSettingsWriter::save(const QString &fileName, const QString &docType) +{ + QFile file(fileName); + if (!file.open(QIODevice::WriteOnly)) + return false; + + QDomDocument doc(docType); + + QDomElement root = doc.createElement(QLatin1String("qtcreator")); + doc.appendChild(root); + + QMap<QString, QVariant>::const_iterator i = m_valueMap.constBegin(); + while (i != m_valueMap.constEnd()) { + QDomElement ps = doc.createElement(QLatin1String("data")); + root.appendChild(ps); + + QDomElement variable = doc.createElement(QLatin1String("variable")); + ps.appendChild(variable); + QDomText variableText = doc.createTextNode(i.key()); + variable.appendChild(variableText); + + writeValue(ps, i.value()); + ++i; + } + + file.write(doc.toByteArray()); + file.close(); + return true; +} diff --git a/src/libs/installer/persistentsettings.h b/src/libs/installer/persistentsettings.h new file mode 100644 index 000000000..4e3171346 --- /dev/null +++ b/src/libs/installer/persistentsettings.h @@ -0,0 +1,75 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef PERSISTENTSETTINGS_H +#define PERSISTENTSETTINGS_H + +#include "projectexplorer_export.h" + +#include <QtCore/QMap> +#include <QtCore/QVariant> + +QT_BEGIN_NAMESPACE +class QDomElement; +QT_END_NAMESPACE + +namespace ProjectExplorer { + +class PROJECTEXPLORER_EXPORT PersistentSettingsReader +{ +public: + PersistentSettingsReader(); + QVariant restoreValue(const QString &variable) const; + QVariantMap restoreValues() const; + bool load(const QString & fileName); + +private: + QVariant readValue(const QDomElement &valElement) const; + void readValues(const QDomElement &data); + QMap<QString, QVariant> m_valueMap; +}; + +class PROJECTEXPLORER_EXPORT PersistentSettingsWriter +{ +public: + PersistentSettingsWriter(); + void saveValue(const QString &variable, const QVariant &value); + bool save(const QString &fileName, const QString &docType); + +private: + void writeValue(QDomElement &ps, const QVariant &value); + QMap<QString, QVariant> m_valueMap; +}; + +} // namespace ProjectExplorer + +#endif // PERSISTENTSETTINGS_H diff --git a/src/libs/installer/progresscoordinator.cpp b/src/libs/installer/progresscoordinator.cpp new file mode 100644 index 000000000..b9bf7738e --- /dev/null +++ b/src/libs/installer/progresscoordinator.cpp @@ -0,0 +1,275 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "progresscoordinator.h" + +#include <QtCore/QCoreApplication> +#include <QtCore/QDebug> + +using namespace QInstaller; + +QT_BEGIN_NAMESPACE +uint qHash(QPointer<QObject> key) +{ + return qHash(key.data()); +} +QT_END_NAMESPACE + +ProgressCoordinator::ProgressCoordinator(QObject *parent) + : QObject(parent), + m_currentCompletePercentage(0), + m_currentBasePercentage(0), + m_manualAddedPercentage(0), + m_reservedPercentage(0), + m_undoMode(false), + m_reachedPercentageBeforeUndo(0) +{ + // it has to be in the main thread to be able refresh the ui with processEvents + Q_ASSERT(thread() == qApp->thread()); +} + +ProgressCoordinator::~ProgressCoordinator() +{ +} + +ProgressCoordinator *ProgressCoordinator::instance() +{ + static ProgressCoordinator *instance = 0; + if (instance == 0) + instance = new ProgressCoordinator(qApp); + return instance; +} + +void ProgressCoordinator::reset() +{ + disconnectAllSenders(); + m_installationLabelText.clear(); + m_currentCompletePercentage = 0; + m_currentBasePercentage = 0; + m_manualAddedPercentage = 0; + m_reservedPercentage = 0; + m_undoMode = false; + m_reachedPercentageBeforeUndo = 0; + emit detailTextResetNeeded(); +} + +void ProgressCoordinator::registerPartProgress(QObject *sender, const char *signal, double partProgressSize) +{ + Q_ASSERT(sender); + Q_ASSERT(QString::fromLatin1(signal).contains(QLatin1String("(double)"))); + Q_ASSERT(partProgressSize <= 1); + + m_senderPartProgressSizeHash.insert(sender, partProgressSize); + bool isConnected = connect(sender, signal, this, SLOT(partProgressChanged(double))); + Q_UNUSED(isConnected); + Q_ASSERT(isConnected); +} + +void ProgressCoordinator::partProgressChanged(double fraction) +{ + if (fraction < 0 || fraction > 1) { + qWarning() << "The fraction is outside from possible value:" << QString::number(fraction); + return; + } + + double partProgressSize = m_senderPartProgressSizeHash.value(sender(), 0); + if (partProgressSize == 0) { + qWarning() << "It seems that this sender was not registered in the right way:" << sender(); + return; + } + + if (m_undoMode) { + //qDebug() << "fraction:" << fraction; + double maxSize = m_reachedPercentageBeforeUndo * partProgressSize; + double pendingCalculatedPartPercentage = maxSize * fraction; + + // allPendingCalculatedPartPercentages has negative values + double newCurrentCompletePercentage = m_currentBasePercentage - pendingCalculatedPartPercentage + + allPendingCalculatedPartPercentages(sender()); + + //we can't check this here, because some round issues can make it little bit under 0 or over 100 + //Q_ASSERT(newCurrentCompletePercentage >= 0); + //Q_ASSERT(newCurrentCompletePercentage <= 100); + if (newCurrentCompletePercentage < 0) { + qDebug() << newCurrentCompletePercentage << "is smaller then 0 - this should happen max once"; + newCurrentCompletePercentage = 0; + } + if (newCurrentCompletePercentage > 100) { + qDebug() << newCurrentCompletePercentage << "is bigger then 100 - this should happen max once"; + newCurrentCompletePercentage = 100; + } + + if (qRound(m_currentCompletePercentage) < qRound(newCurrentCompletePercentage)) { + qFatal("This should not happen!"); + } + + m_currentCompletePercentage = newCurrentCompletePercentage; + if (fraction == 1) { + m_currentBasePercentage = m_currentBasePercentage - pendingCalculatedPartPercentage; + m_senderPendingCalculatedPercentageHash.insert(sender(), 0); + } else { + m_senderPendingCalculatedPercentageHash.insert(sender(), pendingCalculatedPartPercentage); + } + + } else { //if (m_undoMode) + int availablePercentagePoints = 100 - m_manualAddedPercentage - m_reservedPercentage; + double pendingCalculatedPartPercentage = availablePercentagePoints * partProgressSize * fraction; + //double checkValue = allPendingCalculatedPartPercentages(sender()); + + double newCurrentCompletePercentage = m_manualAddedPercentage + m_currentBasePercentage + + pendingCalculatedPartPercentage + allPendingCalculatedPartPercentages(sender()); + + //we can't check this here, because some round issues can make it little bit under 0 or over 100 + //Q_ASSERT(newCurrentCompletePercentage >= 0); + //Q_ASSERT(newCurrentCompletePercentage <= 100); + if (newCurrentCompletePercentage < 0) { + qDebug() << newCurrentCompletePercentage << "is smaller then 0 - this should happen max once"; + newCurrentCompletePercentage = 0; + } + + if (newCurrentCompletePercentage > 100) { + qDebug() << newCurrentCompletePercentage << "is bigger then 100 - this should happen max once"; + newCurrentCompletePercentage = 100; + } + + if (qRound(m_currentCompletePercentage) > qRound(newCurrentCompletePercentage)) { + qFatal("This should not happen!"); + } + m_currentCompletePercentage = newCurrentCompletePercentage; + + if (fraction == 1 || fraction == 0) { + m_currentBasePercentage = m_currentBasePercentage + pendingCalculatedPartPercentage; + m_senderPendingCalculatedPercentageHash.insert(sender(), 0); + } else { + m_senderPendingCalculatedPercentageHash.insert(sender(), pendingCalculatedPartPercentage); + } + } //if (m_undoMode) +} + + +/*! + Contains the installation progress percentage. +*/ +int ProgressCoordinator::progressInPercentage() const +{ + int currentValue = qRound(m_currentCompletePercentage); + Q_ASSERT( currentValue <= 100); + Q_ASSERT( currentValue >= 0); + return currentValue; +} + +void ProgressCoordinator::disconnectAllSenders() +{ + foreach (QPointer<QObject> sender, m_senderPartProgressSizeHash.keys()) { + if (!sender.isNull()) { + bool isDisconnected = sender->disconnect(this); + Q_UNUSED(isDisconnected); + Q_ASSERT(isDisconnected); + } + } + m_senderPartProgressSizeHash.clear(); + m_senderPendingCalculatedPercentageHash.clear(); +} + +void ProgressCoordinator::setUndoMode() +{ + Q_ASSERT(!m_undoMode); + m_undoMode = true; + + disconnectAllSenders(); + m_reachedPercentageBeforeUndo = progressInPercentage(); + m_currentBasePercentage = m_reachedPercentageBeforeUndo; +} + +void ProgressCoordinator::addManualPercentagePoints(int value) +{ + m_manualAddedPercentage = m_manualAddedPercentage + value; + if (m_undoMode) { + //we don't do other things in the undomode, maybe later if the last percentage point comes to early + return; + } + + m_currentCompletePercentage = m_currentCompletePercentage + value; + if (m_currentCompletePercentage > 100.0) + m_currentCompletePercentage = 100.0; + + qApp->processEvents(); //makes the result available in the ui +} + +void ProgressCoordinator::addReservePercentagePoints(int value) +{ + m_reservedPercentage = m_reservedPercentage + value; +} + +void ProgressCoordinator::setLabelText(const QString &text) +{ + if (m_installationLabelText == text) + return; + m_installationLabelText = text; +} + +/*! + Contains the installation progress label text. +*/ +QString ProgressCoordinator::labelText() const +{ + return m_installationLabelText; +} + +void ProgressCoordinator::emitDetailTextChanged(const QString &text) +{ + emit detailTextChanged(text); +} + +void ProgressCoordinator::emitLabelAndDetailTextChanged(const QString &text) +{ + emit detailTextChanged(text); + m_installationLabelText = QString(text).remove(QLatin1String("\n")); + qApp->processEvents(); //makes the result available in the ui +} + +double ProgressCoordinator::allPendingCalculatedPartPercentages(QObject *excludeKeyObject) +{ + double result = 0; + QHash<QPointer<QObject>, double>::iterator it = m_senderPendingCalculatedPercentageHash.begin(); + while (it != m_senderPendingCalculatedPercentageHash.end()) { + if (it.key() != excludeKeyObject) + result += it.value(); + it++; + } + return result; +} + +void ProgressCoordinator::emitDownloadStatus(const QString &status) +{ + emit downloadStatusChanged(status); +} diff --git a/src/libs/installer/progresscoordinator.h b/src/libs/installer/progresscoordinator.h new file mode 100644 index 000000000..12f554666 --- /dev/null +++ b/src/libs/installer/progresscoordinator.h @@ -0,0 +1,96 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef PROGRESSCOORDNINATOR_H +#define PROGRESSCOORDNINATOR_H + +#include <QtCore/QHash> +#include <QtCore/QObject> +#include <QtCore/QPointer> + +namespace QInstaller { + +class ProgressCoordinator : public QObject +{ + Q_OBJECT + +public: + static ProgressCoordinator *instance(); + ~ProgressCoordinator(); + + void registerPartProgress(QObject *sender, const char *signal, double partProgressSize); + +public slots: + void reset(); + void setUndoMode(); + + QString labelText() const; + void setLabelText(const QString &text); + + int progressInPercentage() const; + void partProgressChanged(double fraction); + + void addManualPercentagePoints(int value); + void addReservePercentagePoints(int value); + + void emitDetailTextChanged(const QString &text); + void emitLabelAndDetailTextChanged(const QString &text); + + void emitDownloadStatus(const QString &status); + +signals: + void detailTextChanged(const QString &text); + void detailTextResetNeeded(); + void downloadStatusChanged(const QString &status); + +protected: + explicit ProgressCoordinator(QObject *parent); + +private: + double allPendingCalculatedPartPercentages(QObject *excludeKeyObject = 0); + void disconnectAllSenders(); + +private: + QHash<QPointer<QObject>, double> m_senderPendingCalculatedPercentageHash; + QHash<QPointer<QObject>, double> m_senderPartProgressSizeHash; + QString m_installationLabelText; + double m_currentCompletePercentage; + double m_currentBasePercentage; + int m_manualAddedPercentage; + int m_reservedPercentage; + bool m_undoMode; + double m_reachedPercentageBeforeUndo; +}; + +} //namespace QInstaller + +#endif //PROGRESSCOORDNINATOR_H diff --git a/src/libs/installer/projectexplorer_export.h b/src/libs/installer/projectexplorer_export.h new file mode 100644 index 000000000..0e8cb470b --- /dev/null +++ b/src/libs/installer/projectexplorer_export.h @@ -0,0 +1 @@ +#define PROJECTEXPLORER_EXPORT diff --git a/src/libs/installer/qinstallerglobal.h b/src/libs/installer/qinstallerglobal.h new file mode 100644 index 000000000..8df909e76 --- /dev/null +++ b/src/libs/installer/qinstallerglobal.h @@ -0,0 +1,92 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef QINSTALLER_GLOBAL_H +#define QINSTALLER_GLOBAL_H + +#include <installer_global.h> + +#include <kdupdaterupdate.h> +#include <kdupdaterupdateoperation.h> +#include <kdupdaterpackagesinfo.h> + +QT_BEGIN_NAMESPACE +class QScriptContext; +class QScriptEngine; +class QScriptValue; +QT_END_NAMESPACE + + +namespace QInstaller { + +#define IFW_VERSION 0x010000 +#define IFW_VERSION_STRING "1.0.0" +#define IFW_VERSION_CHECK(major, minor, patch) \ + ((major << 16)|(minor << 8)|(patch)) + +enum INSTALLER_EXPORT RunMode +{ + AllMode, + UpdaterMode +}; + +enum INSTALLER_EXPORT JobError +{ + InvalidUrl = 0x24B04, + Timeout, + DownloadError, + InvalidUpdatesXml, + InvalidMetaInfo, + ExtractionError, + UserIgnoreError, + RepositoryUpdatesReceived +}; + +typedef KDUpdater::UpdateOperation Operation; +typedef QList<QInstaller::Operation*> OperationList; + +typedef KDUpdater::Update Package; +typedef QList<QInstaller::Package*> PackagesList; + +typedef KDUpdater::PackageInfo LocalPackage; +typedef QHash<QString, LocalPackage> LocalPackagesHash; + +QString uncaughtExceptionString(QScriptEngine *scriptEngine, const QString &context = QString()); +QScriptValue qInstallerComponentByName(QScriptContext *context, QScriptEngine *engine); + +QScriptValue qDesktopServicesOpenUrl(QScriptContext *context, QScriptEngine *engine); +QScriptValue qDesktopServicesDisplayName(QScriptContext *context, QScriptEngine *engine); +QScriptValue qDesktopServicesStorageLocation(QScriptContext *context, QScriptEngine *engine); + +} // namespace QInstaller + +#endif // QINSTALLER_GLOBAL_H diff --git a/src/libs/installer/qprocesswrapper.cpp b/src/libs/installer/qprocesswrapper.cpp new file mode 100644 index 000000000..e15168fac --- /dev/null +++ b/src/libs/installer/qprocesswrapper.cpp @@ -0,0 +1,413 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "qprocesswrapper.h" + +#include "fsengineclient.h" +#include "templates.cpp" + +#include <QtCore/QThread> + +#include <QtNetwork/QTcpSocket> + +// -- QProcessWrapper::Private + +class QProcessWrapper::Private +{ +public: + Private(QProcessWrapper *qq) + : q(qq), + ignoreTimer(false), + socket(0) + {} + + bool createSocket() + { + if (!FSEngineClientHandler::instance().isActive()) + return false; + if (socket != 0 && socket->state() == static_cast< int >(QAbstractSocket::ConnectedState)) + return true; + if (socket != 0) + delete socket; + socket = new QTcpSocket; + + if (!FSEngineClientHandler::instance().connect(socket)) + return false; + stream.setDevice(socket); + stream.setVersion(QDataStream::Qt_4_2); + + stream << QString::fromLatin1("createQProcess"); + socket->flush(); + stream.device()->waitForReadyRead(-1); + quint32 test; + stream >> test; + stream.device()->readAll(); + + q->startTimer(250); + + return true; + } + + class TimerBlocker + { + public: + explicit TimerBlocker(const QProcessWrapper *wrapper) + : w(const_cast<QProcessWrapper *>(wrapper)) + { + w->d->ignoreTimer = true; + } + + ~TimerBlocker() + { + w->d->ignoreTimer = false; + } + + private: + QProcessWrapper *const w; + }; + +private: + QProcessWrapper *const q; + +public: + bool ignoreTimer; + + QProcess process; + mutable QTcpSocket *socket; + mutable QDataStream stream; +}; + + +// -- QProcessWrapper + +QProcessWrapper::QProcessWrapper(QObject *parent) + : QObject(parent), + d(new Private(this)) +{ + connect(&d->process, SIGNAL(bytesWritten(qint64)), SIGNAL(bytesWritten(qint64))); + connect(&d->process, SIGNAL(aboutToClose()), SIGNAL(aboutToClose())); + connect(&d->process, SIGNAL(readChannelFinished()), SIGNAL(readChannelFinished())); + connect(&d->process, SIGNAL(error(QProcess::ProcessError)), SIGNAL(error(QProcess::ProcessError))); + connect(&d->process, SIGNAL(readyReadStandardOutput()), SIGNAL(readyReadStandardOutput())); + connect(&d->process, SIGNAL(readyReadStandardError()), SIGNAL(readyReadStandardError())); + connect(&d->process, SIGNAL(finished(int)), SIGNAL(finished(int))); + connect(&d->process, SIGNAL(finished(int,QProcess::ExitStatus)), SIGNAL(finished(int,QProcess::ExitStatus))); + connect(&d->process, SIGNAL(readyRead()), SIGNAL(readyRead())); + connect(&d->process, SIGNAL(started()), SIGNAL(started())); + connect(&d->process, SIGNAL(stateChanged(QProcess::ProcessState)), SIGNAL(stateChanged(QProcess::ProcessState))); +} + +QProcessWrapper::~QProcessWrapper() +{ + if (d->socket != 0) { + d->stream << QString::fromLatin1("destroyQProcess"); + d->socket->flush(); + quint32 result; + d->stream >> result; + + if (QThread::currentThread() == d->socket->thread()) { + d->socket->close(); + delete d->socket; + } else { + d->socket->deleteLater(); + } + } + delete d; +} + +void QProcessWrapper::timerEvent(QTimerEvent *event) +{ + Q_UNUSED(event) + + if (d->ignoreTimer) + return; + + QList<QVariant> receivedSignals; + { + const Private::TimerBlocker blocker(this); + + d->stream << QString::fromLatin1("getQProcessSignals"); + d->socket->flush(); + d->stream.device()->waitForReadyRead(-1); + quint32 test; + d->stream >> test; + d->stream >> receivedSignals; + d->stream.device()->readAll(); + } + + while (!receivedSignals.isEmpty()) { + const QString name = receivedSignals.takeFirst().toString(); + if (name == QLatin1String("started")) { + emit started(); + } else if (name == QLatin1String("readyRead")) { + emit readyRead(); + } else if (name == QLatin1String("stateChanged")) { + const QProcess::ProcessState newState = + static_cast<QProcess::ProcessState> (receivedSignals.takeFirst().toInt()); + emit stateChanged(newState); + } else if (name == QLatin1String("finished")) { + const int exitCode = receivedSignals.takeFirst().toInt(); + const QProcess::ExitStatus exitStatus = + static_cast<QProcess::ExitStatus> (receivedSignals.takeFirst().toInt()); + emit finished(exitCode); + emit finished(exitCode, exitStatus); + } + } +} + +bool startDetached(const QString &program, const QStringList &args, const QString &workingDirectory, + qint64 *pid); + +bool QProcessWrapper::startDetached(const QString &program, const QStringList &arguments, + const QString &workingDirectory, qint64 *pid) +{ + QProcessWrapper w; + if (w.d->createSocket()) { + const QPair<bool, qint64> result = callRemoteMethod<QPair<bool, qint64> >(w.d->stream, + QLatin1String("QProcess::startDetached"), program, arguments, workingDirectory); + if (pid != 0) + *pid = result.second; + return result.first; + } + return ::startDetached(program, arguments, workingDirectory, pid); +} + +bool QProcessWrapper::startDetached(const QString &program, const QStringList &arguments) +{ + return startDetached(program, arguments, QDir::currentPath()); +} + +bool QProcessWrapper::startDetached(const QString &program) +{ + return startDetached(program, QStringList()); +} + +void QProcessWrapper::setProcessChannelMode(QProcessWrapper::ProcessChannelMode mode) +{ + const Private::TimerBlocker blocker(this); + if (d->createSocket()) { + callRemoteVoidMethod(d->stream, QLatin1String("QProcess::setProcessChannelMode"), + static_cast<QProcess::ProcessChannelMode>(mode)); + } else { + d->process.setProcessChannelMode(static_cast<QProcess::ProcessChannelMode>(mode)); + } +} + +/*! + Cancels the process. This methods tries to terminate the process + gracefully by calling QProcess::terminate. After 10 seconds, the process gets killed. + */ +void QProcessWrapper::cancel() +{ + if (state() == QProcessWrapper::Running) + terminate(); + + if (!waitForFinished(10000)) + kill(); +} + +void QProcessWrapper::setReadChannel(QProcessWrapper::ProcessChannel chan) +{ + const Private::TimerBlocker blocker(this); + if (d->createSocket()) { + callRemoteVoidMethod(d->stream, QLatin1String("QProcess::setReadChannel"), + static_cast<QProcess::ProcessChannel>(chan)); + } else { + d->process.setReadChannel(static_cast<QProcess::ProcessChannel>(chan)); + } +} + +bool QProcessWrapper::waitForFinished(int msecs) +{ + const Private::TimerBlocker blocker(this); + if (d->createSocket()) + return callRemoteMethod<bool>(d->stream, QLatin1String("QProcess::waitForFinished"), msecs); + return d->process.waitForFinished(msecs); +} + +bool QProcessWrapper::waitForStarted(int msecs) +{ + const Private::TimerBlocker blocker(this); + if (d->createSocket()) + return callRemoteMethod<bool>(d->stream, QLatin1String("QProcess::waitForStarted"), msecs); + return d->process.waitForStarted(msecs); +} + +qint64 QProcessWrapper::write(const QByteArray &data) +{ + const Private::TimerBlocker blocker(this); + if (d->createSocket()) + return callRemoteMethod<qint64>(d->stream, QLatin1String("QProcess::write"), data); + return d->process.write(data); +} + +void QProcessWrapper::closeWriteChannel() +{ + const Private::TimerBlocker blocker(this); + if (d->createSocket()) + callRemoteVoidMethod<void>(d->stream, QLatin1String("QProcess::closeWriteChannel")); + else + d->process.closeWriteChannel(); +} + +int QProcessWrapper::exitCode() const +{ + const Private::TimerBlocker blocker(this); + if (d->createSocket()) + return callRemoteMethod<int>(d->stream, QLatin1String("QProcess::exitCode")); + return static_cast< int>(d->process.exitCode()); +} + +QProcessWrapper::ExitStatus QProcessWrapper::exitStatus() const +{ + const Private::TimerBlocker blocker(this); + if (d->createSocket()) + return callRemoteMethod<QProcessWrapper::ExitStatus>(d->stream, QLatin1String("QProcess::exitStatus")); + return static_cast< QProcessWrapper::ExitStatus>(d->process.exitStatus()); +} + +void QProcessWrapper::kill() +{ + const Private::TimerBlocker blocker(this); + if (d->createSocket()) + callRemoteVoidMethod<void>(d->stream, QLatin1String("QProcess::kill")); + else + d->process.kill(); +} + +QByteArray QProcessWrapper::readAll() +{ + const Private::TimerBlocker blocker(this); + if (d->createSocket()) + return callRemoteMethod<QByteArray>(d->stream, QLatin1String("QProcess::readAll")); + return d->process.readAll(); +} + +QByteArray QProcessWrapper::readAllStandardOutput() +{ + const Private::TimerBlocker blocker(this); + if (d->createSocket()) + return callRemoteMethod<QByteArray>(d->stream, QLatin1String("QProcess::readAllStandardOutput")); + return d->process.readAllStandardOutput(); +} + +void QProcessWrapper::start(const QString ¶m1, const QStringList ¶m2, QIODevice::OpenMode param3) +{ + const Private::TimerBlocker blocker(this); + if (d->createSocket()) + callRemoteVoidMethod(d->stream, QLatin1String("QProcess::start"), param1, param2, param3); + else + d->process.start(param1, param2, param3); +} + +void QProcessWrapper::start(const QString ¶m1) +{ + const Private::TimerBlocker blocker(this); + if (d->createSocket()) + callRemoteVoidMethod(d->stream, QLatin1String("QProcess::start"), param1); + else + d->process.start(param1); +} + +QProcessWrapper::ProcessState QProcessWrapper::state() const +{ + const Private::TimerBlocker blocker(this); + if (d->createSocket()) + return callRemoteMethod<QProcessWrapper::ProcessState>(d->stream, QLatin1String("QProcess::state")); + return static_cast< QProcessWrapper::ProcessState>(d->process.state()); +} + +void QProcessWrapper::terminate() +{ + const Private::TimerBlocker blocker(this); + if (d->createSocket()) + callRemoteVoidMethod<void>(d->stream, QLatin1String("QProcess::terminate")); + else + d->process.terminate(); +} + +QProcessWrapper::ProcessChannel QProcessWrapper::readChannel() const +{ + const Private::TimerBlocker blocker(this); + if (d->createSocket()) { + return callRemoteMethod<QProcessWrapper::ProcessChannel>(d->stream, + QLatin1String("QProcess::readChannel")); + } + return static_cast< QProcessWrapper::ProcessChannel>(d->process.readChannel()); +} + +QProcessWrapper::ProcessChannelMode QProcessWrapper::processChannelMode() const +{ + const Private::TimerBlocker blocker(this); + if (d->createSocket()) { + return callRemoteMethod<QProcessWrapper::ProcessChannelMode>(d->stream, + QLatin1String("QProcess::processChannelMode")); + } + return static_cast< QProcessWrapper::ProcessChannelMode>(d->process.processChannelMode()); +} + +QString QProcessWrapper::workingDirectory() const +{ + const Private::TimerBlocker blocker(this); + if (d->createSocket()) + return callRemoteMethod<QString>(d->stream, QLatin1String("QProcess::workingDirectory")); + return static_cast< QString>(d->process.workingDirectory()); +} + +void QProcessWrapper::setEnvironment(const QStringList ¶m1) +{ + const Private::TimerBlocker blocker(this); + if (d->createSocket()) + callRemoteVoidMethod(d->stream, QLatin1String("QProcess::setEnvironment"), param1); + else + d->process.setEnvironment(param1); +} + +#ifdef Q_OS_WIN +void QProcessWrapper::setNativeArguments(const QString ¶m1) +{ + const Private::TimerBlocker blocker(this); + if (d->createSocket()) + callRemoteVoidMethod(d->stream, QLatin1String("QProcess::setNativeArguments"), param1); + else + d->process.setNativeArguments(param1); +} +#endif + +void QProcessWrapper::setWorkingDirectory(const QString ¶m1) +{ + const Private::TimerBlocker blocker(this); + if (d->createSocket()) + callRemoteVoidMethod(d->stream, QLatin1String("QProcess::setWorkingDirectory"), param1); + else + d->process.setWorkingDirectory(param1); +} diff --git a/src/libs/installer/qprocesswrapper.h b/src/libs/installer/qprocesswrapper.h new file mode 100644 index 000000000..e9cf1a9d2 --- /dev/null +++ b/src/libs/installer/qprocesswrapper.h @@ -0,0 +1,127 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef QPROCESSWRAPPER_H +#define QPROCESSWRAPPER_H + +#include <installer_global.h> + +#include<QtCore/QIODevice> +#include<QtCore/QObject> +#include<QtCore/QProcess> + +class INSTALLER_EXPORT QProcessWrapper : public QObject +{ + Q_OBJECT +public: + enum ProcessState { + NotRunning, + Starting, + Running + }; + + enum ExitStatus { + NormalExit, + CrashExit + }; + + enum ProcessChannel { + StandardOutput = 0, + StandardError = 1 + }; + + enum ProcessChannelMode { + SeparateChannels = 0, + MergedChannels = 1, + ForwardedChannels = 2 + }; + + explicit QProcessWrapper(QObject *parent = 0); + ~QProcessWrapper(); + + void closeWriteChannel(); + int exitCode() const; + ExitStatus exitStatus() const; + void kill(); + void terminate(); + QByteArray readAll(); + QByteArray readAllStandardOutput(); + void setWorkingDirectory(const QString &dir); + + void start(const QString &program); + void start(const QString &program, const QStringList &arguments, + QIODevice::OpenMode mode = QIODevice::ReadWrite); + + static bool startDetached(const QString &program); + static bool startDetached(const QString &program, const QStringList &arguments); + static bool startDetached(const QString &program, const QStringList &arguments, + const QString &workingDirectory, qint64 *pid = 0); + + ProcessState state() const; + bool waitForStarted(int msecs = 30000); + bool waitForFinished(int msecs = 30000); + void setEnvironment(const QStringList &environment); + QString workingDirectory() const; + qint64 write(const QByteArray &byteArray); + QProcessWrapper::ProcessChannel readChannel() const; + void setReadChannel(QProcessWrapper::ProcessChannel channel); + QProcessWrapper::ProcessChannelMode processChannelMode() const; + void setProcessChannelMode(QProcessWrapper::ProcessChannelMode channel); +#ifdef Q_OS_WIN + void setNativeArguments(const QString &arguments); +#endif + +Q_SIGNALS: + void bytesWritten(qint64); + void aboutToClose(); + void readChannelFinished(); + void error(QProcess::ProcessError); + void readyReadStandardOutput(); + void readyReadStandardError(); + void finished(int exitCode); + void finished(int exitCode, QProcess::ExitStatus exitStatus); + void readyRead(); + void started(); + void stateChanged(QProcess::ProcessState newState); + +public Q_SLOTS: + void cancel(); + +protected: + void timerEvent(QTimerEvent *event); + +private: + class Private; + Private *d; +}; + +#endif // QPROCESSWRAPPER_H diff --git a/src/libs/installer/qsettingswrapper.cpp b/src/libs/installer/qsettingswrapper.cpp new file mode 100644 index 000000000..09d5b227a --- /dev/null +++ b/src/libs/installer/qsettingswrapper.cpp @@ -0,0 +1,366 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "qsettingswrapper.h" + +#include "fsengineclient.h" +#include "templates.cpp" + +#include <QtCore/QSettings> +#include <QtCore/QThread> + +#include <QtNetwork/QTcpSocket> + + +// -- QSettingsWrapper::Private + +class QSettingsWrapper::Private +{ +public: + Private(const QString &organization, const QString &application) + : native(true), + settings(organization, application), + socket(0) + { + } + + Private(QSettings::Scope scope, const QString &organization, const QString &application) + : native(true), + settings(scope, organization, application), + socket(0) + { + } + + Private(QSettings::Format format, QSettings::Scope scope, const QString &organization, + const QString &application) + : native(format == QSettings::NativeFormat), + settings(format, scope, organization, application), + socket(0) + { + } + + Private(const QString &fileName, QSettings::Format format) + : native(format == QSettings::NativeFormat), + fileName(fileName), + settings(fileName, format), + socket(0) + { + } + + Private() + : native(true), + socket(0) + { + } + + bool createSocket() + { + if (!native || !FSEngineClientHandler::instance().isActive()) + return false; + + if (socket != 0 && socket->state() == static_cast<int>(QAbstractSocket::ConnectedState)) + return true; + + if (socket != 0) + delete socket; + + socket = new QTcpSocket; + if (!FSEngineClientHandler::instance().connect(socket)) + return false; + + stream.setDevice(socket); + stream.setVersion(QDataStream::Qt_4_2); + + stream << QString::fromLatin1("createQSettings"); + stream << this->fileName; + socket->flush(); + stream.device()->waitForReadyRead(-1); + quint32 test; + stream >> test; + stream.device()->readAll(); + return true; + } + + const bool native; + const QString fileName; + QSettings settings; + mutable QTcpSocket *socket; + mutable QDataStream stream; +}; + + +// -- QSettingsWrapper + +QSettingsWrapper::QSettingsWrapper(const QString &organization, const QString &application, QObject *parent) + : QObject(parent) + , d(new Private(organization, application)) +{ +} + +QSettingsWrapper::QSettingsWrapper(QSettingsWrapper::Scope scope, const QString &organization, + const QString &application, QObject *parent) + : QObject(parent) + , d(new Private(static_cast<QSettings::Scope>(scope), organization, application)) +{ +} + +QSettingsWrapper::QSettingsWrapper(QSettingsWrapper::Format format, QSettingsWrapper::Scope scope, + const QString &organization, const QString &application, QObject *parent) + : QObject(parent) + , d(new Private(static_cast<QSettings::Format>(format), static_cast<QSettings::Scope> (scope), + organization, application)) +{ +} + +QSettingsWrapper::QSettingsWrapper(const QString &fileName, QSettingsWrapper::Format format, QObject *parent) + : QObject(parent) + , d(new Private(fileName, static_cast<QSettings::Format>(format))) +{ +} + +QSettingsWrapper::QSettingsWrapper(QObject *parent) + : QObject(parent) + , d(new Private) +{ +} + +QSettingsWrapper::~QSettingsWrapper() +{ + if (d->socket != 0) { + d->stream << QString::fromLatin1("destroyQSettings"); + d->socket->flush(); + quint32 result; + d->stream >> result; + + if (QThread::currentThread() == d->socket->thread()) { + d->socket->close(); + delete d->socket; + } else { + d->socket->deleteLater(); + } + } + delete d; +} + +QStringList QSettingsWrapper::allKeys() const +{ + if (d->createSocket()) + return callRemoteMethod<QStringList>(d->stream, QLatin1String("QSettings::allKeys")); + return static_cast<QStringList>(d->settings.allKeys()); +} + +QString QSettingsWrapper::applicationName() const +{ + if (d->createSocket()) + return callRemoteMethod<QString>(d->stream, QLatin1String("QSettings::applicationName")); + return static_cast<QString>(d->settings.applicationName()); +} + +void QSettingsWrapper::beginGroup(const QString ¶m1) +{ + if (d->createSocket()) + callRemoteVoidMethod(d->stream, QLatin1String("QSettings::beginGroup"), param1); + else + d->settings.beginGroup(param1); +} + +int QSettingsWrapper::beginReadArray(const QString ¶m1) +{ + if (d->createSocket()) + return callRemoteMethod<int>(d->stream, QLatin1String("QSettings::beginReadArray"), param1); + return d->settings.beginReadArray(param1); +} + +void QSettingsWrapper::beginWriteArray(const QString ¶m1, int param2) +{ + if (d->createSocket()) + callRemoteVoidMethod(d->stream, QLatin1String("QSettings::beginWriteArray"), param1, param2); + else + d->settings.beginWriteArray(param1, param2); +} + +QStringList QSettingsWrapper::childGroups() const +{ + if (d->createSocket()) + return callRemoteMethod<QStringList>(d->stream, QLatin1String("QSettings::childGroups")); + return static_cast<QStringList>(d->settings.childGroups()); +} + +QStringList QSettingsWrapper::childKeys() const +{ + if (d->createSocket()) + return callRemoteMethod<QStringList>(d->stream, QLatin1String("QSettings::childKeys")); + return static_cast<QStringList>(d->settings.childKeys()); +} + +void QSettingsWrapper::clear() +{ + if (d->createSocket()) + callRemoteVoidMethod<void>(d->stream, QLatin1String("QSettings::clear")); + else d->settings.clear(); +} + +bool QSettingsWrapper::contains(const QString ¶m1) const +{ + if (d->createSocket()) + return callRemoteMethod<bool>(d->stream, QLatin1String("QSettings::contains"), param1); + return d->settings.contains(param1); +} + +void QSettingsWrapper::endArray() +{ + if (d->createSocket()) + callRemoteVoidMethod<void>(d->stream, QLatin1String("QSettings::endArray")); + else + d->settings.endArray(); +} + +void QSettingsWrapper::endGroup() +{ + if (d->createSocket()) + callRemoteVoidMethod<void>(d->stream, QLatin1String("QSettings::endGroup")); + else + d->settings.endGroup(); +} + +bool QSettingsWrapper::fallbacksEnabled() const +{ + if (d->createSocket()) + return callRemoteMethod<bool>(d->stream, QLatin1String("QSettings::fallbacksEnabled")); + return static_cast<bool>(d->settings.fallbacksEnabled()); +} + +QString QSettingsWrapper::fileName() const +{ + if (d->createSocket()) + return callRemoteMethod<QString>(d->stream, QLatin1String("QSettings::fileName")); + return static_cast<QString>(d->settings.fileName()); +} + +QSettingsWrapper::Format QSettingsWrapper::format() const +{ + return static_cast<QSettingsWrapper::Format>(d->settings.format()); +} + +QString QSettingsWrapper::group() const +{ + if (d->createSocket()) + return callRemoteMethod<QString>(d->stream, QLatin1String("QSettings::group")); + return static_cast<QString>(d->settings.group()); +} + +QTextCodec* QSettingsWrapper::iniCodec() const +{ + return d->settings.iniCodec(); +} + +bool QSettingsWrapper::isWritable() const +{ + if (d->createSocket()) + return callRemoteMethod<bool>(d->stream, QLatin1String("QSettings::isWritable")); + return static_cast<bool>(d->settings.isWritable()); +} + +QString QSettingsWrapper::organizationName() const +{ + if (d->createSocket()) + return callRemoteMethod<QString>(d->stream, QLatin1String("QSettings::organizationName")); + return static_cast<QString>(d->settings.organizationName()); +} + +void QSettingsWrapper::remove(const QString ¶m1) +{ + if (d->createSocket()) + callRemoteVoidMethod(d->stream, QLatin1String("QSettings::remove"), param1); + else d->settings.remove(param1); +} + +QSettingsWrapper::Scope QSettingsWrapper::scope() const +{ + return static_cast<QSettingsWrapper::Scope>(d->settings.scope()); +} + +void QSettingsWrapper::setArrayIndex(int param1) +{ + if (d->createSocket()) + callRemoteVoidMethod(d->stream, QLatin1String("QSettings::setArrayIndex"), param1); + else + d->settings.setArrayIndex(param1); +} + +void QSettingsWrapper::setFallbacksEnabled(bool param1) +{ + if (d->createSocket()) + callRemoteVoidMethod(d->stream, QLatin1String("QSettings::setFallbacksEnabled"), param1); + else + d->settings.setFallbacksEnabled(param1); +} + +void QSettingsWrapper::setIniCodec(QTextCodec *codec) +{ + d->settings.setIniCodec(codec); +} + +void QSettingsWrapper::setIniCodec(const char *codecName) +{ + d->settings.setIniCodec(codecName); +} + +void QSettingsWrapper::setValue(const QString ¶m1, const QVariant ¶m2) +{ + if (d->createSocket()) + callRemoteVoidMethod(d->stream, QLatin1String("QSettings::setValue"), param1, param2); + else + d->settings.setValue(param1, param2); +} + +QSettingsWrapper::Status QSettingsWrapper::status() const +{ + if (d->createSocket()) + return callRemoteMethod<QSettingsWrapper::Status>(d->stream, QLatin1String("QSettings::status")); + return static_cast<QSettingsWrapper::Status>(d->settings.status()); +} + +void QSettingsWrapper::sync() +{ + if (d->createSocket()) + callRemoteVoidMethod<void>(d->stream, QLatin1String("QSettings::sync")); + else + d->settings.sync(); +} + +QVariant QSettingsWrapper::value(const QString ¶m1, const QVariant ¶m2) const +{ + if (d->createSocket()) + return callRemoteMethod<QVariant>(d->stream, QLatin1String("QSettings::value"), param1, param2); + return d->settings.value(param1, param2); +} diff --git a/src/libs/installer/qsettingswrapper.h b/src/libs/installer/qsettingswrapper.h new file mode 100644 index 000000000..970b57414 --- /dev/null +++ b/src/libs/installer/qsettingswrapper.h @@ -0,0 +1,107 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef QSETTINGSWRAPPER_H +#define QSETTINGSWRAPPER_H + +#include <installer_global.h> + +#include <QtCore/QObject> +#include <QtCore/QVariant> + +class INSTALLER_EXPORT QSettingsWrapper : public QObject +{ + Q_OBJECT + +public: + enum Format { + NativeFormat, + IniFormat, + InvalidFormat + }; + + enum Status { + NoError, + AccessError, + FormatError + }; + + enum Scope { + UserScope, + SystemScope + }; + + explicit QSettingsWrapper(QObject *parent = 0); + explicit QSettingsWrapper(const QString &organization, const QString &application = QString(), + QObject *parent = 0); + QSettingsWrapper(const QString &fileName, QSettingsWrapper::Format format, QObject *parent = 0); + QSettingsWrapper(QSettingsWrapper::Scope scope, const QString &organization, + const QString &application = QString(), QObject *parent = 0); + QSettingsWrapper(QSettingsWrapper::Format format, QSettingsWrapper::Scope scope, + const QString &organization, const QString &application = QString(), QObject *parent = 0); + ~QSettingsWrapper(); + + QStringList allKeys() const; + QString applicationName() const; + void beginGroup(const QString &prefix); + int beginReadArray(const QString &prefix); + void beginWriteArray(const QString &prefix, int size = -1); + QStringList childGroups() const; + QStringList childKeys() const; + void clear(); + bool contains(const QString &key) const; + void endArray(); + void endGroup(); + bool fallbacksEnabled() const; + QString fileName() const; + QSettingsWrapper::Format format() const; + QString group() const; + QTextCodec* iniCodec() const; + bool isWritable() const; + QString organizationName() const; + void remove(const QString &key); + QSettingsWrapper::Scope scope() const; + void setArrayIndex(int i); + void setFallbacksEnabled(bool b); + void setIniCodec(QTextCodec *codec); + void setIniCodec(const char *codecName); + void setValue(const QString &key, const QVariant &value); + QSettingsWrapper::Status status() const; + void sync(); + QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; + +private: + class Private; + Private *d; +}; + +#endif // QSETTINGSWRAPPER_H diff --git a/src/libs/installer/qtcreator_constants.h b/src/libs/installer/qtcreator_constants.h new file mode 100644 index 000000000..d8c29a302 --- /dev/null +++ b/src/libs/installer/qtcreator_constants.h @@ -0,0 +1,77 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef QTCREATOR_CONSTANTS_H +#define QTCREATOR_CONSTANTS_H + +#if defined(Q_OS_MAC) + static const char QtCreatorSettingsSuffixPath[] = + "/Qt Creator.app/Contents/Resources/Nokia/QtCreator.ini"; +#else + static const char QtCreatorSettingsSuffixPath[] = + "/QtCreator/share/qtcreator/Nokia/QtCreator.ini"; +#endif + +#if defined(Q_OS_MAC) + static const char ToolChainSettingsSuffixPath[] = + "/Qt Creator.app/Contents/Resources/Nokia/toolChains.xml"; +#else + static const char ToolChainSettingsSuffixPath[] = + "/QtCreator/share/qtcreator/Nokia/toolChains.xml"; +#endif + +#if defined(Q_OS_MAC) + static const char QtVersionSettingsSuffixPath[] = + "/Qt Creator.app/Contents/Resources/Nokia/qtversion.xml"; +#else + static const char QtVersionSettingsSuffixPath[] = + "/QtCreator/share/qtcreator/Nokia/qtversion.xml"; +#endif + +// Begin - copied from Creator src\plugins\projectexplorer\toolchainmanager.cpp +static const char TOOLCHAIN_DATA_KEY[] = "ToolChain."; +static const char TOOLCHAIN_COUNT_KEY[] = "ToolChain.Count"; +static const char TOOLCHAIN_FILE_VERSION_KEY[] = "Version"; +static const char DEFAULT_DEBUGGER_COUNT_KEY[] = "DefaultDebugger.Count"; +static const char DEFAULT_DEBUGGER_ABI_KEY[] = "DefaultDebugger.Abi."; +static const char DEFAULT_DEBUGGER_PATH_KEY[] = "DefaultDebugger.Path."; + +static const char ID_KEY[] = "ProjectExplorer.ToolChain.Id"; +static const char DISPLAY_NAME_KEY[] = "ProjectExplorer.ToolChain.DisplayName"; +// End - copied from Creator + +// Begin - copied from Creator src\plugins\qt4projectmanager\qtversionmanager.cpp +static const char QtVersionsSectionName[] = "QtVersions"; +static const char newQtVersionsKey[] = "NewQtVersions"; +// End - copied from Creator + +#endif // QTCREATOR_CONSTANTS_H diff --git a/src/libs/installer/qtcreatorpersistentsettings.cpp b/src/libs/installer/qtcreatorpersistentsettings.cpp new file mode 100644 index 000000000..6f257ab5a --- /dev/null +++ b/src/libs/installer/qtcreatorpersistentsettings.cpp @@ -0,0 +1,227 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "qtcreatorpersistentsettings.h" +#include "qtcreator_constants.h" + +#include <QDebug> +#include <QStringList> +#include <QFile> +#include <QDir> + +QtCreatorPersistentSettings::QtCreatorPersistentSettings() +{ +} + +bool QtCreatorPersistentSettings::init(const QString &fileName) +{ + Q_ASSERT(!fileName.isEmpty()); + if (fileName.isEmpty()) + return false; + m_fileName = fileName; + if (m_reader.load(m_fileName) && !versionCheck()) + return false; + + m_toolChains = readValidToolChains(); + m_abiToDebuggerHash = readAbiToDebuggerHash(); + + return true; +} + +bool QtCreatorPersistentSettings::versionCheck() +{ + QVariantMap data = m_reader.restoreValues(); + + // Check version: + int version = data.value(QLatin1String(TOOLCHAIN_FILE_VERSION_KEY), 0).toInt(); + if (version < 1) + return false; + return true; +} + + +QHash<QString, QVariantMap> QtCreatorPersistentSettings::readValidToolChains() +{ + if (m_fileName.isEmpty()) + return QHash<QString, QVariantMap>(); + + QHash<QString, QVariantMap> toolChainHash; + + QVariantMap data = m_reader.restoreValues(); + + int count = data.value(QLatin1String(TOOLCHAIN_COUNT_KEY), 0).toInt(); + for (int i = 0; i < count; ++i) { + const QString key = QLatin1String(TOOLCHAIN_DATA_KEY) + QString::number(i); + + const QVariantMap toolChainMap = data.value(key).toMap(); + + //gets the path variable, hope ".Path" will stay in QtCreator + QStringList pathContainingKeyList = QStringList(toolChainMap.keys()).filter(QLatin1String( + ".Path")); + foreach (const QString& pathKey, pathContainingKeyList) { + QString path = toolChainMap.value(pathKey).toString(); + if (!path.isEmpty() && QFile::exists(path)) { + toolChainHash.insert(path, toolChainMap); + } + } + } + return toolChainHash; +} + +QHash<QString, QString> QtCreatorPersistentSettings::abiToDebuggerHash() +{ + return m_abiToDebuggerHash; +} + +QHash<QString, QString> QtCreatorPersistentSettings::readAbiToDebuggerHash() +{ + if (m_fileName.isEmpty()) + return QHash<QString, QString>(); + + QHash<QString, QString> abiToDebuggerHash; + + QVariantMap data = m_reader.restoreValues(); + + // Read default debugger settings (if any) + int count = data.value(QLatin1String(DEFAULT_DEBUGGER_COUNT_KEY)).toInt(); + for (int i = 0; i < count; ++i) { + const QString abiKey = QString::fromLatin1(DEFAULT_DEBUGGER_ABI_KEY) + QString::number(i); + if (!data.contains(abiKey)) + continue; + const QString pathKey = QString::fromLatin1(DEFAULT_DEBUGGER_PATH_KEY) + QString::number(i); + if (!data.contains(pathKey)) + continue; + abiToDebuggerHash.insert(data.value(abiKey).toString(), data.value(pathKey).toString()); + } + return abiToDebuggerHash; +} + +bool QtCreatorPersistentSettings::addToolChain(const QtCreatorToolChain &toolChain) +{ + if (toolChain.key.isEmpty()) + return false; + if (toolChain.type.isEmpty()) + return false; + if (toolChain.displayName.isEmpty()) + return false; + if (toolChain.abiString.isEmpty()) + return false; + if (toolChain.compilerPath.isEmpty()) + return false; + + QVariantMap newToolChainVariantMap; + + newToolChainVariantMap.insert(QLatin1String(ID_KEY), + QString::fromLatin1("%1:%2.%3").arg(toolChain.type, QFileInfo(toolChain.compilerPath + ).absoluteFilePath(), toolChain.abiString)); + newToolChainVariantMap.insert(QLatin1String(DISPLAY_NAME_KEY), toolChain.displayName); + newToolChainVariantMap.insert(QString::fromLatin1("ProjectExplorer.%1.Path").arg(toolChain.key), + QFileInfo(toolChain.compilerPath).absoluteFilePath()); + newToolChainVariantMap.insert(QString::fromLatin1("ProjectExplorer.%1.TargetAbi").arg(toolChain.key), + toolChain.abiString); + newToolChainVariantMap.insert(QString::fromLatin1("ProjectExplorer.%1.Debugger").arg(toolChain.key), + QFileInfo(toolChain.debuggerPath).absoluteFilePath()); + + m_toolChains.insert(QFileInfo(toolChain.compilerPath).absoluteFilePath(), newToolChainVariantMap); + return true; +} + +bool QtCreatorPersistentSettings::removeToolChain(const QtCreatorToolChain &toolChain) +{ + m_toolChains.remove(QFileInfo(toolChain.compilerPath).absoluteFilePath()); + return true; +} + +void QtCreatorPersistentSettings::addDefaultDebugger(const QString &abiString, const QString &debuggerPath) +{ + m_abiToDebuggerHash.insert(abiString, debuggerPath); +} + +void QtCreatorPersistentSettings::removeDefaultDebugger(const QString &abiString) +{ + m_abiToDebuggerHash.remove(abiString); +} + +bool QtCreatorPersistentSettings::save() +{ + if (m_fileName.isEmpty()) + return false; + + m_writer.saveValue(QLatin1String(DEFAULT_DEBUGGER_COUNT_KEY), m_abiToDebuggerHash.count()); + + QHashIterator<QString, QString> it(m_abiToDebuggerHash); + int debuggerCounter = 0; + while (it.hasNext()) { + it.next(); + const QString abiKey = QString::fromLatin1(DEFAULT_DEBUGGER_ABI_KEY) + QString::number( + debuggerCounter); + const QString pathKey = QString::fromLatin1(DEFAULT_DEBUGGER_PATH_KEY) + QString::number( + debuggerCounter); + m_writer.saveValue(abiKey, it.key()); + m_writer.saveValue(pathKey, it.value()); + debuggerCounter++; + } + + m_writer.saveValue(QLatin1String(TOOLCHAIN_COUNT_KEY), m_toolChains.count()); + + int toolChainCounter = 0; + + foreach (QVariantMap toolChainMap, m_toolChains.values()) { + if (toolChainMap.isEmpty()) + continue; + + //if we added a new debugger we need to adjust the tool chains + QString abiString; + //find the abiString + foreach (const QString &key, toolChainMap.keys()) { + if (key.contains(QLatin1String(".TargetAbi"))) { + abiString = toolChainMap.value(key).toString(); + break; + } + } + //adjust debugger path + foreach (const QString &key, toolChainMap.keys()) { + if (key.contains(QLatin1String(".Debugger"))) { + toolChainMap.insert(key, m_abiToDebuggerHash.value(abiString)); + break; + } + } + + m_writer.saveValue(QLatin1String(TOOLCHAIN_DATA_KEY) + QString::number(toolChainCounter++), + toolChainMap); + } + + m_writer.saveValue(QLatin1String(TOOLCHAIN_FILE_VERSION_KEY), 1); + + QDir().mkpath(QFileInfo(m_fileName).absolutePath()); + return m_writer.save(m_fileName, QLatin1String("QtCreatorToolChains")); +} diff --git a/src/libs/installer/qtcreatorpersistentsettings.h b/src/libs/installer/qtcreatorpersistentsettings.h new file mode 100644 index 000000000..6e7a251a6 --- /dev/null +++ b/src/libs/installer/qtcreatorpersistentsettings.h @@ -0,0 +1,79 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef QTCREATORPERSISTENTSETTINGS_H +#define QTCREATORPERSISTENTSETTINGS_H + +#include "persistentsettings.h" + +#include <QHash> +#include <QString> +#include <QVariantMap> + +struct QtCreatorToolChain +{ + QString key; + QString type; + QString displayName; + QString abiString; + QString compilerPath; + QString debuggerPath; + QString armVersion; + QString force32Bit; +}; + +class QtCreatorPersistentSettings +{ +public: + QtCreatorPersistentSettings(); + bool init(const QString &fileName); + bool addToolChain(const QtCreatorToolChain &toolChain); + bool removeToolChain(const QtCreatorToolChain &toolChain); + void addDefaultDebugger(const QString &abiString, const QString &debuggerPath); + void removeDefaultDebugger(const QString &abiString); + bool save(); + QHash<QString, QString> abiToDebuggerHash(); + +private: + QHash<QString, QVariantMap> readValidToolChains(); + QHash<QString, QString> readAbiToDebuggerHash(); + bool versionCheck(); + + QString m_fileName; + QHash<QString, QVariantMap> m_toolChains; + QHash<QString, QString> m_abiToDebuggerHash; + ProjectExplorer::PersistentSettingsReader m_reader; + ProjectExplorer::PersistentSettingsWriter m_writer; + +}; + +#endif // QTCREATORPERSISTENTSETTINGS_H diff --git a/src/libs/installer/qtpatch.cpp b/src/libs/installer/qtpatch.cpp new file mode 100644 index 000000000..bff86506a --- /dev/null +++ b/src/libs/installer/qtpatch.cpp @@ -0,0 +1,233 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "qtpatch.h" + +#include <QString> +#include <QStringList> +#include <QFileInfo> +#include <QProcess> +#include <QTextStream> +#include <QVector> +#include <QTime> +#include <QtCore/QDebug> +#include <QCoreApplication> +#include <QByteArrayMatcher> + +#ifdef Q_OS_WIN +#include <windows.h> // for Sleep +#endif +#ifdef Q_OS_UNIX +#include <errno.h> +#include <signal.h> +#include <time.h> +#endif + +static void sleepCopiedFromQTest(int ms) +{ + if (ms < 0) + return; +#ifdef Q_OS_WIN + Sleep(uint(ms)); +#else + struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 }; + nanosleep(&ts, NULL); +#endif +} + +static void uiDetachedWait(int ms) +{ + QTime timer; + timer.start(); + do { + QCoreApplication::processEvents(QEventLoop::AllEvents, ms); + sleepCopiedFromQTest(10); + } while (timer.elapsed() < ms); +} + +QHash<QString, QByteArray> QtPatch::qmakeValues(const QString &qmakePath, QByteArray *qmakeOutput) +{ + QHash<QString, QByteArray> qmakeValueHash; + + // in some cases qmake is not runable, because another process is blocking it(filewatcher ...) + int waitCount = 0; + while (qmakeValueHash.isEmpty() && waitCount < 60) { + QFileInfo qmake(qmakePath); + + if (!qmake.exists()) { + qmakeOutput->append(QString::fromLatin1("%1 is not existing").arg(qmakePath)); + return qmakeValueHash; + } + if (!qmake.isExecutable()) { + qmakeOutput->append(QString::fromLatin1("%1 is not executable").arg(qmakePath)); + return qmakeValueHash; + } + + QStringList args; + args << QLatin1String("-query"); + + QProcess process; + process.start(qmake.absoluteFilePath(), args, QIODevice::ReadOnly); + if (process.waitForFinished(2000)) { + if (process.exitStatus() == QProcess::CrashExit) { + qDebug() << qmakePath << "was crashed"; + return qmakeValueHash; + } + QByteArray output = process.readAllStandardOutput(); + qmakeOutput->append(output); + QTextStream stream(&output); + while (!stream.atEnd()) { + const QString line = stream.readLine(); + const int index = line.indexOf(QLatin1Char(':')); + if (index != -1) { + QString value = line.mid(index+1); + if (value != QLatin1String("**Unknown**") ) + qmakeValueHash.insert(line.left(index), value.toUtf8()); + } + } + } + if (qmakeValueHash.isEmpty()) { + ++waitCount; + static const int waitTimeInMilliSeconds = 500; + uiDetachedWait(waitTimeInMilliSeconds); + } + if (process.state() > QProcess::NotRunning ) { + qDebug() << "qmake process is still running, need to kill it."; + process.kill(); + } + + } + if (qmakeValueHash.isEmpty()) + qDebug() << "Can't get any query output from qmake."; + return qmakeValueHash; +} + +bool QtPatch::patchBinaryFile(const QString &fileName, + const QByteArray &oldQtPath, + const QByteArray &newQtPath) +{ + QFile file(fileName); + if (!file.exists()) { + qDebug() << "qpatch: warning: file" << fileName << "not found"; + return false; + } + + openFileForPatching(&file); + if (!file.isOpen()) { + qDebug() << "qpatch: warning: file" << qPrintable(fileName) << "can not open."; + qDebug() << qPrintable(file.errorString()); + return false; + } + + bool isPatched = patchBinaryFile(&file, oldQtPath, newQtPath); + + file.close(); + return isPatched; +} + +// device must be open +bool QtPatch::patchBinaryFile(QIODevice *device, + const QByteArray &oldQtPath, + const QByteArray &newQtPath) +{ + if (!(device->openMode() == QIODevice::ReadWrite)) { + qDebug() << "qpatch: warning: This function needs an open device for writing."; + return false; + } + const QByteArray source = device->readAll(); + device->seek(0); + + int offset = 0; + QByteArray overwritePath(newQtPath); + if (overwritePath.size() < oldQtPath.size()) { + QByteArray fillByteArray(oldQtPath.size() - overwritePath.size(), '\0'); + overwritePath.append(fillByteArray); + } + + QByteArrayMatcher byteArrayMatcher(oldQtPath); + forever { + offset = byteArrayMatcher.indexIn(source, offset); + if (offset == -1) + break; + device->seek(offset); + device->write(overwritePath); + offset += overwritePath.size(); + } + device->seek(0); //for next reading we should be at the beginning + return true; +} + +bool QtPatch::patchTextFile(const QString &fileName, + const QHash<QByteArray, QByteArray> &searchReplacePairs) +{ + QFile file(fileName); + + if (!file.open(QFile::ReadOnly)) { + qDebug() << QString::fromLatin1("qpatch: warning: Open the file '%1' stopped: %2").arg( + fileName, file.errorString()); + return false; + } + + QByteArray source = file.readAll(); + file.close(); + + QHashIterator<QByteArray, QByteArray> it(searchReplacePairs); + while (it.hasNext()) { + it.next(); + source.replace(it.key(), it.value()); + } + + if (!file.open(QFile::WriteOnly | QFile::Truncate)) { + qDebug() << QString::fromLatin1("qpatch: error: file '%1' not writable").arg(fileName); + return false; + } + + file.write(source); + return true; +} + +bool QtPatch::openFileForPatching(QFile *file) +{ + if (file->openMode() == QIODevice::NotOpen) { + // in some cases the file can not open, because another process is blocking it(filewatcher ...) + int waitCount = 0; + while (!file->open(QFile::ReadWrite) && waitCount < 60) { + ++waitCount; + static const int waitTimeInMilliSeconds = 500; + uiDetachedWait(waitTimeInMilliSeconds); + } + return file->openMode() == QFile::ReadWrite; + } + qDebug() << QString::fromLatin1("qpatch: error: File '%1 is open, so it can not open it again.").arg( + file->fileName()); + return false; +} diff --git a/src/libs/installer/qtpatch.h b/src/libs/installer/qtpatch.h new file mode 100644 index 000000000..566ce9903 --- /dev/null +++ b/src/libs/installer/qtpatch.h @@ -0,0 +1,60 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef QTPATCH_H +#define QTPATCH_H + +#include "installer_global.h" +#include <QString> +#include <QByteArray> +#include <QHash> +#include <QFile> + +namespace QtPatch { + +QHash<QString, QByteArray> INSTALLER_EXPORT qmakeValues(const QString &qmakePath, QByteArray *qmakeOutput); + +bool INSTALLER_EXPORT patchBinaryFile(const QString &fileName, + const QByteArray &oldQtPath, + const QByteArray &newQtPath ); + +bool INSTALLER_EXPORT patchBinaryFile(QIODevice *device, + const QByteArray &oldQtPath, + const QByteArray &newQtPath ); + +bool INSTALLER_EXPORT patchTextFile(const QString &fileName, + const QHash<QByteArray, QByteArray> &searchReplacePairs); +bool INSTALLER_EXPORT openFileForPatching(QFile *file); + +} + +#endif // QTPATCH_H diff --git a/src/libs/installer/qtpatchoperation.cpp b/src/libs/installer/qtpatchoperation.cpp new file mode 100644 index 000000000..348d52e48 --- /dev/null +++ b/src/libs/installer/qtpatchoperation.cpp @@ -0,0 +1,343 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "qtpatchoperation.h" +#include "qtpatch.h" +#ifdef Q_OS_MAC +#include "macrelocateqt.h" +#endif + +#include "constants.h" +#include "packagemanagercore.h" + +#include <QMap> +#include <QSet> +#include <QFile> +#include <QTextStream> +#include <QDir> +#include <QtCore/QDebug> + +using namespace QInstaller; + +static QMap<QByteArray, QByteArray> generatePatchValueMap(const QByteArray &newQtPath, + const QHash<QString, QByteArray> &qmakeValueHash) +{ + QMap<QByteArray, QByteArray> replaceMap; //first == searchstring: second == replace string + char nativeSeperator = QDir::separator().toAscii(); + QByteArray oldValue; + + oldValue = qmakeValueHash.value(QLatin1String("QT_INSTALL_PREFIX")); + replaceMap.insert(QByteArray("qt_prfxpath=%1").replace("%1", oldValue), + QByteArray("qt_prfxpath=%1/").replace("%1/", newQtPath)); + + oldValue = qmakeValueHash.value(QLatin1String("QT_INSTALL_DOCS")); + replaceMap.insert(QByteArray("qt_docspath=%1").replace("%1", oldValue), + QByteArray("qt_docspath=%1/doc").replace("%1/", newQtPath + nativeSeperator)); + + oldValue = qmakeValueHash.value(QLatin1String("QT_INSTALL_HEADERS")); + replaceMap.insert(QByteArray("qt_hdrspath=%1").replace("%1", oldValue), + QByteArray("qt_hdrspath=%1/include").replace("%1/", newQtPath + nativeSeperator)); + + oldValue = qmakeValueHash.value(QLatin1String("QT_INSTALL_LIBS")); + replaceMap.insert(QByteArray("qt_libspath=%1").replace("%1", oldValue), + QByteArray("qt_libspath=%1/lib").replace("%1/", newQtPath + nativeSeperator)); + + oldValue = qmakeValueHash.value(QLatin1String("QT_INSTALL_BINS")); + replaceMap.insert(QByteArray("qt_binspath=%1").replace("%1", oldValue), + QByteArray("qt_binspath=%1/bin").replace("%1/", newQtPath + nativeSeperator)); + + oldValue = qmakeValueHash.value(QLatin1String("QT_INSTALL_PLUGINS")); + replaceMap.insert(QByteArray("qt_plugpath=%1").replace("%1", oldValue), + QByteArray("qt_plugpath=%1/plugins").replace("%1/", newQtPath + nativeSeperator)); + + oldValue = qmakeValueHash.value(QLatin1String("QT_INSTALL_IMPORTS")); + replaceMap.insert(QByteArray("qt_impspath=%1").replace("%1", oldValue), + QByteArray("qt_impspath=%1/imports").replace("%1/", newQtPath + nativeSeperator)); + + oldValue = qmakeValueHash.value(QLatin1String("QT_INSTALL_DATA")); + replaceMap.insert( QByteArray("qt_datapath=%1").replace("%1", oldValue), + QByteArray("qt_datapath=%1/").replace("%1/", newQtPath + nativeSeperator)); + + oldValue = qmakeValueHash.value(QLatin1String("QT_INSTALL_TRANSLATIONS")); + replaceMap.insert( QByteArray("qt_trnspath=%1").replace("%1", oldValue), + QByteArray("qt_trnspath=%1/translations").replace("%1/", newQtPath + nativeSeperator)); + + // This must not be patched. Commenting out to fix QTSDK-429 + // oldValue = qmakeValueHash.value(QLatin1String("QT_INSTALL_CONFIGURATION")); + // replaceMap.insert( QByteArray("qt_stngpath=%1").replace("%1", oldValue), + // QByteArray("qt_stngpath=%1").replace("%1", newQtPath)); + + //examples and demoes can patched outside separately, + //but for cosmetic reasons - if the qt version gets no examples later. + oldValue = qmakeValueHash.value(QLatin1String("QT_INSTALL_EXAMPLES")); + replaceMap.insert( QByteArray("qt_xmplpath=%1").replace("%1", oldValue), + QByteArray("qt_xmplpath=%1/examples").replace("%1/", newQtPath + nativeSeperator)); + + oldValue = qmakeValueHash.value(QLatin1String("QT_INSTALL_DEMOS")); + replaceMap.insert( QByteArray("qt_demopath=%1").replace("%1", oldValue), + QByteArray("qt_demopath=%1/demos").replace("%1/", newQtPath + nativeSeperator)); + + return replaceMap; +} + +QtPatchOperation::QtPatchOperation() +{ + setName(QLatin1String("QtPatch")); +} + +void QtPatchOperation::backup() +{ +} + +bool QtPatchOperation::performOperation() +{ + // Arguments: + // 1. type + // 2. new/target qtpath + + if (arguments().count() != 2) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in %0: %1 arguments given, 2 expected.").arg(name()) + .arg(arguments().count())); + return false; + } + + QString type = arguments().at(0); + bool isPlatformSupported = type.contains(QLatin1String("linux"), Qt::CaseInsensitive) + || type.contains(QLatin1String("windows"), Qt::CaseInsensitive) + || type.contains(QLatin1String("mac"), Qt::CaseInsensitive); + if (!isPlatformSupported) { + setError(InvalidArguments); + setErrorString(tr("First argument should be 'linux', 'mac' or 'windows'. No other type is supported " + "at this time.")); + return false; + } + + const QString newQtPathStr = QDir::toNativeSeparators(arguments().at(1)); + const QByteArray newQtPath = newQtPathStr.toUtf8(); + + QString qmakePath = QString::fromUtf8(newQtPath) + QLatin1String("/bin/qmake"); +#ifdef Q_OS_WIN + qmakePath = qmakePath + QLatin1String(".exe"); +#endif + + if (!QFile::exists(qmakePath)) { + setError(UserDefinedError); + setErrorString(tr("QMake from the current Qt version \n(%1)is not existing. Please file a bugreport " + "with this dialog at https://bugreports.qt-project.org.").arg(QDir::toNativeSeparators(qmakePath))); + return false; + } + + QByteArray qmakeOutput; + QHash<QString, QByteArray> qmakeValueHash = QtPatch::qmakeValues(qmakePath, &qmakeOutput); + + if (qmakeValueHash.isEmpty()) { + setError(UserDefinedError); + setErrorString(tr("The output of \n%1 -query\nis not parseable. Please file a bugreport with this " + "dialog https://bugreports.qt-project.org.\noutput: \"%2\"").arg(QDir::toNativeSeparators(qmakePath), + QString::fromUtf8(qmakeOutput))); + return false; + } + + const QByteArray oldQtPath = qmakeValueHash.value(QLatin1String("QT_INSTALL_PREFIX")); + bool oldQtPathFromQMakeIsEmpty = oldQtPath.isEmpty(); + + //maybe we don't need this, but I 255 should be a rational limit + if (255 < newQtPath.size()) { + setError(UserDefinedError); + setErrorString(tr("Qt patch error: new Qt dir(%1)\nneeds to be less than 255 characters.") + .arg(newQtPathStr)); + return false; + } + + QFile patchFileListFile; + if (type == QLatin1String("windows")) + patchFileListFile.setFileName(QLatin1String(":/files-to-patch-windows")); + else if (type == QLatin1String("linux")) + patchFileListFile.setFileName(QLatin1String(":/files-to-patch-linux")); + else if (type == QLatin1String("linux-emb-arm")) + patchFileListFile.setFileName(QLatin1String(":/files-to-patch-linux-emb-arm")); + else if (type == QLatin1String("mac")) + patchFileListFile.setFileName(QLatin1String(":/files-to-patch-macx")); + + if (!patchFileListFile.open(QFile::ReadOnly)) { + setError(UserDefinedError); + setErrorString(tr("Qt patch error: Can not open %1.(%2)").arg(patchFileListFile.fileName(), + patchFileListFile.errorString())); + return false; + } + + QStringList filesToPatch, textFilesToPatch; + bool readingTextFilesToPatch = false; + + // read the input file + QTextStream in(&patchFileListFile); + + forever { + const QString line = in.readLine(); + + if (line.isNull()) + break; + + else if (line.isEmpty()) + continue; + + else if (line.startsWith(QLatin1String("%%"))) + readingTextFilesToPatch = true; + + //with empty old path we don't know what we want to replace + else if (readingTextFilesToPatch && !oldQtPathFromQMakeIsEmpty) + textFilesToPatch.append(line); + + else + filesToPatch.append(line); + } + + QString prefix = QFile::decodeName(newQtPath); + + if (! prefix.endsWith(QLatin1Char('/'))) + prefix += QLatin1Char('/'); + +//BEGIN - patch binary files + QMap<QByteArray, QByteArray> patchValueMap = generatePatchValueMap(newQtPath, qmakeValueHash); + + foreach (QString fileName, filesToPatch) { + fileName.prepend(prefix); + QFile file(fileName); + + //without a file we can't do anything + if (!file.exists()) { + continue; + } + + if (!QtPatch::openFileForPatching(&file)) { + setError(UserDefinedError); + setErrorString(tr("Qt patch error: Can not open %1.(%2)").arg(file.fileName()) + .arg(file.errorString())); + return false; + } + + QMapIterator<QByteArray, QByteArray> it(patchValueMap); + while (it.hasNext()) { + it.next(); + bool isPatched = QtPatch::patchBinaryFile(&file, it.key(), it.value()); + if (!isPatched) { + qDebug() << QString::fromLatin1("qpatch: warning: file '%1' could not patched").arg(fileName); + } + } + } //foreach (QString fileName, filesToPatch) +//END - patch binary files + +//BEGIN - patch text files + QByteArray newQtPathWithNormalSlashes = QDir::fromNativeSeparators(newQtPathStr).toUtf8(); + + QHash<QByteArray, QByteArray> searchReplacePairs; + searchReplacePairs.insert(oldQtPath, newQtPathWithNormalSlashes); + searchReplacePairs.insert(QByteArray(oldQtPath).replace("/", "\\"), newQtPathWithNormalSlashes); + searchReplacePairs.insert(QByteArray(oldQtPath).replace("\\", "/"), newQtPathWithNormalSlashes); + +#ifdef Q_OS_WIN + QByteArray newQtPathWithDoubleBackSlashes = QByteArray(newQtPathWithNormalSlashes).replace("/", "\\\\"); + searchReplacePairs.insert(QByteArray(oldQtPath).replace("/", "\\\\"), newQtPathWithDoubleBackSlashes); + searchReplacePairs.insert(QByteArray(oldQtPath).replace("\\", "\\\\"), newQtPathWithDoubleBackSlashes); + + //this is checking for a possible drive letter, which could be upper or lower + if (oldQtPath.mid(1,1) == ":") { + QHash<QByteArray, QByteArray> tempSearchReplacePairs; + QHashIterator<QByteArray, QByteArray> it(searchReplacePairs); + QByteArray driveLetter = oldQtPath.left(1); + while (it.hasNext()) { + it.next(); + QByteArray currentPossibleSearchByteArrayWithoutDriveLetter = QByteArray(it.key()).remove(0, 1); + tempSearchReplacePairs.insert(driveLetter.toLower() + + currentPossibleSearchByteArrayWithoutDriveLetter, it.value()); + tempSearchReplacePairs.insert(driveLetter.toUpper() + + currentPossibleSearchByteArrayWithoutDriveLetter, it.value()); + } + searchReplacePairs = tempSearchReplacePairs; + } +#endif + + foreach (QString fileName, textFilesToPatch) { + fileName.prepend(prefix); + + if (QFile::exists(fileName)) { + //TODO: use the return value for an error message at the end of the operation + QtPatch::patchTextFile(fileName, searchReplacePairs); + } + } +//END - patch text files + +#ifdef Q_OS_MAC + Relocator relocator; + bool successMacRelocating = false; + PackageManagerCore *const core = qVariantValue<PackageManagerCore*>(value(QLatin1String("installer"))); + if (!core) { + setError(UserDefinedError); + setErrorString(tr("Needed installer object in \"%1\" operation is empty.").arg(name())); + return false; + } + Q_CHECK_PTR(core); + successMacRelocating = relocator.apply(newQtPathStr, core->value(scTargetDir)); + if (!successMacRelocating) { + setError(UserDefinedError); + setErrorString(tr("Error while relocating Qt: %1").arg(relocator.errorMessage())); + return false; + } +#endif + if (oldQtPathFromQMakeIsEmpty) { + setError(UserDefinedError); + setErrorString(tr("The installer was not able to get the unpatched path from \n%1.(maybe it is " + "broken or removed)\nIt tried to patch the Qt binaries, but all other files in Qt are unpatched." + "\nThis could result in a broken Qt version.\nSometimes it helps to restart the installer with a " + "switched off antivirus software.").arg(QDir::toNativeSeparators(qmakePath))); + return false; + } + + return true; +} + +bool QtPatchOperation::undoOperation() +{ + return true; +} + +bool QtPatchOperation::testOperation() +{ + return true; +} + +Operation *QtPatchOperation::clone() const +{ + return new QtPatchOperation(); +} + diff --git a/src/libs/installer/qtpatchoperation.h b/src/libs/installer/qtpatchoperation.h new file mode 100644 index 000000000..31f3abb4e --- /dev/null +++ b/src/libs/installer/qtpatchoperation.h @@ -0,0 +1,54 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef QTPATCHOPERATION_H +#define QTPATCHOPERATION_H + +#include "qinstallerglobal.h" + +namespace QInstaller { + +class QtPatchOperation : public Operation +{ +public: + QtPatchOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation *clone() const; +}; + +} // namespace QInstaller + +#endif // QTPATCHOPERATION_H diff --git a/src/libs/installer/range.h b/src/libs/installer/range.h new file mode 100644 index 000000000..f97aa0317 --- /dev/null +++ b/src/libs/installer/range.h @@ -0,0 +1,88 @@ +/************************************************************************** +** +** This file is part of Installer Framework** +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).* +** +** Contact: Nokia Corporation qt-info@nokia.com** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception version +** 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you are unsure which license is appropriate for your use, please contact +** (qt-info@nokia.com). +** +**************************************************************************/ +#ifndef RANGE_H +#define RANGE_H + +#include <algorithm> + +template <typename T> +class Range { +public: + static Range<T> fromStartAndEnd( const T& start, const T& end ) { + Range<T> r; + r.m_start = start; + r.m_end = end; + return r; + } + + static Range<T> fromStartAndLength( const T& start, const T& length ) { + Range<T> r; + r.m_start = start; + r.m_end = start + length; + return r; + } + + Range() : m_start( 0 ), m_end( 0 ) {} + + T start() const { return m_start; } + + T end() const { return m_end; } + + void move( const T& by ) { + m_start += by; + m_end += by; + } + + Range<T> moved( const T& by ) const { + Range<T> b = *this; + b.move( by ); + return b; + } + + T length() const { return m_end - m_start; } + + Range<T> normalized() const { + Range<T> r2( *this ); + if ( r2.m_start > r2.m_end ) + std::swap( r2.m_start, r2.m_end ); + return r2; + } + + bool operator==( const Range<T>& other ) const { + return m_start == other.m_start && m_end && other.m_end; + } + bool operator<( const Range<T>& other ) const { + if ( m_start != other.m_start ) + return m_start < other.m_start; + return m_end < other.m_end; + } + +private: + T m_start; + T m_end; +}; + +#endif /* RANGE_H_ */ diff --git a/src/libs/installer/registerdefaultdebuggeroperation.cpp b/src/libs/installer/registerdefaultdebuggeroperation.cpp new file mode 100644 index 000000000..2cdb4e6bc --- /dev/null +++ b/src/libs/installer/registerdefaultdebuggeroperation.cpp @@ -0,0 +1,160 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "registerdefaultdebuggeroperation.h" + +#include "constants.h" +#include "persistentsettings.h" +#include "packagemanagercore.h" +#include "qtcreator_constants.h" +#include "qtcreatorpersistentsettings.h" + +#include <QString> +#include <QFileInfo> +#include <QDir> +#include <QSettings> +#include <QDebug> + +using namespace QInstaller; + +using namespace ProjectExplorer; + +//TODO move this to a general location it is used on some classes +static QString fromNativeSeparatorsAllOS(const QString &pathName) +{ + QString n = pathName; + for (int i = 0; i < n.size(); ++i) { + if (n.at(i) == QLatin1Char('\\')) + n[i] = QLatin1Char('/'); + } + return n; +} + +RegisterDefaultDebuggerOperation::RegisterDefaultDebuggerOperation() +{ + setName(QLatin1String("RegisterDefaultDebugger")); +} + +void RegisterDefaultDebuggerOperation::backup() +{ +} + +/** application binary interface - this is an internal creator typ as a String CPU-OS-OS_FLAVOR-BINARY_FORMAT-WORD_WIDTH + * CPU: arm x86 mips ppc itanium + * OS: linux macos symbian unix windows + * OS_FLAVOR: generic maemo meego generic device emulator generic msvc2005 msvc2008 msvc2010 msys ce + * BINARY_FORMAT: elf pe mach_o qml_rt + * WORD_WIDTH: 8 16 32 64 + */ + +bool RegisterDefaultDebuggerOperation::performOperation() +{ + const QStringList args = arguments(); + + if (args.count() != 2) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in %0: %1 arguments given, 2 expected.") + .arg(name()).arg(args.count())); + return false; + } + + QString toolChainsXmlFilePath; + + PackageManagerCore *const core = qVariantValue<PackageManagerCore *>(value(QLatin1String("installer"))); + if (!core) { + setError(UserDefinedError); + setErrorString(tr("Needed installer object in \"%1\" operation is empty.").arg(name())); + return false; + } + const QString &rootInstallPath = core->value(scTargetDir); + toolChainsXmlFilePath = rootInstallPath + QLatin1String(ToolChainSettingsSuffixPath); + + int argCounter = 0; + const QString &abiString = args.at(argCounter++); //for example x86-windows-msys-pe-32bit + const QString &debuggerPath = fromNativeSeparatorsAllOS(args.at(argCounter++)); + + QtCreatorPersistentSettings creatorToolChainSettings; + + if (!creatorToolChainSettings.init(toolChainsXmlFilePath)) { + setError(UserDefinedError); + setErrorString(tr("Can't read from tool chains xml file(%1) correctly.") + .arg(toolChainsXmlFilePath)); + return false; + } + + creatorToolChainSettings.addDefaultDebugger(abiString, debuggerPath); + return creatorToolChainSettings.save(); +} + +bool RegisterDefaultDebuggerOperation::undoOperation() +{ + const QStringList args = arguments(); + + if (args.count() != 2) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in %0: %1 arguments given, 2 expected.") + .arg(name()).arg(args.count())); + return false; + } + + QString toolChainsXmlFilePath; + + PackageManagerCore *const core = qVariantValue<PackageManagerCore *>(value(QLatin1String("installer"))); + if (!core) { + setError(UserDefinedError); + setErrorString(tr("Needed installer object in \"%1\" operation is empty.").arg(name())); + return false; + } + const QString &rootInstallPath = core->value(scTargetDir); + toolChainsXmlFilePath = rootInstallPath + QLatin1String(ToolChainSettingsSuffixPath); + + int argCounter = 0; + const QString &abiString = args.at(argCounter++); //for example x86-windows-msys-pe-32bit + const QString &debuggerPath = fromNativeSeparatorsAllOS(args.at(argCounter++)); + Q_UNUSED(debuggerPath) + + QtCreatorPersistentSettings creatorToolChainSettings; + + creatorToolChainSettings.init(toolChainsXmlFilePath); + creatorToolChainSettings.removeDefaultDebugger(abiString); + return creatorToolChainSettings.save(); +} + +bool RegisterDefaultDebuggerOperation::testOperation() +{ + return true; +} + +Operation *RegisterDefaultDebuggerOperation::clone() const +{ + return new RegisterDefaultDebuggerOperation(); +} diff --git a/src/libs/installer/registerdefaultdebuggeroperation.h b/src/libs/installer/registerdefaultdebuggeroperation.h new file mode 100644 index 000000000..6baabb1a6 --- /dev/null +++ b/src/libs/installer/registerdefaultdebuggeroperation.h @@ -0,0 +1,54 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef REGISTERDEFAULTDEBUGGEROPERATION_H +#define REGISTERDEFAULTDEBUGGEROPERATION_H + +#include "qinstallerglobal.h" + +namespace QInstaller { + +class RegisterDefaultDebuggerOperation : public Operation +{ +public: + RegisterDefaultDebuggerOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation *clone() const; +}; + +} // namespace QInstaller + +#endif // REGISTERDEFAULTDEBUGGEROPERATION_H diff --git a/src/libs/installer/registerdocumentationoperation.cpp b/src/libs/installer/registerdocumentationoperation.cpp new file mode 100644 index 000000000..837128c07 --- /dev/null +++ b/src/libs/installer/registerdocumentationoperation.cpp @@ -0,0 +1,153 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "registerdocumentationoperation.h" + +#include <QSettings> +#include <QHelpEngine> +#include <QString> +#include <QFileInfo> +#include <QDir> +#include <QtCore/QDebug> + +using namespace QInstaller; + +RegisterDocumentationOperation::RegisterDocumentationOperation() +{ + setName(QLatin1String("RegisterDocumentation")); +} + +void RegisterDocumentationOperation::backup() +{ +} + +// get the right filename of the qsettingsfile (root/user) +static QString settingsFileName() +{ + #if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) + // If the system settings are writable, don't touch the user settings. + // The reason is that a doc registered while running with sudo could otherwise create + // a root-owned configuration file a user directory. + QScopedPointer<QSettings> settings(new QSettings(QSettings::IniFormat, + QSettings::SystemScope, QLatin1String("Nokia"), QLatin1String("QtCreator"))); + + // QSettings::isWritable isn't reliable enough in 4.7, determine writability experimentally + settings->setValue(QLatin1String("iswritable"), QLatin1String("accomplished")); + settings->sync(); + if (settings->status() == QSettings::NoError) { + // we can use the system settings + if (settings->contains(QLatin1String("iswritable"))) + settings->remove(QLatin1String("iswritable")); + } else { + // we have to use user settings + settings.reset(new QSettings(QSettings::IniFormat, QSettings::UserScope, + QLatin1String("Nokia"), QLatin1String("QtCreator"))); + } + + #else + QScopedPointer<QSettings> settings(new QSettings(QSettings::IniFormat, + QSettings::UserScope, QLatin1String("Nokia"), QLatin1String("QtCreator"))); + #endif + return settings->fileName(); +} + +bool RegisterDocumentationOperation::performOperation() +{ + const QStringList args = arguments(); + + if (args.count() != 1) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in %0: %1 arguments given, 1 expected.") + .arg(name()).arg(args.count())); + return false; + } + const QString helpFile = args.at(0); + + QFileInfo fileInfo(settingsFileName()); + QDir settingsDir(fileInfo.absolutePath() + QLatin1String("/qtcreator")); + + if (!settingsDir.exists()) + settingsDir.mkpath(settingsDir.absolutePath()); + const QString collectionFile = settingsDir.absolutePath() + QLatin1String("/helpcollection.qhc"); + + if (!QFileInfo(helpFile).exists()) { + setError(UserDefinedError); + setErrorString(tr("Could not register help file %1: File not found.").arg(helpFile)); + return false; + } + + QHelpEngineCore help(collectionFile); + QString oldData = help.customValue(QLatin1String("AddedDocs")).toString(); + if (!oldData.isEmpty()) + oldData.append(QLatin1Char(';')); + const QString newData = oldData + QFileInfo(helpFile).absoluteFilePath(); + if (!help.setCustomValue(QLatin1String("AddedDocs"), newData)) { + qWarning() << "Can't register doc file:" << helpFile << help.error(); +// setError(UserDefinedError); +// setErrorString(help.error()); +// return false; + } + return true; +} + +bool RegisterDocumentationOperation::undoOperation() +{ + const QString helpFile = arguments().first(); + + QFileInfo fileInfo(settingsFileName()); + QDir settingsDir(fileInfo.absolutePath() + QLatin1String("/qtcreator")); + + if (!settingsDir.exists()) + settingsDir.mkpath(settingsDir.absolutePath()); + const QString collectionFile = settingsDir.absolutePath() + QLatin1String("/helpcollection.qhc"); + + if (!QFileInfo( helpFile ).exists()) { + setError ( UserDefinedError ); + setErrorString( tr("Could not unregister help file %1: File not found.").arg( helpFile ) ); + return false; + } + + QHelpEngineCore help(collectionFile); + const QString nsName = help.namespaceName(helpFile); + return help.unregisterDocumentation(nsName); +} + +bool RegisterDocumentationOperation::testOperation() +{ + return true; +} + +Operation *RegisterDocumentationOperation::clone() const +{ + return new RegisterDocumentationOperation(); +} + diff --git a/src/libs/installer/registerdocumentationoperation.h b/src/libs/installer/registerdocumentationoperation.h new file mode 100644 index 000000000..5fd3a9688 --- /dev/null +++ b/src/libs/installer/registerdocumentationoperation.h @@ -0,0 +1,54 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef REGISTERDOCUMENTATIONOPERATION_H +#define REGISTERDOCUMENTATIONOPERATION_H + +#include "qinstallerglobal.h" + +namespace QInstaller { + +class RegisterDocumentationOperation : public Operation +{ +public: + RegisterDocumentationOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation *clone() const; +}; + +} // namespace QInstaller + +#endif // REGISTERDOCUMENTATIONOPERATION_H diff --git a/src/libs/installer/registerfiletypeoperation.cpp b/src/libs/installer/registerfiletypeoperation.cpp new file mode 100644 index 000000000..5de90733f --- /dev/null +++ b/src/libs/installer/registerfiletypeoperation.cpp @@ -0,0 +1,207 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "registerfiletypeoperation.h" + +#include "qsettingswrapper.h" + +using namespace QInstaller; + +RegisterFileTypeOperation::RegisterFileTypeOperation() +{ + setName(QLatin1String("RegisterFileType")); +} + +void RegisterFileTypeOperation::backup() +{ +} + +bool RegisterFileTypeOperation::performOperation() +{ + // extension + // command + // (description) + // (content type) + // (icon) +#ifdef Q_WS_WIN + const QStringList args = arguments(); + if (args.count() < 2 || args.count() > 5) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in %0").arg(name())); + return false; + } + + const QString extension = args.at(0); + const QString command = args.at(1); + const QString description = args.value(2); // optional + const QString contentType = args.value(3); // optional + const QString icon = args.value(4); // optional + + const QString className = QString::fromLatin1("%1_auto_file").arg(extension); + const QString settingsPrefix = QString::fromLatin1("Software/Classes/"); + + //QSettingsWrapper settings(QLatin1String("HKEY_CLASSES_ROOT"), QSettingsWrapper::NativeFormat); + QSettingsWrapper settings(QLatin1String("HKEY_CURRENT_USER"), QSettingsWrapper::NativeFormat); + // first backup the old values + setValue(QLatin1String("oldClass"), settings.value(QString::fromLatin1("%1.%2/Default").arg(settingsPrefix, + extension))); + setValue(QLatin1String("oldContentType"), settings.value(QString::fromLatin1("%1.%2/Content Type") + .arg(settingsPrefix, extension))); + + // the file extension was not yet registered. Let's do so now + settings.setValue(QString::fromLatin1("%1.%2/Default").arg(settingsPrefix, extension), className); + + // register the content type, if given + if (!contentType.isEmpty()) + settings.setValue(QString::fromLatin1("%1.%2/Content Type").arg(settingsPrefix, extension), contentType); + + setValue(QLatin1String("className"), className); + // register the description, if given + if (!description.isEmpty()) + settings.setValue(QString::fromLatin1("%1%2/Default").arg(settingsPrefix, className), description); + + // register the icon, if given + if (!icon.isEmpty()) { + settings.setValue(QString::fromLatin1("%1%2/DefaultIcon/Default").arg(settingsPrefix, + className), icon); + } + + // register the command to open the file + settings.setValue(QString::fromLatin1("%1%2/shell/Open/Command/Default").arg(settingsPrefix, + className), command); + + return true; +#else + setError(UserDefinedError); + setErrorString(QObject::tr("Registering file types in only supported on Windows.")); + return false; +#endif +} + +bool RegisterFileTypeOperation::undoOperation() +{ + // extension + // command + // (description) + // (content type) + // (icon) +#ifdef Q_WS_WIN + const QStringList args = arguments(); + if (args.count() < 2 || args.count() > 5) { + setErrorString(tr("Register File Type: Invalid arguments")); + return false; + } + const QString extension = args.at(0); + const QString command = args.at(1); + const QString description = args.value(2); // optional + const QString contentType = args.value(3); // optional + const QString icon = args.value(4); // optional + + const QString className = QString::fromLatin1("%1_auto_file").arg(extension); + const QString classes = QString::fromLatin1("Software/Classes/%1").arg(className); + const QString extensions = QString::fromLatin1("Software/Classes/.%1").arg(extension); + + QSettingsWrapper settings(QLatin1String("HKEY_CURRENT_USER"), QSettingsWrapper::NativeFormat); + + const QString restoredClassName = value(QLatin1String("oldClassName")).toString(); + // register the command to open the file + if (settings.value(QString::fromLatin1("%1/shell/Open/Command/Default").arg(classes)).toString() != command) + return false; + if (settings.value(QString::fromLatin1("%1/DefaultIcon/Default").arg(classes)).toString() != icon) + return false; + if (settings.value(QString::fromLatin1("%1/Default").arg(classes)).toString() != description) + return false; + if (settings.value(QString::fromLatin1("%1/Content Type").arg(extensions)).toString() != contentType) + return false; + if (settings.value(QString::fromLatin1("%1/Default").arg(extensions)).toString() != className) + return false; + + const QLatin1String settingsPrefix("Software/Classes/"); + const QVariant oldCommand = value(QLatin1String("oldCommand")); + if (!oldCommand.isNull()) { + settings.setValue(QString::fromLatin1("%1%2/shell/Open/Command/Default").arg(settingsPrefix, + restoredClassName), oldCommand); + } else { + settings.remove(QString::fromLatin1("%1/shell/Open/Command/Default").arg(classes)); + } + + // register the icon, if given + const QVariant oldIcon = value(QLatin1String("oldIcon")); + if (!oldIcon.isNull()) { + settings.setValue(QString::fromLatin1("%1%2/DefaultIcon/Default").arg(settingsPrefix, + restoredClassName), oldIcon); + } else { + settings.remove(QString::fromLatin1("%1%2/DefaultIcon/Default").arg(classes)); + } + + // register the description, if given + const QVariant oldDescription = value(QLatin1String("oldDescription")); + if (!oldDescription.isNull()) { + settings.setValue(QString::fromLatin1("%1%2/Default").arg(settingsPrefix, restoredClassName), + oldDescription); + } else { + settings.remove(QString::fromLatin1("%1%2/Default").arg(classes)); + } + + // content type + settings.remove(classes); + + const QVariant oldContentType = value(QLatin1String("oldContentType")); + if(!oldContentType.isNull()) + settings.setValue(QString::fromLatin1("%1/Content Type").arg(extensions), oldContentType); + else + settings.remove(QString::fromLatin1("%1/Content Type").arg(extensions)); + + // class + + const QVariant oldClass = value(QLatin1String("oldClass")); + if(!oldClass.isNull()) + settings.setValue(QString::fromLatin1("%1/Default").arg(extensions), oldClass); + else + settings.remove(extensions); + + return true; +#else + setErrorString(QObject::tr("Registering file types in only supported on Windows.")); + return false; +#endif +} + +bool RegisterFileTypeOperation::testOperation() +{ + return true; +} + +Operation *RegisterFileTypeOperation::clone() const +{ + return new RegisterFileTypeOperation(); +} diff --git a/src/libs/installer/registerfiletypeoperation.h b/src/libs/installer/registerfiletypeoperation.h new file mode 100644 index 000000000..824024892 --- /dev/null +++ b/src/libs/installer/registerfiletypeoperation.h @@ -0,0 +1,54 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef REGISTERFILETYPEOPERATION_H +#define REGISTERFILETYPEOPERATION_H + +#include "qinstallerglobal.h" + +namespace QInstaller { + +class INSTALLER_EXPORT RegisterFileTypeOperation : public Operation +{ +public: + RegisterFileTypeOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation *clone() const; +}; + +} + +#endif diff --git a/src/libs/installer/registerqtoperation.cpp b/src/libs/installer/registerqtoperation.cpp new file mode 100644 index 000000000..4f721ca4c --- /dev/null +++ b/src/libs/installer/registerqtoperation.cpp @@ -0,0 +1,224 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "registerqtoperation.h" + +#include "component.h" +#include "packagemanagercore.h" +#include "qtcreator_constants.h" +#include "qtcreatorpersistentsettings.h" +#include "registertoolchainoperation.h" +#include "registerqtv2operation.h" + +#include <QtCore/QDir> +#include <QtCore/QFileInfo> +#include <QtCore/QSettings> +#include <QtCore/QString> + +using namespace QInstaller; + + +RegisterQtInCreatorOperation::RegisterQtInCreatorOperation() +{ + setName(QLatin1String("RegisterQtInCreator")); +} + +void RegisterQtInCreatorOperation::backup() +{ +} + +bool RegisterQtInCreatorOperation::performOperation() +{ + const QStringList args = arguments(); + + if (args.count() < 3) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in %0: %1 arguments given, minimum 3 expected.").arg(name()) + .arg(args.count())); + return false; + } + + const QString &rootInstallPath = args.value(0); //for example "C:\\Nokia_SDK\\" + const QString &versionName = args.value(1); + const QString &path = args.value(2); + QString mingwPath = args.value(3); + QString s60SdkPath = args.value(4); + QString gccePath = args.value(5); + QString carbidePath = args.value(6); + QString msvcPath = args.value(7); + QString sbsPath = args.value(8); + +//this is for creator 2.2 + PackageManagerCore *const core = qVariantValue<PackageManagerCore *>(value(QLatin1String("installer"))); + if (!core) { + setError(UserDefinedError); + setErrorString(tr("Needed installer object in \"%1\" operation is empty.").arg(name())); + return false; + } + QString toolChainsXmlFilePath = rootInstallPath + QLatin1String(ToolChainSettingsSuffixPath); + bool isCreator22 = false; + //in case of the fake installer this component doesn't exist + Component *creatorComponent = + core->componentByName(QLatin1String("com.nokia.ndk.tools.qtcreator.application")); + if (creatorComponent) { + const QString creatorVersion = creatorComponent->value(scInstalledVersion); + isCreator22 = PackageManagerCore::versionMatches(creatorVersion, QLatin1String("2.2")); + } + + if (QFileInfo(toolChainsXmlFilePath).exists() || isCreator22) { + QtCreatorPersistentSettings creatorToolChainSettings; + + if (!creatorToolChainSettings.init(toolChainsXmlFilePath)) { + setError(UserDefinedError); + setErrorString(tr("Can't read from tool chains xml file(%1) correctly.") + .arg(toolChainsXmlFilePath)); + return false; + } + if (!mingwPath.isEmpty()) { + RegisterToolChainOperation operation; + operation.setValue(QLatin1String("installer"), QVariant::fromValue(core)); + operation.setArguments(QStringList() + << QLatin1String("GccToolChain") + << QLatin1String("ProjectExplorer.ToolChain.Mingw") + << QLatin1String("Mingw as a GCC for Windows targets") + << QLatin1String("x86-windows-msys-pe-32bit") + << mingwPath + QLatin1String("\\bin\\g++.exe") + << creatorToolChainSettings.abiToDebuggerHash().value(QLatin1String + ("x86-windows-msys-pe-32bit")) + ); + if (!operation.performOperation()) { + setError(operation.error()); + setErrorString(operation.errorString()); + return false; + } + } + if (!gccePath.isEmpty()) { + RegisterToolChainOperation operation; + operation.setValue(QLatin1String("installer"), QVariant::fromValue(core)); + operation.setArguments(QStringList() + << QLatin1String("GccToolChain") + << QLatin1String("Qt4ProjectManager.ToolChain.GCCE") + << QLatin1String("GCCE 4 for Symbian targets") + << QLatin1String("arm-symbian-device-elf-32bit") + << gccePath + QLatin1String("\\bin\\arm-none-symbianelf-g++.exe") + << creatorToolChainSettings.abiToDebuggerHash().value(QLatin1String( + "arm-symbian-device-elf-32bit")) + ); + if (!operation.performOperation()) { + setError(operation.error()); + setErrorString(operation.errorString()); + return false; + } + } + RegisterQtInCreatorV2Operation registerQtInCreatorV2Operation; + registerQtInCreatorV2Operation.setValue(QLatin1String("installer"), QVariant::fromValue(core)); + registerQtInCreatorV2Operation.setArguments(QStringList() << versionName << path << s60SdkPath + << sbsPath); + if (!registerQtInCreatorV2Operation.performOperation()) { + setError(registerQtInCreatorV2Operation.error()); + setErrorString(registerQtInCreatorV2Operation.errorString()); + return false; + } + return true; + } +//END - this is for creator 2.2 + + QSettings settings(rootInstallPath + QLatin1String(QtCreatorSettingsSuffixPath), QSettings::IniFormat); + const QStringList oldNewQtVersions = settings.value(QLatin1String("NewQtVersions")).toString() + .split(QLatin1String(";")); + + QString newVersions; + //remove not existing Qt versions + if (!oldNewQtVersions.isEmpty()) { + foreach (const QString &qtVersion, oldNewQtVersions) { + QStringList splitedQtConfiguration = qtVersion.split(QLatin1String("=")); + if (splitedQtConfiguration.count() > 1 + && splitedQtConfiguration.at(1).contains(QLatin1String("qmake"), Qt::CaseInsensitive)) { + QString qmakePath = splitedQtConfiguration.at(1); + if (QFile::exists(qmakePath)) + newVersions.append(qtVersion + QLatin1String(";")); + } + } + } +#if defined (Q_OS_WIN ) + QString addedVersion = versionName + QLatin1Char('=') + QDir(path) + .absoluteFilePath(QLatin1String("bin/qmake.exe")).replace(QLatin1String("/"), QLatin1String("\\")); +#elif defined(Q_OS_UNIX ) + QString addedVersion = versionName + QLatin1Char('=') + QDir(path) + .absoluteFilePath(QLatin1String("bin/qmake")); +#endif + addedVersion += QLatin1Char('=') + mingwPath.replace(QLatin1String("/"), QLatin1String("\\")); + addedVersion += QLatin1Char('=') + s60SdkPath.replace(QLatin1String("/"), QLatin1String("\\")); + addedVersion += QLatin1Char('=') + gccePath.replace(QLatin1String("/"), QLatin1String("\\")); + addedVersion += QLatin1Char('=') + carbidePath.replace(QLatin1String("/"), QLatin1String("\\")); + addedVersion += QLatin1Char('=') + msvcPath.replace(QLatin1String("/"), QLatin1String("\\")); + addedVersion += QLatin1Char('=') + sbsPath.replace(QLatin1String("/"), QLatin1String("\\")); + newVersions += addedVersion; + settings.setValue(QLatin1String("NewQtVersions"), newVersions); + + return true; +} + +//works with creator 2.1 and 2.2 +bool RegisterQtInCreatorOperation::undoOperation() +{ + const QString &rootInstallPath = arguments().value(0); //for example "C:\\Nokia_SDK\\" + QSettings settings(rootInstallPath + QLatin1String(QtCreatorSettingsSuffixPath), QSettings::IniFormat); + const QStringList oldNewQtVersions = settings.value(QLatin1String("NewQtVersions")).toString() + .split(QLatin1String(";")); + + QString newVersions; + //remove not existing Qt versions, the current to remove Qt version has an already removed qmake + if (!oldNewQtVersions.isEmpty()) { + foreach (const QString &qtVersion, oldNewQtVersions) { + QStringList splitedQtConfiguration = qtVersion.split(QLatin1String("=")); + if (splitedQtConfiguration.count() > 1 + && splitedQtConfiguration.at(1).contains(QLatin1String("qmake"), Qt::CaseInsensitive)) { + QString qmakePath = splitedQtConfiguration.at(1); + if (QFile::exists(qmakePath)) + newVersions.append(qtVersion + QLatin1String(";")); + } + } + } + settings.setValue(QLatin1String("NewQtVersions"), newVersions); + return true; +} + +bool RegisterQtInCreatorOperation::testOperation() +{ + return true; +} + +Operation *RegisterQtInCreatorOperation::clone() const +{ + return new RegisterQtInCreatorOperation(); +} diff --git a/src/libs/installer/registerqtoperation.h b/src/libs/installer/registerqtoperation.h new file mode 100644 index 000000000..62f9b0a94 --- /dev/null +++ b/src/libs/installer/registerqtoperation.h @@ -0,0 +1,54 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef REGISTERQTINCREATOROPERATION_H +#define REGISTERQTINCREATOROPERATION_H + +#include "qinstallerglobal.h" + +namespace QInstaller { + +class RegisterQtInCreatorOperation : public Operation +{ +public: + RegisterQtInCreatorOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation *clone() const; +}; + +} // namespace QInstaller + +#endif // REGISTERQTINCREATOROPERATION_H diff --git a/src/libs/installer/registerqtv23operation.cpp b/src/libs/installer/registerqtv23operation.cpp new file mode 100644 index 000000000..19b0342e7 --- /dev/null +++ b/src/libs/installer/registerqtv23operation.cpp @@ -0,0 +1,231 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "registerqtv23operation.h" + +#include "constants.h" +#include "packagemanagercore.h" +#include "qtcreator_constants.h" +#include "persistentsettings.h" + +#include <QString> +#include <QFileInfo> +#include <QDir> +#include <QSettings> +#include <QDebug> + + +using namespace QInstaller; + +// TODO: move this to a general location it is used on some classes +static QString fromNativeSeparatorsAllOS(const QString &pathName) +{ + QString n = pathName; + for (int i = 0; i < n.size(); ++i) { + if (n.at(i) == QLatin1Char('\\')) + n[i] = QLatin1Char('/'); + } + return n; +} + +static QString absoluteQmakePath(const QString &path) +{ + QString versionQmakePath = QDir(path).absolutePath(); + if (!versionQmakePath.endsWith(QLatin1String("qmake")) + && !versionQmakePath.endsWith(QLatin1String("qmake.exe"))) { +#if defined (Q_OS_WIN) + versionQmakePath.append(QLatin1String("/bin/qmake.exe")); +#elif defined(Q_OS_UNIX) + versionQmakePath.append(QLatin1String("/bin/qmake")); +#endif + } + return fromNativeSeparatorsAllOS(versionQmakePath); +} + +RegisterQtInCreatorV23Operation::RegisterQtInCreatorV23Operation() +{ + setName(QLatin1String("RegisterQtInCreatorV23")); +} + +void RegisterQtInCreatorV23Operation::backup() +{ +} + +// Parameter List: +// Name - String displayed as name in Qt Creator +// qmake path - location of the qmake binary +// Type identifier - Desktop, Simulator, Symbian, ... +// SDK identifier - unique string to identify Qt version inside of the SDK (eg. desk473, simu11, ...) +// System Root Path +// sbs path +bool RegisterQtInCreatorV23Operation::performOperation() +{ + const QStringList args = arguments(); + + if (args.count() < 4) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in %0: %1 arguments given, minimum 4 expected.") + .arg(name()).arg(args.count())); + return false; + } + + PackageManagerCore *const core = qVariantValue<PackageManagerCore*>(value(QLatin1String("installer"))); + if (!core) { + setError(UserDefinedError); + setErrorString(tr("Needed installer object in \"%1\" operation is empty.").arg(name())); + return false; + } + const QString &rootInstallPath = core->value(scTargetDir); + if (rootInstallPath.isEmpty() || !QDir(rootInstallPath).exists()) { + setError(UserDefinedError); + setErrorString(tr("The given TargetDir %1 is not a valid/existing dir.").arg(rootInstallPath)); + return false; + } + + const QString qtVersionsFileName = rootInstallPath + + QLatin1String(QtVersionSettingsSuffixPath); + int argCounter = 0; + const QString &versionName = args.at(argCounter++); + const QString &path = QDir::toNativeSeparators(args.value(argCounter++)); + const QString versionQmakePath = absoluteQmakePath(path); + + const QString &versionTypeIdentifier = args.at(argCounter++); + const QString &versionSDKIdentifier = args.at(argCounter++); + const QString &versionSystemRoot = fromNativeSeparatorsAllOS(args.value(argCounter++)); + const QString &versionSbsPath = fromNativeSeparatorsAllOS(args.value(argCounter++)); + + ProjectExplorer::PersistentSettingsReader reader; + int qtVersionCount = 0; + QVariantMap map; + if (reader.load(qtVersionsFileName)) { + map = reader.restoreValues(); + qtVersionCount = map.value(QLatin1String("QtVersion.Count")).toInt(); + map.remove(QLatin1String("QtVersion.Count")); + map.remove(QLatin1String("Version")); + } + + ProjectExplorer::PersistentSettingsWriter writer; + // Store old qt versions + if (!map.isEmpty()) { + for (int i = 0; i < qtVersionCount; ++i) { + writer.saveValue(QString::fromLatin1("QtVersion.%1").arg(i) + , map[QLatin1String("QtVersion.") + QString::number(i)].toMap()); + } + map.clear(); + } + // Enter new version + map.insert(QLatin1String("Id"), -1); + map.insert(QLatin1String("Name"), versionName); + map.insert(QLatin1String("QMakePath"), versionQmakePath); + map.insert(QLatin1String("QtVersion.Type"), + QLatin1String("Qt4ProjectManager.QtVersion.") + versionTypeIdentifier); + map.insert(QLatin1String("isAutodetected"), true); + map.insert(QLatin1String("autodetectionSource"), + QLatin1String("SDK.") + versionSDKIdentifier); + if (!versionSystemRoot.isEmpty()) + map.insert(QLatin1String("SystemRoot"), versionSystemRoot); + if (!versionSbsPath.isEmpty()) + map.insert(QLatin1String("SBSv2Directory"), versionSbsPath); + + writer.saveValue(QLatin1String("QtVersion.") + QString::number(qtVersionCount), map); + + writer.saveValue(QLatin1String("Version"), 1); + writer.saveValue(QLatin1String("QtVersion.Count"), qtVersionCount + 1); + QDir().mkpath(QFileInfo(qtVersionsFileName).absolutePath()); + writer.save(qtVersionsFileName, QLatin1String("QtCreatorQtVersions")); + + return true; +} + +bool RegisterQtInCreatorV23Operation::undoOperation() +{ + const QStringList args = arguments(); + + if (args.count() < 4) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in %0: %1 arguments given, minimum 4 expected.") + .arg(name()).arg(args.count())); + return false; + } + + PackageManagerCore *const core = qVariantValue<PackageManagerCore*>(value(QLatin1String("installer"))); + if (!core) { + setError(UserDefinedError); + setErrorString(tr("Needed installer object in \"%1\" operation is empty.").arg(name())); + return false; + } + const QString &rootInstallPath = core->value(scTargetDir); + + const QString qtVersionsFileName = rootInstallPath + + QLatin1String(QtVersionSettingsSuffixPath); + + ProjectExplorer::PersistentSettingsReader reader; + // If no file, then it has been removed already + if (!reader.load(qtVersionsFileName)) + return true; + + const QVariantMap map = reader.restoreValues(); + + ProjectExplorer::PersistentSettingsWriter writer; + const int qtVersionCount = map.value(QLatin1String("QtVersion.Count")).toInt(); + + int currentVersionIndex = 0; + for (int i = 0; i < qtVersionCount; ++i) { + QVariantMap versionMap = map[QLatin1String("QtVersion.") + QString::number(i)].toMap(); + + const QString path = QDir::toNativeSeparators(args.value(1)); + const QString versionQmakePath = absoluteQmakePath(path); + + //use absoluteQmakePath function to normalize the path string, for example // + const QString existingQtQMakePath = absoluteQmakePath( + versionMap[QLatin1String("QMakePath")].toString()); + if (existingQtQMakePath == versionQmakePath) + continue; + writer.saveValue(QString::fromLatin1("QtVersion.%1").arg(currentVersionIndex++), versionMap); + } + + writer.saveValue(QLatin1String("QtVersion.Count"), currentVersionIndex); + writer.saveValue(QLatin1String("Version"), map[QLatin1String("Version")].toInt()); + + writer.save(qtVersionsFileName, QLatin1String("QtCreatorQtVersions")); + return true; +} + +bool RegisterQtInCreatorV23Operation::testOperation() +{ + return true; +} + +Operation *RegisterQtInCreatorV23Operation::clone() const +{ + return new RegisterQtInCreatorV23Operation(); +} diff --git a/src/libs/installer/registerqtv23operation.h b/src/libs/installer/registerqtv23operation.h new file mode 100644 index 000000000..5e4f383b8 --- /dev/null +++ b/src/libs/installer/registerqtv23operation.h @@ -0,0 +1,54 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef REGISTERQTINCREATORV23OPERATION_H +#define REGISTERQTINCREATORV23OPERATION_H + +#include "qinstallerglobal.h" + +namespace QInstaller { + +class RegisterQtInCreatorV23Operation : public Operation +{ +public: + RegisterQtInCreatorV23Operation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation *clone() const; +}; + +} // namespace QInstaller + +#endif // REGISTERQTINCREATORV2OPERATION_H diff --git a/src/libs/installer/registerqtv2operation.cpp b/src/libs/installer/registerqtv2operation.cpp new file mode 100644 index 000000000..1209e52b9 --- /dev/null +++ b/src/libs/installer/registerqtv2operation.cpp @@ -0,0 +1,201 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "registerqtv2operation.h" + +#include "constants.h" +#include "packagemanagercore.h" +#include "qtcreator_constants.h" + +#include <QString> +#include <QFileInfo> +#include <QDir> +#include <QSettings> +#include <QDebug> + +using namespace QInstaller; + +RegisterQtInCreatorV2Operation::RegisterQtInCreatorV2Operation() +{ + setName(QLatin1String("RegisterQtInCreatorV2")); +} + +void RegisterQtInCreatorV2Operation::backup() +{ +} + +//version name; qmake path; system root path; sbs path +bool RegisterQtInCreatorV2Operation::performOperation() +{ + const QStringList args = arguments(); + + if (args.count() < 2) { + setError(InvalidArguments); + setErrorString( tr("Invalid arguments in %0: %1 arguments given, minimum 2 expected.") + .arg(name()).arg( args.count() ) ); + return false; + } + + PackageManagerCore *const core = qVariantValue<PackageManagerCore*>(value(QLatin1String("installer"))); + if (!core) { + setError(UserDefinedError); + setErrorString(tr("Needed installer object in \"%1\" operation is empty.").arg(name())); + return false; + } + const QString &rootInstallPath = core->value(scTargetDir); + if (rootInstallPath.isEmpty() || !QDir(rootInstallPath).exists()) { + setError(UserDefinedError); + setErrorString(tr("The given TargetDir %1 is not a valid/existing dir.").arg(rootInstallPath)); + return false; + } + + int argCounter = 0; + const QString &versionName = args.value(argCounter++); + const QString &path = QDir::toNativeSeparators(args.value(argCounter++)); + QString qmakePath = QDir(path).absolutePath(); + if ( !qmakePath.endsWith(QLatin1String("qmake")) + && !qmakePath.endsWith(QLatin1String("qmake.exe"))) + { +#if defined ( Q_OS_WIN ) + qmakePath.append(QLatin1String("/bin/qmake.exe")); +#elif defined( Q_OS_UNIX ) + qmakePath.append(QLatin1String("/bin/qmake")); +#endif + } + qmakePath = QDir::toNativeSeparators(qmakePath); + + const QString &systemRoot = QDir::toNativeSeparators(args.value(argCounter++)); //Symbian SDK root for example + const QString &sbsPath = QDir::toNativeSeparators(args.value(argCounter++)); + + QSettings settings(rootInstallPath + QLatin1String(QtCreatorSettingsSuffixPath), + QSettings::IniFormat); + + QString newVersions; + QStringList oldNewQtVersions = settings.value(QLatin1String("NewQtVersions") + ).toString().split(QLatin1String(";")); + + //remove not existing Qt versions and the current new one(because its added after this) + if (!oldNewQtVersions.isEmpty()) { + foreach (const QString &qtVersion, oldNewQtVersions) { + QStringList splitedQtConfiguration = qtVersion.split(QLatin1String("=")); + if (splitedQtConfiguration.value(1).contains(QLatin1String("qmake"), + Qt::CaseInsensitive)) { + QString foundVersionName = splitedQtConfiguration.at(0); + QString foundQmakePath = splitedQtConfiguration.at(1); + if (QFileInfo(qmakePath) != QFileInfo(foundQmakePath) && versionName != foundVersionName + && QFile::exists(foundQmakePath)) { + newVersions.append(qtVersion + QLatin1String(";")); + } + } + } + } + + QString addedVersion = versionName; + + addedVersion += QLatin1Char('=') + qmakePath; + addedVersion += QLatin1Char('=') + systemRoot; + addedVersion += QLatin1Char('=') + sbsPath; + newVersions += addedVersion; + settings.setValue(QLatin1String("NewQtVersions"), newVersions); + + return true; +} + +bool RegisterQtInCreatorV2Operation::undoOperation() +{ + const QStringList args = arguments(); + + if (args.count() < 2) { + setError(InvalidArguments); + setErrorString( tr("Invalid arguments in %0: %1 arguments given, minimum 2 expected.") + .arg(name()).arg( args.count() ) ); + return false; + } + + PackageManagerCore *const core = qVariantValue<PackageManagerCore*>(value(QLatin1String("installer"))); + if (!core) { + setError(UserDefinedError); + setErrorString(tr("Needed installer object in \"%1\" operation is empty.").arg(name())); + return false; + } + const QString &rootInstallPath = core->value(scTargetDir); + + int argCounter = 0; + const QString &versionName = args.value(argCounter++); + const QString &path = QDir::toNativeSeparators(args.value(argCounter++)); + QString qmakePath = QDir(path).absolutePath(); + if (!qmakePath.endsWith(QLatin1String("qmake")) + || !qmakePath.endsWith(QLatin1String("qmake.exe"))) + { +#if defined ( Q_OS_WIN ) + qmakePath.append(QLatin1String("/bin/qmake.exe")); +#elif defined( Q_OS_UNIX ) + qmakePath.append(QLatin1String("/bin/qmake")); +#endif + } + qmakePath = QDir::toNativeSeparators(qmakePath); + + QSettings settings(rootInstallPath + QLatin1String(QtCreatorSettingsSuffixPath), + QSettings::IniFormat); + + QString newVersions; + QStringList oldNewQtVersions = settings.value(QLatin1String("NewQtVersions") + ).toString().split(QLatin1String(";")); + + //remove the removed Qt version from "NewQtVersions" setting + if (!oldNewQtVersions.isEmpty()) { + foreach (const QString &qtVersion, oldNewQtVersions) { + QStringList splitedQtConfiguration = qtVersion.split(QLatin1String("=")); + if (splitedQtConfiguration.value(1).contains(QLatin1String("qmake"), + Qt::CaseInsensitive)) { + QString foundVersionName = splitedQtConfiguration.at(0); + QString foundQmakePath = splitedQtConfiguration.at(1); + if (QFileInfo(qmakePath) != QFileInfo(foundQmakePath) &&versionName != foundVersionName + && QFile::exists(foundQmakePath)) { + newVersions.append(qtVersion + QLatin1String(";")); + } + } + } + } + settings.setValue(QLatin1String("NewQtVersions"), newVersions); + return true; +} + +bool RegisterQtInCreatorV2Operation::testOperation() +{ + return true; +} + +Operation *RegisterQtInCreatorV2Operation::clone() const +{ + return new RegisterQtInCreatorV2Operation(); +} diff --git a/src/libs/installer/registerqtv2operation.h b/src/libs/installer/registerqtv2operation.h new file mode 100644 index 000000000..9dc726f6c --- /dev/null +++ b/src/libs/installer/registerqtv2operation.h @@ -0,0 +1,54 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef REGISTERQTINCREATORV2OPERATION_H +#define REGISTERQTINCREATORV2OPERATION_H + +#include "qinstallerglobal.h" + +namespace QInstaller { + +class RegisterQtInCreatorV2Operation : public Operation +{ +public: + RegisterQtInCreatorV2Operation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation *clone() const; +}; + +} // namespace QInstaller + +#endif // REGISTERQTINCREATORV2OPERATION_H diff --git a/src/libs/installer/registertoolchainoperation.cpp b/src/libs/installer/registertoolchainoperation.cpp new file mode 100644 index 000000000..66190e289 --- /dev/null +++ b/src/libs/installer/registertoolchainoperation.cpp @@ -0,0 +1,178 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "registertoolchainoperation.h" + +#include "constants.h" +#include "persistentsettings.h" +#include "packagemanagercore.h" +#include "qtcreator_constants.h" +#include "qtcreatorpersistentsettings.h" + +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QFileInfo> +#include <QtCore/QSettings> +#include <QtCore/QString> + +using namespace QInstaller; + +using namespace ProjectExplorer; + +RegisterToolChainOperation::RegisterToolChainOperation() +{ + setName(QLatin1String("RegisterToolChain")); +} + +void RegisterToolChainOperation::backup() +{ +} + +bool RegisterToolChainOperation::performOperation() +{ + const QStringList args = arguments(); + + if (args.count() < 4) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in %0: %1 arguments given, minimum 4 expected.") + .arg(name()).arg(args.count())); + return false; + } + + QString toolChainsXmlFilePath; + + PackageManagerCore *const core = qVariantValue<PackageManagerCore*>(value(QLatin1String("installer"))); + if (!core) { + setError(UserDefinedError); + setErrorString(tr("Needed installer object in \"%1\" operation is empty.").arg(name())); + return false; + } + const QString &rootInstallPath = core->value(scTargetDir); + toolChainsXmlFilePath = rootInstallPath + QLatin1String(ToolChainSettingsSuffixPath); + + QtCreatorToolChain toolChain; + + int argCounter = 0; + toolChain.key = args.at(argCounter++); //Qt SDK:gccPath + toolChain.type = args.at(argCounter++); //where this toolchain is defined in QtCreator + toolChain.displayName = args.at(argCounter++); //nice special Toolchain (Qt SDK) + toolChain.abiString = args.at(argCounter++); //x86-windows-msys-pe-32bit + toolChain.compilerPath = QDir::toNativeSeparators(args.at(argCounter++)); //gccPath + if (args.count() > argCounter) + toolChain.debuggerPath = QDir::toNativeSeparators(args.at(argCounter++)); + if (args.count() > argCounter) + toolChain.armVersion = args.at(argCounter++); + if (args.count() > argCounter) + toolChain.force32Bit = args.at(argCounter++); + + QtCreatorPersistentSettings creatorToolChainSettings; + + if (!creatorToolChainSettings.init(toolChainsXmlFilePath)) { + setError(UserDefinedError); + setErrorString(tr("Can't read from tool chains xml file(%1) correctly.") + .arg(toolChainsXmlFilePath)); + return false; + } + + if (!creatorToolChainSettings.addToolChain(toolChain)) { + setError(InvalidArguments); + setErrorString(tr("Some arguments are not right in %1 operation.") + .arg(name()).arg(args.count())); + return false; + } + return creatorToolChainSettings.save(); +} + +bool RegisterToolChainOperation::undoOperation() +{ + const QStringList args = arguments(); + + if (args.count() < 4) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in %0: %1 arguments given, minimum 4 expected.") + .arg(name()).arg(args.count())); + return false; + } + + QString toolChainsXmlFilePath; + + PackageManagerCore *const core = qVariantValue<PackageManagerCore*>(value(QLatin1String("installer"))); + if (!core) { + setError(UserDefinedError); + setErrorString(tr("Needed installer object in \"%1\" operation is empty.").arg(name())); + return false; + } + const QString &rootInstallPath = core->value(scTargetDir); + toolChainsXmlFilePath = rootInstallPath + QLatin1String(ToolChainSettingsSuffixPath); + + QtCreatorToolChain toolChain; + + int argCounter = 0; + toolChain.key = args.at(argCounter++); //Qt SDK:gccPath + toolChain.type = args.at(argCounter++); //where this toolchain is defined in QtCreator + toolChain.displayName = args.at(argCounter++); //nice special Toolchain (Qt SDK) + toolChain.abiString = args.at(argCounter++); //x86-windows-msys-pe-32bit + toolChain.compilerPath = QDir::toNativeSeparators(args.at(argCounter++)); //gccPath + if (args.count() > argCounter) + toolChain.debuggerPath = QDir::toNativeSeparators(args.at(argCounter++)); + if (args.count() > argCounter) + toolChain.armVersion = args.at(argCounter++); + if (args.count() > argCounter) + toolChain.force32Bit = args.at(argCounter++); + + QtCreatorPersistentSettings creatorToolChainSettings; + + if (!creatorToolChainSettings.init(toolChainsXmlFilePath)) { + setError(UserDefinedError); + setErrorString(tr("Can't read from tool chains xml file(%1) correctly.") + .arg(toolChainsXmlFilePath)); + return false; + } + + if (!creatorToolChainSettings.removeToolChain(toolChain)) { + setError(InvalidArguments); + setErrorString(tr("Some arguments are not right in %1 operation.") + .arg(name()).arg(args.count())); + return false; + } + return creatorToolChainSettings.save(); +} + +bool RegisterToolChainOperation::testOperation() +{ + return true; +} + +Operation *RegisterToolChainOperation::clone() const +{ + return new RegisterToolChainOperation(); +} diff --git a/src/libs/installer/registertoolchainoperation.h b/src/libs/installer/registertoolchainoperation.h new file mode 100644 index 000000000..90fd20671 --- /dev/null +++ b/src/libs/installer/registertoolchainoperation.h @@ -0,0 +1,72 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef REGISTERTOOLCHAINOPERATION_H +#define REGISTERTOOLCHAINOPERATION_H + +#include "qinstallerglobal.h" + +namespace QInstaller { + +/*! + Arguments: + * SDK Path - to find the QtCreator installation + * ToolChainKey - is the internal QtCreator settings key usually: GccToolChain + * toolchain type - where this toolchain is defined in QtCreator + * ProjectExplorer.ToolChain.Gcc ProjectExplorer.ToolChain.Mingw + * ProjectExplorer.ToolChain.LinuxIcc ProjectExplorer.ToolChain.Msvc + * Qt4ProjectManager.ToolChain.GCCE Qt4ProjectManager.ToolChain.Maemo + * display name - the name how it will be displayed in QtCreator + * application binary interface - this is an internal creator typ as a String CPU-OS-OS_FLAVOR-BINARY_FORMAT-WORD_WIDTH + * CPU: arm x86 mips ppc itanium + * OS: linux macos symbian unix windows + * OS_FLAVOR: generic maemo meego generic device emulator generic msvc2005 msvc2008 msvc2010 msys ce + * BINARY_FORMAT: elf pe mach_o qml_rt + * WORD_WIDTH: 8 16 32 64 + * compiler path - the binary which is used as the compiler + * debugger path - the binary which is used as the debugger +*/ +class RegisterToolChainOperation : public Operation +{ +public: + RegisterToolChainOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation *clone() const; +}; + +} // namespace QInstaller + +#endif // REGISTERTOOLCHAINOPERATION_H diff --git a/src/libs/installer/replaceoperation.cpp b/src/libs/installer/replaceoperation.cpp new file mode 100644 index 000000000..d24383ab9 --- /dev/null +++ b/src/libs/installer/replaceoperation.cpp @@ -0,0 +1,106 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "replaceoperation.h" + +#include <QtCore/QDir> +#include <QtCore/QFile> +#include <QtCore/QTextStream> + +using namespace QInstaller; + +ReplaceOperation::ReplaceOperation() +{ + setName(QLatin1String("Replace")); +} + +void ReplaceOperation::backup() +{ +} + +bool ReplaceOperation::performOperation() +{ + const QStringList args = arguments(); + + // Arguments: + // 1. filename + // 2. Source-String + // 3. Replace-String + if (args.count() != 3) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in %0: %1 arguments given, 3 expected.").arg(name()).arg(args + .count())); + return false; + } + const QString fileName = args.at(0); + const QString before = args.at(1); + const QString after = args.at(2); + + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) { + setError(UserDefinedError); + setErrorString(QObject::tr("Failed to open %1 for reading").arg(fileName)); + return false; + } + + QTextStream stream(&file); + QString replacedFileContent = stream.readAll(); + file.close(); + + if (!file.open(QIODevice::WriteOnly)) { + setError(UserDefinedError); + setErrorString(QObject::tr("Failed to open %1 for writing").arg(fileName)); + return false; + } + + stream.setDevice(&file); + stream << replacedFileContent.replace(before, after); + file.close(); + + return true; +} + +bool ReplaceOperation::undoOperation() +{ + // Need to remove settings again + return true; +} + +bool ReplaceOperation::testOperation() +{ + return true; +} + +Operation *ReplaceOperation::clone() const +{ + return new ReplaceOperation(); +} diff --git a/src/libs/installer/replaceoperation.h b/src/libs/installer/replaceoperation.h new file mode 100644 index 000000000..e651e7115 --- /dev/null +++ b/src/libs/installer/replaceoperation.h @@ -0,0 +1,54 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef REPLACEOPERATION_H +#define REPLACEOPERATION_H + +#include "qinstallerglobal.h" + +namespace QInstaller { + +class INSTALLER_EXPORT ReplaceOperation : public Operation +{ +public: + ReplaceOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation *clone() const; +}; + +} // namespace QInstaller + +#endif // REPLACEOPERATION_H diff --git a/src/libs/installer/repository.cpp b/src/libs/installer/repository.cpp new file mode 100644 index 000000000..cc80a20d9 --- /dev/null +++ b/src/libs/installer/repository.cpp @@ -0,0 +1,213 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "repository.h" + +namespace QInstaller { + +/* + Constructs an invalid Repository object. +*/ +Repository::Repository() + : m_default(false) + , m_enabled(false) +{ + registerMetaType(); +} + +/*! + Constructs a new repository by using all fields of the given repository \a other. +*/ +Repository::Repository(const Repository &other) + : m_url(other.m_url) + , m_default(other.m_default) + , m_enabled(other.m_enabled) + , m_username(other.m_username) + , m_password(other.m_password) +{ + registerMetaType(); +} + +/*! + Constructs a new repository by setting it's address to \a url and it's default state. +*/ +Repository::Repository(const QUrl &url, bool isDefault) + : m_url(url) + , m_default(isDefault) + , m_enabled(true) +{ + registerMetaType(); +} + +/*! + Returns true if the repository URL is valid; otherwise returns false. + + Note: The URL is simply run through a conformance test. It is not checked that the repository + actually exists. +*/ +bool Repository::isValid() const +{ + return m_url.isValid(); +} + +/*! + Returns true if the repository was set using the package manager configuration file; otherwise returns + false. +*/ +bool Repository::isDefault() const +{ + return m_default; +} + +/*! + Returns the URL of the repository. By default an invalid \sa QUrl is returned. +*/ +QUrl Repository::url() const +{ + return m_url; +} + +/*! + Sets the repository URL to the one specified at \a url. +*/ +void Repository::setUrl(const QUrl &url) +{ + m_url = url; +} + +/*! + Returns whether the repository is enabled and used during information retrieval. +*/ +bool Repository::isEnabled() const +{ + return m_enabled; +} + +/*! + Sets this repository to \n enabled state and thus to use this repository for information retrieval or not. +*/ +void Repository::setEnabled(bool enabled) +{ + m_enabled = enabled; +} + +/*! + Returns the user name used for authentication. +*/ +QString Repository::username() const +{ + return m_username; +} + +/*! + Sets the user name for authentication to be \a username. +*/ +void Repository::setUsername(const QString &username) +{ + m_username = username; +} + +/*! + Returns the password used for authentication. +*/ +QString Repository::password() const +{ + return m_password; +} + +/*! + Sets the password for authentication to be \a password. +*/ +void Repository::setPassword(const QString &password) +{ + m_password = password; +} + +/*! + Compares the values of this repository to \a other and returns true if they are equal (same server, + default state, enabled state as well as username and password). \sa operator!=() +*/ +bool Repository::operator==(const Repository &other) const +{ + return m_url == other.m_url && m_default == other.m_default && m_enabled == other.m_enabled + && m_username == other.m_username && m_password == other.m_password; +} + +/*! + Returns true if the \a other repository is not equal to this repository; otherwise returns false. Two + repositories are considered equal if they contain the same elements. \sa operator==() +*/ +bool Repository::operator!=(const Repository &other) const +{ + return !(*this == other); +} + +/*! + Assigns the values of repository \a other to this repository. +*/ +const Repository &Repository::operator=(const Repository &other) +{ + if (this == &other) + return *this; + + m_url = other.m_url; + m_default = other.m_default; + m_enabled = other.m_enabled; + m_username = other.m_username; + m_password = other.m_password; + + return *this; +} + +void Repository::registerMetaType() +{ + qRegisterMetaType<Repository>("Repository"); + qRegisterMetaTypeStreamOperators<Repository>("Repository"); +} + +QDataStream &operator>>(QDataStream &istream, Repository &repository) +{ + QByteArray url, username, password; + istream >> url >> repository.m_default >> repository.m_enabled >> username >> password; + repository.setUrl(QUrl::fromEncoded(QByteArray::fromBase64(url))); + repository.setUsername(QString::fromUtf8(QByteArray::fromBase64(username))); + repository.setPassword(QString::fromUtf8(QByteArray::fromBase64(password))); + return istream; +} + +QDataStream &operator<<(QDataStream &ostream, const Repository &repository) +{ + return ostream << repository.m_url.toEncoded().toBase64() << repository.m_default << repository.m_enabled + << repository.m_username.toUtf8().toBase64() << repository.m_password.toUtf8().toBase64(); +} + +} diff --git a/src/libs/installer/repository.h b/src/libs/installer/repository.h new file mode 100644 index 000000000..432a851c4 --- /dev/null +++ b/src/libs/installer/repository.h @@ -0,0 +1,97 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef REPOSITORY_H +#define REPOSITORY_H + +#include "installer_global.h" + +#include <QtCore/QMetaType> +#include <QtCore/QUrl> + +namespace QInstaller { + +class INSTALLER_EXPORT Repository +{ +public: + explicit Repository(); + Repository(const Repository &other); + explicit Repository(const QUrl &url, bool isDefault); + + bool isValid() const; + bool isDefault() const; + + QUrl url() const; + void setUrl(const QUrl &url); + + bool isEnabled() const; + void setEnabled(bool enabled); + + QString username() const; + void setUsername(const QString &username); + + QString password() const; + void setPassword(const QString &password); + + bool operator==(const Repository &other) const; + bool operator!=(const Repository &other) const; + + uint qHash(const Repository &repository); + const Repository &operator=(const Repository &other); + + friend QDataStream &operator>>(QDataStream &istream, Repository &repository); + friend QDataStream &operator<<(QDataStream &ostream, const Repository &repository); + +private: + void registerMetaType(); + +private: + QUrl m_url; + bool m_default; + bool m_enabled; + QString m_username; + QString m_password; +}; + +inline uint qHash(const Repository &repository) +{ + return qHash(repository.url().toString()); +} + +QDataStream &operator>>(QDataStream &istream, Repository &repository); +QDataStream &operator<<(QDataStream &ostream, const Repository &repository); + +} // namespace QInstaller + +Q_DECLARE_METATYPE(QInstaller::Repository) + +#endif // REPOSITORY_H diff --git a/src/libs/installer/resources/files-to-patch-linux b/src/libs/installer/resources/files-to-patch-linux new file mode 100644 index 000000000..9c7b39f4a --- /dev/null +++ b/src/libs/installer/resources/files-to-patch-linux @@ -0,0 +1,72 @@ +bin/qmake +bin/lrelease +lib/libQtCore.so +%% +lib/libQtCore.la +lib/libQt3Support.la +lib/libQtCLucene.la +lib/libQtDBus.la +lib/libQtDeclarative.la +lib/libQtGui.la +lib/libQtHelp.la +lib/libQtMultimedia.la +lib/libQtNetwork.la +lib/libQtOpenGL.la +lib/libphonon.la +lib/libQtScript.la +lib/libQtScriptTools.la +lib/libQtSql.la +lib/libQtSvg.la +lib/libQtTest.la +lib/libQtWebKit.la +lib/libQtXml.la +lib/libQtXmlPatterns.la +demos/shared/libdemo_shared.prl +lib/libQt3Support.prl +lib/libQtAssistantClient.prl +lib/libQtCLucene.prl +lib/libQtCore.prl +lib/libQtDBus.prl +lib/libQtDesignerComponents.prl +lib/libQtDesigner.prl +lib/libQtDeclarative.prl +lib/libQtGui.prl +lib/libQtHelp.prl +lib/libQtMultimedia.prl +lib/libQtNetwork.prl +lib/libQtOpenGL.prl +lib/libQtScript.prl +lib/libQtScriptTools.prl +lib/libQtSql.prl +lib/libQtSvg.prl +lib/libQtTest.prl +lib/libQtUiTools.prl +lib/libQtWebKit.prl +lib/libQtXmlPatterns.prl +lib/libQtXml.prl +lib/libphonon.prl +lib/libqtmain.prl +lib/pkgconfig/phonon.pc +lib/pkgconfig/Qt3Support.pc +lib/pkgconfig/QtAssistantClient.pc +lib/pkgconfig/QtCLucene.pc +lib/pkgconfig/QtCore.pc +lib/pkgconfig/QtDBus.pc +lib/pkgconfig/QtDeclarative.pc +lib/pkgconfig/QtDesignerComponents.pc +lib/pkgconfig/QtDesigner.pc +lib/pkgconfig/QtGui.pc +lib/pkgconfig/QtHelp.pc +lib/pkgconfig/QtMultimedia.pc +lib/pkgconfig/QtNetwork.pc +lib/pkgconfig/QtOpenGL.pc +lib/pkgconfig/QtScript.pc +lib/pkgconfig/QtScriptTools.pc +lib/pkgconfig/QtSql.pc +lib/pkgconfig/QtSvg.pc +lib/pkgconfig/QtTest.pc +lib/pkgconfig/QtUiTools.pc +lib/pkgconfig/QtWebKit.pc +lib/pkgconfig/QtXmlPatterns.pc +lib/pkgconfig/QtXml.pc +mkspecs/qconfig.pri diff --git a/src/libs/installer/resources/files-to-patch-linux-emb-arm b/src/libs/installer/resources/files-to-patch-linux-emb-arm new file mode 100644 index 000000000..0ae1605de --- /dev/null +++ b/src/libs/installer/resources/files-to-patch-linux-emb-arm @@ -0,0 +1,70 @@ +bin/qmake +bin/lrelease +%% +lib/libQtCore.la +lib/libQt3Support.la +lib/libQtCLucene.la +lib/libQtDBus.la +lib/libQtDeclarative.la +lib/libQtGui.la +lib/libQtHelp.la +lib/libQtMultimedia.la +lib/libQtNetwork.la +lib/libQtOpenGL.la +lib/libphonon.la +lib/libQtScript.la +lib/libQtScriptTools.la +lib/libQtSql.la +lib/libQtSvg.la +lib/libQtTest.la +lib/libQtWebKit.la +lib/libQtXml.la +lib/libQtXmlPatterns.la +demos/shared/libdemo_shared.prl +lib/libQt3Support.prl +lib/libQtAssistantClient.prl +lib/libQtCLucene.prl +lib/libQtCore.prl +lib/libQtDBus.prl +lib/libQtDesignerComponents.prl +lib/libQtDesigner.prl +lib/libQtDeclarative.prl +lib/libQtGui.prl +lib/libQtHelp.prl +lib/libQtMultimedia.prl +lib/libQtNetwork.prl +lib/libQtOpenGL.prl +lib/libQtScript.prl +lib/libQtScriptTools.prl +lib/libQtSql.prl +lib/libQtSvg.prl +lib/libQtTest.prl +lib/libQtUiTools.prl +lib/libQtWebKit.prl +lib/libQtXmlPatterns.prl +lib/libQtXml.prl +lib/libphonon.prl +lib/libqtmain.prl +lib/pkgconfig/phonon.pc +lib/pkgconfig/Qt3Support.pc +lib/pkgconfig/QtAssistantClient.pc +lib/pkgconfig/QtCLucene.pc +lib/pkgconfig/QtCore.pc +lib/pkgconfig/QtDBus.pc +lib/pkgconfig/QtDeclarative.pc +lib/pkgconfig/QtDesignerComponents.pc +lib/pkgconfig/QtDesigner.pc +lib/pkgconfig/QtGui.pc +lib/pkgconfig/QtHelp.pc +lib/pkgconfig/QtMultimedia.pc +lib/pkgconfig/QtNetwork.pc +lib/pkgconfig/QtOpenGL.pc +lib/pkgconfig/QtScript.pc +lib/pkgconfig/QtScriptTools.pc +lib/pkgconfig/QtSql.pc +lib/pkgconfig/QtSvg.pc +lib/pkgconfig/QtTest.pc +lib/pkgconfig/QtUiTools.pc +lib/pkgconfig/QtWebKit.pc +lib/pkgconfig/QtXmlPatterns.pc +lib/pkgconfig/QtXml.pc diff --git a/src/libs/installer/resources/files-to-patch-macx b/src/libs/installer/resources/files-to-patch-macx new file mode 100644 index 000000000..7f8deda14 --- /dev/null +++ b/src/libs/installer/resources/files-to-patch-macx @@ -0,0 +1,61 @@ +bin/qmake +bin/lrelease +bin/lconvert +bin/lupdate +bin/macdeployqt +bin/qcollectiongenerator +bin/qdoc3 +bin/qhelpgenerator +bin/qt3to4 +bin/xmlpatterns +bin/xmlpatternsvalidator +lib/QtCore.framework/QtCore +bin/Designer.app/Contents/MacOS/Designer +bin/Linguist.app/Contents/MacOS/Linguist +bin/qhelpconverter.app/Contents/MacOS/qhelpconverter +bin/QMLViewer.app/Contents/MacOS/QMLViewer +bin/qttracereplay.app/Contents/MacOS/qttracereplay +%% +lib/QtCore.la +lib/libQtCLucene.la +lib/QtDeclarative.la +lib/QtGui.la +lib/QtHelp.la +lib/QtMultimedia.la +lib/QtNetwork.la +lib/QtOpenGL.la +lib/phonon.la +lib/QtScript.la +lib/QtScriptTools.la +lib/QtSql.la +lib/QtSvg.la +lib/QtTest.la +lib/QtWebKit.la +lib/QtXml.la +lib/QtXmlPatterns.la +lib/libQtCLucene.prl +lib/libQtUiTools.prl +lib/pkgconfig/phonon.pc +lib/pkgconfig/Qt3Support.pc +lib/pkgconfig/QtAssistantClient.pc +lib/pkgconfig/QtCLucene.pc +lib/pkgconfig/QtCore.pc +lib/pkgconfig/QtDBus.pc +lib/pkgconfig/QtDeclarative.pc +lib/pkgconfig/QtDesignerComponents.pc +lib/pkgconfig/QtDesigner.pc +lib/pkgconfig/QtGui.pc +lib/pkgconfig/QtHelp.pc +lib/pkgconfig/QtMultimedia.pc +lib/pkgconfig/QtNetwork.pc +lib/pkgconfig/QtOpenGL.pc +lib/pkgconfig/QtScript.pc +lib/pkgconfig/QtScriptTools.pc +lib/pkgconfig/QtSql.pc +lib/pkgconfig/QtSvg.pc +lib/pkgconfig/QtTest.pc +lib/pkgconfig/QtUiTools.pc +lib/pkgconfig/QtWebKit.pc +lib/pkgconfig/QtXmlPatterns.pc +lib/pkgconfig/QtXml.pc +mkspecs/qconfig.pri diff --git a/src/libs/installer/resources/files-to-patch-windows b/src/libs/installer/resources/files-to-patch-windows new file mode 100644 index 000000000..845a299ce --- /dev/null +++ b/src/libs/installer/resources/files-to-patch-windows @@ -0,0 +1,60 @@ +bin/qmake.exe +bin/lrelease.exe +bin/QtCore4.dll +bin/QtCored4.dll +lib/QtCore4.dll +lib/QtCored4.dll +%% +mkspecs/default/qmake.conf +demos/shared/libdemo_shared.prl +lib/Qt3Support.prl +lib/QtAssistantClient.prl +lib/QtCLucene.prl +lib/QtCore.prl +lib/QtDesignerComponents.prl +lib/QtDesigner.prl +lib/QtGui.prl +lib/QtHelp.prl +lib/QtMultimedia.prl +lib/QtNetwork.prl +lib/QtOpenGL.prl +lib/QtScript.prl +lib/QtScriptTools.prl +lib/QtSql.prl +lib/QtSvg.prl +lib/QtTest.prl +lib/QtUiTools.prl +lib/QtWebKit.prl +lib/QtXmlPatterns.prl +lib/QtXml.prl +lib/Qt3Supportd.prl +lib/QtAssistantClientd.prl +lib/QtCLucened.prl +lib/QtCored.prl +lib/QtDesignerComponentsd.prl +lib/QtDesignerd.prl +lib/QtGuid.prl +lib/QtHelpd.prl +lib/QtMultimediad.prl +lib/QtNetworkd.prl +lib/QtOpenGLd.prl +lib/QtScriptd.prl +lib/QtScriptToolsd.prl +lib/QtSqld.prl +lib/QtSvgd.prl +lib/QtTestd.prl +lib/QtUiToolsd.prl +lib/QtWebKitd.prl +lib/QtXmlPatternsd.prl +lib/QtXmld.prl +lib/phonon.prl +lib/phonond.prl +lib/QtDeclarative.prl +lib/QtDeclaratived.prl +lib/qtmain.prl +lib/qtmaind.prl +lib/QAxContainer.prl +lib/QAxContainerd.prl +lib/QAxServer.prl +lib/QAxServerd.prl +.qmake.cache diff --git a/src/libs/installer/resources/patch_file_lists.qrc b/src/libs/installer/resources/patch_file_lists.qrc new file mode 100644 index 000000000..a51500369 --- /dev/null +++ b/src/libs/installer/resources/patch_file_lists.qrc @@ -0,0 +1,7 @@ +<RCC> + <qresource prefix="/"> + <file>files-to-patch-linux</file> + <file>files-to-patch-windows</file> + <file>files-to-patch-macx</file> + </qresource> +</RCC> diff --git a/src/libs/installer/selfrestartoperation.cpp b/src/libs/installer/selfrestartoperation.cpp new file mode 100644 index 000000000..a9df116b8 --- /dev/null +++ b/src/libs/installer/selfrestartoperation.cpp @@ -0,0 +1,88 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "selfrestartoperation.h" +#include "packagemanagercore.h" + +#include <kdselfrestarter.h> + +using namespace QInstaller; + +SelfRestartOperation::SelfRestartOperation() +{ + setName(QLatin1String("SelfRestart")); +} + +void SelfRestartOperation::backup() +{ + setValue(QLatin1String("PreviousSelfRestart"), KDSelfRestarter::restartOnQuit()); +} + +bool SelfRestartOperation::performOperation() +{ + PackageManagerCore *const core = qVariantValue<PackageManagerCore *>(value(QLatin1String("installer"))); + if (!core) { + setError(UserDefinedError); + setErrorString(tr("Needed installer object in \"%1\" operation is empty.").arg(name())); + return false; + } + + if (!core->isUpdater() && !core->isPackageManager()) { + setError(UserDefinedError); + setErrorString(tr("Self Restart: Only valid within updater or packagemanager mode.")); + return false; + } + + if (!arguments().isEmpty()) { + setError(InvalidArguments); + setErrorString(tr("Self Restart: Invalid arguments")); + return false; + } + KDSelfRestarter::setRestartOnQuit(true); + return KDSelfRestarter::restartOnQuit(); +} + +bool SelfRestartOperation::undoOperation() +{ + KDSelfRestarter::setRestartOnQuit(value(QLatin1String("PreviousSelfRestart")).toBool()); + return true; +} + +bool SelfRestartOperation::testOperation() +{ + return true; +} + +Operation *SelfRestartOperation::clone() const +{ + return new SelfRestartOperation(); +} diff --git a/src/libs/installer/selfrestartoperation.h b/src/libs/installer/selfrestartoperation.h new file mode 100644 index 000000000..24b9b5733 --- /dev/null +++ b/src/libs/installer/selfrestartoperation.h @@ -0,0 +1,54 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef SELFRESTARTOPERATION_H +#define SELFRESTARTOPERATION_H + +#include "qinstallerglobal.h" + +namespace QInstaller { + +class INSTALLER_EXPORT SelfRestartOperation : public Operation +{ +public: + SelfRestartOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation *clone() const; +}; + +} + +#endif diff --git a/src/libs/installer/setdemospathonqtoperation.cpp b/src/libs/installer/setdemospathonqtoperation.cpp new file mode 100644 index 000000000..8f3137d55 --- /dev/null +++ b/src/libs/installer/setdemospathonqtoperation.cpp @@ -0,0 +1,126 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "setdemospathonqtoperation.h" + +#include "qtpatch.h" + +#include <QtCore/QDir> +#include <QtCore/QSettings> +#include <QtCore/QDebug> + +using namespace QInstaller; + +SetDemosPathOnQtOperation::SetDemosPathOnQtOperation() +{ + setName(QLatin1String("SetDemosPathOnQt")); +} + +void SetDemosPathOnQtOperation::backup() +{ +} + +bool SetDemosPathOnQtOperation::performOperation() +{ + const QStringList args = arguments(); + + if (args.count() != 2) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in %0: %1 arguments given, exactly 2 expected.").arg(name()) + .arg(arguments().count())); + return false; + } + + const QString qtDir = args.at(0); + QByteArray newValue = QDir::toNativeSeparators(args.at(1)).toUtf8(); + + QString qmakePath = qtDir + QLatin1String("/bin/qmake"); +#ifdef Q_OS_WIN + qmakePath = qmakePath + QLatin1String(".exe"); +#endif + + QByteArray qmakeOutput; + QHash<QString, QByteArray> qmakeValueHash = QtPatch::qmakeValues(qmakePath, &qmakeOutput); + + if (qmakeValueHash.isEmpty()) { + setError(UserDefinedError); + setErrorString(tr("The output of \n%1 -query\nis not parseable. Please file a bugreport with this " + "dialog https://bugreports.qt-project.org.\noutput: %2").arg(QDir::toNativeSeparators(qmakePath), + QString::fromUtf8(qmakeOutput))); + return false; + } + + QByteArray oldValue = qmakeValueHash.value(QLatin1String("QT_INSTALL_DEMOS")); + bool oldQtPathFromQMakeIsEmpty = oldValue.isEmpty(); + if (oldQtPathFromQMakeIsEmpty) { + qDebug() << "qpatch: warning: It was not able to get the old values from" << qmakePath; + } + + if (255 < newValue.size()) { + setError(UserDefinedError); + setErrorString(tr("Qt patch error: new Qt demo path (%1)\nneeds to be less than 255 characters.") + .arg(QString::fromLocal8Bit(newValue)) ); + return false; + } + + QString qtConfPath = qtDir + QLatin1String("/bin/qt.conf"); + if (QFile::exists(qtConfPath)) { + QSettings settings(qtConfPath, QSettings::IniFormat); + settings.setValue(QLatin1String("Paths/Demos"), QString::fromUtf8(newValue)); + } + + oldValue = QByteArray("qt_demopath=%1").replace("%1", oldValue); + newValue = QByteArray("qt_demopath=%1").replace("%1", newValue); + + bool isPatched = QtPatch::patchBinaryFile(qmakePath, oldValue, newValue); + if (!isPatched) { + qDebug() << "qpatch: warning: could not patch the demo path in" << qmakePath; + } + + return true; +} + +bool SetDemosPathOnQtOperation::undoOperation() +{ + return true; +} + +bool SetDemosPathOnQtOperation::testOperation() +{ + return true; +} + +Operation *SetDemosPathOnQtOperation::clone() const +{ + return new SetDemosPathOnQtOperation(); +} + diff --git a/src/libs/installer/setdemospathonqtoperation.h b/src/libs/installer/setdemospathonqtoperation.h new file mode 100644 index 000000000..c8400b9ff --- /dev/null +++ b/src/libs/installer/setdemospathonqtoperation.h @@ -0,0 +1,54 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef SETDEMOSPATHONQTOPERATION_H +#define SETDEMOSPATHONQTOPERATION_H + +#include "qinstallerglobal.h" + +namespace QInstaller { + +class SetDemosPathOnQtOperation : public Operation +{ +public: + SetDemosPathOnQtOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation *clone() const; +}; + +} // namespace + +#endif // SETDEMOSPATHONQTOPERATION_H diff --git a/src/libs/installer/setexamplespathonqtoperation.cpp b/src/libs/installer/setexamplespathonqtoperation.cpp new file mode 100644 index 000000000..7b1e8e55b --- /dev/null +++ b/src/libs/installer/setexamplespathonqtoperation.cpp @@ -0,0 +1,127 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "setexamplespathonqtoperation.h" + +#include "qtpatch.h" + +#include <QtCore/QDir> +#include <QtCore/QSettings> +#include <QtCore/QDebug> + +using namespace QInstaller; + +SetExamplesPathOnQtOperation::SetExamplesPathOnQtOperation() +{ + setName(QLatin1String("SetExamplesPathOnQt")); +} + +void SetExamplesPathOnQtOperation::backup() +{ +} + +bool SetExamplesPathOnQtOperation::performOperation() +{ + const QStringList args = arguments(); + + if (args.count() != 2) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in %0: %1 arguments given, exactly 2 expected.").arg(name()) + .arg(arguments().count())); + return false; + } + + const QString qtDir = args.at(0); + QByteArray newValue = QDir::toNativeSeparators(args.at(1)).toUtf8(); + + QString qmakePath = qtDir + QLatin1String("/bin/qmake"); +#ifdef Q_OS_WIN + qmakePath = qmakePath + QLatin1String(".exe"); +#endif + + QByteArray qmakeOutput; + QHash<QString, QByteArray> qmakeValueHash = QtPatch::qmakeValues(qmakePath, &qmakeOutput); + + if (qmakeValueHash.isEmpty()) { + setError(UserDefinedError); + setErrorString(tr("The output of \n%1 -query\nis not parseable. Please file a bugreport with this " + "dialog https://bugreports.qt-project.org.\noutput: %2").arg(QDir::toNativeSeparators(qmakePath), + QString::fromUtf8(qmakeOutput))); + return false; + } + + QByteArray oldValue = qmakeValueHash.value(QLatin1String("QT_INSTALL_EXAMPLES")); + bool oldQtPathFromQMakeIsEmpty = oldValue.isEmpty(); + if (oldQtPathFromQMakeIsEmpty) { + qDebug() << "qpatch: warning: It was not able to get the old values from" << qmakePath; + } + + if (255 < newValue.size()) { + setError(UserDefinedError); + setErrorString(tr("Qt patch error: new Qt example path (%1)\nneeds to be less than 255 characters.") + .arg(QString::fromLocal8Bit(newValue))); + return false; + } + + QString qtConfPath = qtDir + QLatin1String("/bin/qt.conf"); + + if (QFile::exists(qtConfPath)) { + QSettings settings(qtConfPath, QSettings::IniFormat); + settings.setValue( QLatin1String("Paths/Examples"), QString::fromUtf8(newValue)); + } + + oldValue = QByteArray("qt_xmplpath=%1").replace("%1", oldValue); + newValue = QByteArray("qt_xmplpath=%1").replace("%1", newValue); + + bool isPatched = QtPatch::patchBinaryFile(qmakePath, oldValue, newValue); + if (!isPatched) { + qDebug() << "qpatch: warning: could not patch the example path in" << qmakePath; + } + + return true; +} + +bool SetExamplesPathOnQtOperation::undoOperation() +{ + return true; +} + +bool SetExamplesPathOnQtOperation::testOperation() +{ + return true; +} + +Operation *SetExamplesPathOnQtOperation::clone() const +{ + return new SetExamplesPathOnQtOperation(); +} + diff --git a/src/libs/installer/setexamplespathonqtoperation.h b/src/libs/installer/setexamplespathonqtoperation.h new file mode 100644 index 000000000..96e15b206 --- /dev/null +++ b/src/libs/installer/setexamplespathonqtoperation.h @@ -0,0 +1,54 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef SETEXAMPLESPATHONQTOPERATION_H +#define SETEXAMPLESPATHONQTOPERATION_H + +#include "qinstallerglobal.h" + +namespace QInstaller { + +class SetExamplesPathOnQtOperation : public Operation +{ +public: + SetExamplesPathOnQtOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation *clone() const; +}; + +} // namespace + +#endif // SETEXAMPLESPATHONQTOPERATION_H diff --git a/src/libs/installer/setimportspathonqtcoreoperation.cpp b/src/libs/installer/setimportspathonqtcoreoperation.cpp new file mode 100644 index 000000000..2bfacebb3 --- /dev/null +++ b/src/libs/installer/setimportspathonqtcoreoperation.cpp @@ -0,0 +1,151 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "setimportspathonqtcoreoperation.h" + +#include "qtpatch.h" + +#include <QtCore/QByteArrayMatcher> +#include <QtCore/QDir> +#include <QtCore/QDebug> + +using namespace QInstaller; + +namespace { + QByteArray getOldValue(const QString &binaryPath) + { + QFileInfo fileInfo(binaryPath); + + if (!fileInfo.exists()) { + qDebug() << QString::fromLatin1("qpatch: warning: file %1 not found").arg(binaryPath); + return QByteArray(); + } + + + QFile file(binaryPath); + int readOpenCount = 0; + while (!file.open(QFile::ReadOnly) && readOpenCount < 20000) { + ++readOpenCount; + qApp->processEvents(); + } + Q_ASSERT(file.isOpen()); + if (!file.isOpen()) { + qDebug() << QString::fromLatin1("qpatch: warning: file %1 can not be opened as ReadOnly.").arg( + binaryPath); + qDebug() << file.errorString(); + return QByteArray(); + } + + const QByteArray source = file.readAll(); + file.close(); + + int offset = 0; + QByteArray searchValue("qt_impspath="); + QByteArrayMatcher byteArrayMatcher(searchValue); + offset = byteArrayMatcher.indexIn(source, offset); + Q_ASSERT(offset > 0); + if (offset == -1) + return QByteArray(); + + int stringEndPosition = offset; + while(source.at(stringEndPosition++) != '\0') {} + //after search string till the first \0 it should be our looking for QByteArray + return source.mid(offset + searchValue.size(), stringEndPosition - offset); + } +} + +SetImportsPathOnQtCoreOperation::SetImportsPathOnQtCoreOperation() +{ + setName(QLatin1String("SetImportsPathOnQtCore")); +} + +void SetImportsPathOnQtCoreOperation::backup() +{ +} + +bool SetImportsPathOnQtCoreOperation::performOperation() +{ + const QStringList args = arguments(); + + if (args.count() != 2) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in %0: %1 arguments given, exactly 2 expected.").arg(name()) + .arg(arguments().count())); + return false; + } + + const QString qtCoreLibraryDir = args.at(0); + const QByteArray newValue = QDir::toNativeSeparators(args.at(1)).toUtf8(); + + if (255 < newValue.size()) { + qDebug() << "qpatch: error: newQtDir needs to be less than 255 characters."; + return false; + } + QStringList libraryFiles; +#ifdef Q_OS_WIN + libraryFiles << QString::fromLatin1("%1/QtCore4.dll").arg(qtCoreLibraryDir); + libraryFiles << QString::fromLatin1("%1/QtCore4d.dll").arg(qtCoreLibraryDir); +#else + libraryFiles << qtCoreLibraryDir + QLatin1String("/libQtCore.so"); +#endif + foreach (const QString coreLibrary, libraryFiles) { + if (QFile::exists(coreLibrary)) { + QByteArray oldValue(getOldValue(coreLibrary)); + Q_ASSERT(!oldValue.isEmpty()); + oldValue = QByteArray("qt_impspath=%1").replace("%1", oldValue); + QByteArray adjutedNewValue = QByteArray("qt_impspath=%1").replace("%1", newValue); + + bool isPatched = QtPatch::patchBinaryFile(coreLibrary, oldValue, adjutedNewValue); + if (!isPatched) { + qDebug() << "qpatch: warning: could not patch the imports path in" << coreLibrary; + } + } + } + + return true; +} + +bool SetImportsPathOnQtCoreOperation::undoOperation() +{ + return true; +} + +bool SetImportsPathOnQtCoreOperation::testOperation() +{ + return true; +} + +Operation *SetImportsPathOnQtCoreOperation::clone() const +{ + return new SetImportsPathOnQtCoreOperation(); +} + diff --git a/src/libs/installer/setimportspathonqtcoreoperation.h b/src/libs/installer/setimportspathonqtcoreoperation.h new file mode 100644 index 000000000..93a1290d7 --- /dev/null +++ b/src/libs/installer/setimportspathonqtcoreoperation.h @@ -0,0 +1,54 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef SETIMPORTSPATHONQTCOREOPERATION_H +#define SETIMPORTSPATHONQTCOREOPERATION_H + +#include "qinstallerglobal.h" + +namespace QInstaller { + +class SetImportsPathOnQtCoreOperation : public Operation +{ +public: + SetImportsPathOnQtCoreOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation *clone() const; +}; + +} // namespace + +#endif // SETIMPORTSPATHONQTCOREOPERATION_H diff --git a/src/libs/installer/setpathonqtcoreoperation.cpp b/src/libs/installer/setpathonqtcoreoperation.cpp new file mode 100644 index 000000000..80001b773 --- /dev/null +++ b/src/libs/installer/setpathonqtcoreoperation.cpp @@ -0,0 +1,175 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "setpathonqtcoreoperation.h" + +#include "qtpatch.h" + +#include <QtCore/QByteArrayMatcher> +#include <QtCore/QDir> +#include <QtCore/QDebug> + +using namespace QInstaller; + +namespace { + QByteArray getOldValue(const QString &binaryPath, const QByteArray &typeValue) + { + QFileInfo fileInfo(binaryPath); + + if (!fileInfo.exists()) { + qDebug() << "qpatch: warning: file '" << binaryPath << "' not found"; + return QByteArray(); + } + + QFile file(binaryPath); + int readOpenCount = 0; + while (!file.open(QFile::ReadOnly) && readOpenCount < 20000) { + ++readOpenCount; + qApp->processEvents(); + } + Q_ASSERT(file.isOpen()); + if (!file.isOpen()) { + qDebug() << "qpatch: warning: file '" << binaryPath << "' can not open as ReadOnly."; + qDebug() << file.errorString(); + return QByteArray(); + } + + const QByteArray source = file.readAll(); + file.close(); + + int offset = 0; + QByteArray searchValue = typeValue; + searchValue.append("="); + QByteArrayMatcher byteArrayMatcher(searchValue); + offset = byteArrayMatcher.indexIn(source, offset); + Q_ASSERT(offset > 0); + if (offset == -1) + return QByteArray(); + + int stringEndPosition = offset; + + //go to the position where other data starts + while (source.at(stringEndPosition++) != '\0') {} + + //after search string till the first \0 it should be our looking for QByteArray + return source.mid(offset + searchValue.size(), stringEndPosition - offset); + } +} + +SetPathOnQtCoreOperation::SetPathOnQtCoreOperation() +{ + setName(QLatin1String("SetPathOnQtCore")); +} + +void SetPathOnQtCoreOperation::backup() +{ +} + +bool SetPathOnQtCoreOperation::performOperation() +{ + const QStringList args = arguments(); + + if (args.count() != 3) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in %0: %1 arguments given, exactly 3 expected.").arg(name()) + .arg(arguments().count())); + return false; + } + + const QString qtCoreLibraryDir = args.at(0); + const QByteArray typeValue(args.at(1).toUtf8()); + const QByteArray newValue = QDir::toNativeSeparators(args.at(2)).toUtf8(); + + QStringList possibleTypes; + possibleTypes << QLatin1String("qt_prfxpath") + << QLatin1String("qt_docspath") + << QLatin1String("qt_hdrspath") + << QLatin1String("qt_libspath") + << QLatin1String("qt_binspath") + << QLatin1String("qt_plugpath") + << QLatin1String("qt_impspath") + << QLatin1String("qt_datapath") + << QLatin1String("qt_trnspath") + << QLatin1String("qt_xmplpath") + << QLatin1String("qt_demopath"); + + if (!possibleTypes.contains(QString::fromUtf8(typeValue))) { + setError(InvalidArguments); + setErrorString(tr("The second type/value needs to be one of: %1").arg(possibleTypes.join( + QLatin1String(", ")))); + return false; + } + + if (255 < newValue.size()) { + qDebug() << "qpatch: error: newQtDir needs to be less than 255 characters."; + return false; + } + QStringList libraryFiles; +#ifdef Q_OS_WIN + libraryFiles << QString::fromLatin1("%1/QtCore4.dll").arg(qtCoreLibraryDir); + libraryFiles << QString::fromLatin1("%1/QtCore4d.dll").arg(qtCoreLibraryDir); +#else + libraryFiles << qtCoreLibraryDir + QLatin1String("/libQtCore.so"); +#endif + foreach (const QString coreLibrary, libraryFiles) { + if (QFile::exists(coreLibrary)) { + QByteArray oldValue(getOldValue(coreLibrary, typeValue)); + Q_ASSERT(!oldValue.isEmpty()); + oldValue = QByteArray("%0=%1").replace("%0", typeValue).replace("%1", oldValue); + QByteArray adjutedNewValue = + QByteArray("%0=%1").replace("%0", typeValue).replace("%1", newValue); + + bool isPatched = QtPatch::patchBinaryFile(coreLibrary, oldValue, adjutedNewValue); + if (!isPatched) { + qDebug() << "qpatch: warning: could not patch the plugin path in" << coreLibrary; + } + } + } + + return true; +} + +bool SetPathOnQtCoreOperation::undoOperation() +{ + return true; +} + +bool SetPathOnQtCoreOperation::testOperation() +{ + return true; +} + +Operation *SetPathOnQtCoreOperation::clone() const +{ + return new SetPathOnQtCoreOperation(); +} + diff --git a/src/libs/installer/setpathonqtcoreoperation.h b/src/libs/installer/setpathonqtcoreoperation.h new file mode 100644 index 000000000..912cbf5a5 --- /dev/null +++ b/src/libs/installer/setpathonqtcoreoperation.h @@ -0,0 +1,54 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef SETPATHONQTCOREOPERATION_H +#define SETPATHONQTCOREOPERATION_H + +#include "qinstallerglobal.h" + +namespace QInstaller { + +class SetPathOnQtCoreOperation : public Operation +{ +public: + SetPathOnQtCoreOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation *clone() const; +}; + +} // namespace + +#endif // SETPATHONQTCOREOPERATION_H diff --git a/src/libs/installer/setpluginpathonqtcoreoperation.cpp b/src/libs/installer/setpluginpathonqtcoreoperation.cpp new file mode 100644 index 000000000..20573e9b1 --- /dev/null +++ b/src/libs/installer/setpluginpathonqtcoreoperation.cpp @@ -0,0 +1,149 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "setpluginpathonqtcoreoperation.h" + +#include "qtpatch.h" + +#include <QtCore/QByteArrayMatcher> +#include <QtCore/QDir> +#include <QtCore/QDebug> + +using namespace QInstaller; + +namespace { + QByteArray getOldValue(const QString &binaryPath) + { + QFileInfo fileInfo(binaryPath); + + if (!fileInfo.exists()) { + qDebug() << QString::fromLatin1("qpatch: warning: file '%1' not found").arg(binaryPath); + return QByteArray(); + } + + QFile file(binaryPath); + int readOpenCount = 0; + while (!file.open(QFile::ReadOnly) && readOpenCount < 20000) { + ++readOpenCount; + qApp->processEvents(); + } + Q_ASSERT(file.isOpen()); + if (!file.isOpen()) { + qDebug() << QString::fromLatin1("qpatch: warning: file '%1' can not open as ReadOnly.").arg( + binaryPath); + qDebug() << file.errorString(); + return QByteArray(); + } + + const QByteArray source = file.readAll(); + file.close(); + + int offset = 0; + QByteArray searchValue("qt_plugpath="); + QByteArrayMatcher byteArrayMatcher(searchValue); + offset = byteArrayMatcher.indexIn(source, offset); + Q_ASSERT(offset > 0); + if (offset == -1) + return QByteArray(); + + int stringEndPosition = offset; + while(source.at(stringEndPosition++) != '\0') {} + //after search string till the first \0 it should be our looking for QByteArray + return source.mid(offset + searchValue.size(), stringEndPosition - offset); + } +} + +SetPluginPathOnQtCoreOperation::SetPluginPathOnQtCoreOperation() +{ + setName(QLatin1String("SetPluginPathOnQtCore")); +} + +void SetPluginPathOnQtCoreOperation::backup() +{ +} + +bool SetPluginPathOnQtCoreOperation::performOperation() +{ + const QStringList args = arguments(); + + if (args.count() != 2) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in %0: %1 arguments given, exactly 2 expected.").arg(name()) + .arg(arguments().count())); + return false; + } + + const QString qtCoreLibraryDir = args.at(0); + const QByteArray newValue = QDir::toNativeSeparators(args.at(1)).toUtf8(); + + if (255 < newValue.size()) { + qDebug() << "qpatch: error: newQtDir needs to be less than 255 characters."; + return false; + } + QStringList libraryFiles; +#ifdef Q_OS_WIN + libraryFiles << QString::fromLatin1("%1/QtCore4.dll").arg(qtCoreLibraryDir); + libraryFiles << QString::fromLatin1("%1/QtCore4d.dll").arg(qtCoreLibraryDir); +#else + libraryFiles << qtCoreLibraryDir + QLatin1String("/libQtCore.so"); +#endif + foreach (const QString &coreLibrary, libraryFiles) { + if (QFile::exists(coreLibrary)) { + QByteArray oldValue(getOldValue(coreLibrary)); + Q_ASSERT(!oldValue.isEmpty()); + oldValue = QByteArray("qt_plugpath=%1").replace("%1", oldValue); + QByteArray adjutedNewValue = QByteArray("qt_plugpath=%1").replace("%1", newValue); + + bool isPatched = QtPatch::patchBinaryFile(coreLibrary, oldValue, adjutedNewValue); + if (!isPatched) + qDebug() << "qpatch: warning: could not patch the plugin path in" << coreLibrary; + } + } + + return true; +} + +bool SetPluginPathOnQtCoreOperation::undoOperation() +{ + return true; +} + +bool SetPluginPathOnQtCoreOperation::testOperation() +{ + return true; +} + +Operation *SetPluginPathOnQtCoreOperation::clone() const +{ + return new SetPluginPathOnQtCoreOperation(); +} + diff --git a/src/libs/installer/setpluginpathonqtcoreoperation.h b/src/libs/installer/setpluginpathonqtcoreoperation.h new file mode 100644 index 000000000..78121e01d --- /dev/null +++ b/src/libs/installer/setpluginpathonqtcoreoperation.h @@ -0,0 +1,54 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef SETPLUGINPATHONQTCOREOPERATION_H +#define SETPLUGINPATHONQTCOREOPERATION_H + +#include "qinstallerglobal.h" + +namespace QInstaller { + +class SetPluginPathOnQtCoreOperation : public Operation +{ +public: + SetPluginPathOnQtCoreOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation *clone() const; +}; + +} // namespace + +#endif // SETPLUGINPATHONQTCOREOPERATION_H diff --git a/src/libs/installer/setqtcreatorvalueoperation.cpp b/src/libs/installer/setqtcreatorvalueoperation.cpp new file mode 100644 index 000000000..a63c46e58 --- /dev/null +++ b/src/libs/installer/setqtcreatorvalueoperation.cpp @@ -0,0 +1,137 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "setqtcreatorvalueoperation.h" + +#include "qtcreator_constants.h" +#include "updatecreatorsettingsfrom21to22operation.h" +#include "packagemanagercore.h" + +#include <QtCore/QSettings> + +using namespace QInstaller; + +static QString groupName(const QString &groupName) +{ + return groupName == QLatin1String("General") ? QString() : groupName; +} + +SetQtCreatorValueOperation::SetQtCreatorValueOperation() +{ + setName(QLatin1String("SetQtCreatorValue")); +} + +void SetQtCreatorValueOperation::backup() +{ +} + +bool SetQtCreatorValueOperation::performOperation() +{ + const QStringList args = arguments(); + + if (args.count() != 4) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in %0: %1 arguments given, exactly 4 expected (rootInstallPath, " + "group, key, value).").arg(name()).arg( arguments().count())); + return false; + } + + const QString &rootInstallPath = args.at(0); //for example "C:\\Nokia_SDK\\" + + const QString &group = groupName(args.at(1)); + const QString &key = args.at(2); + const QString &settingsValue = args.at(3); +{ + QSettings settings(rootInstallPath + QLatin1String(QtCreatorSettingsSuffixPath), QSettings::IniFormat); + if (!group.isEmpty()) + settings.beginGroup(group); + + if (settingsValue.contains(QLatin1String(","))) // comma separated list of strings + settings.setValue(key, settingsValue.split(QRegExp(QLatin1String("\\s*,\\s*")), QString::SkipEmptyParts)); + else + settings.setValue(key, settingsValue); + + if (!group.isEmpty()) + settings.endGroup(); + + settings.sync(); //be save ;) +} //destruct QSettings + + if (group == QLatin1String("GdbBinaries21")) { + PackageManagerCore *const core = qVariantValue<PackageManagerCore*>(value(QLatin1String("installer"))); + if (!core) { + setError(UserDefinedError); + setErrorString(tr("Needed installer object in %1 operation is empty.").arg(name())); + return false; + } + UpdateCreatorSettingsFrom21To22Operation updateCreatorSettingsOperation; + updateCreatorSettingsOperation.setValue(QLatin1String("installer"), QVariant::fromValue(core)); + if (!updateCreatorSettingsOperation.performOperation()) { + setError(updateCreatorSettingsOperation.error()); + setErrorString(updateCreatorSettingsOperation.errorString()); + return false; + } + } + + return true; +} + +bool SetQtCreatorValueOperation::undoOperation() +{ + const QStringList args = arguments(); + + const QString &rootInstallPath = args.at(0); //for example "C:\\Nokia_SDK\\" + + const QString &group = groupName(args.at(1)); + const QString &key = args.at(2); + + QSettings settings(rootInstallPath + QLatin1String(QtCreatorSettingsSuffixPath), QSettings::IniFormat); + if (!group.isEmpty()) + settings.beginGroup(group); + + settings.remove(key); + + if (!group.isEmpty()) + settings.endGroup(); + + return true; +} + +bool SetQtCreatorValueOperation::testOperation() +{ + return true; +} + +Operation *SetQtCreatorValueOperation::clone() const +{ + return new SetQtCreatorValueOperation(); +} diff --git a/src/libs/installer/setqtcreatorvalueoperation.h b/src/libs/installer/setqtcreatorvalueoperation.h new file mode 100644 index 000000000..88e104ee0 --- /dev/null +++ b/src/libs/installer/setqtcreatorvalueoperation.h @@ -0,0 +1,54 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef SETQTCREATORVALUEOPERATION_H +#define SETQTCREATORVALUEOPERATION_H + +#include "qinstallerglobal.h" + +namespace QInstaller { + +class SetQtCreatorValueOperation : public Operation +{ +public: + SetQtCreatorValueOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation *clone() const; +}; + +} // namespace + +#endif // SETQTCREATORVALUEOPERATION_H diff --git a/src/libs/installer/settings.cpp b/src/libs/installer/settings.cpp new file mode 100644 index 000000000..ace61761e --- /dev/null +++ b/src/libs/installer/settings.cpp @@ -0,0 +1,523 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ +#include "settings.h" + +#include "errors.h" +#include "qinstallerglobal.h" +#include "repository.h" + +#include <QtCore/QFileInfo> +#include <QtCore/QStringList> + +#include <QtXml/QXmlStreamReader> + +using namespace QInstaller; + +static const QLatin1String scIcon("Icon"); +static const QLatin1String scLogo("Logo"); +static const QLatin1String scPages("Pages"); +static const QLatin1String scPrefix("Prefix"); +static const QLatin1String scLogoSmall("LogoSmall"); +static const QLatin1String scWatermark("Watermark"); +static const QLatin1String scProductUrl("ProductUrl"); +static const QLatin1String scBackground("Background"); +static const QLatin1String scAdminTargetDir("AdminTargetDir"); +static const QLatin1String scUninstallerName("UninstallerName"); +static const QLatin1String scUserRepositories("UserRepositories"); +static const QLatin1String scTmpRepositories("TemporaryRepositories"); +static const QLatin1String scUninstallerIniFile("UninstallerIniFile"); +static const QLatin1String scRemoteRepositories("RemoteRepositories"); +static const QLatin1String scSigningCertificate("SigningCertificate"); + +static const QLatin1String scFtpProxy("FtpProxy"); +static const QLatin1String scHttpProxy("HttpProxy"); +static const QLatin1String scProxyType("ProxyType"); + +template <typename T> +static QSet<T> variantListToSet(const QVariantList &list) +{ + QSet<T> set; + foreach (const QVariant &variant, list) + set.insert(variant.value<T>()); + return set; +} + +static QSet<Repository> readRepositories(QXmlStreamReader &reader, bool isDefault) +{ + QSet<Repository> set; + while (reader.readNextStartElement()) { + if (reader.name() == QLatin1String("Repository")) { + Repository repo(QString(), isDefault); + while (reader.readNextStartElement()) { + if (reader.name() == QLatin1String("Url")) + repo.setUrl(reader.readElementText()); + else if (reader.name() == QLatin1String("Username")) + repo.setUsername(reader.readElementText()); + else if (reader.name() == QLatin1String("Password")) + repo.setPassword(reader.readElementText()); + else if (reader.name() == QLatin1String("Enabled")) + repo.setEnabled(bool(reader.readElementText().toInt())); + else + reader.skipCurrentElement(); + } + set.insert(repo); + } else { + reader.skipCurrentElement(); + } + } + return set; +} + +static QVariantHash readTitles(QXmlStreamReader &reader) +{ + QVariantHash hash; + while (reader.readNextStartElement()) + hash.insert(reader.name().toString(), reader.readElementText(QXmlStreamReader::SkipChildElements)); + return hash; +} + +static QHash<QString, QVariantHash> readPages(QXmlStreamReader &reader) +{ + QHash<QString, QVariantHash> hash; + while (reader.readNextStartElement()) { + if (reader.name() == QLatin1String("Page")) { + QVariantHash pageElements; + QString pageName = reader.attributes().value(QLatin1String("name")).toString(); + while (reader.readNextStartElement()) { + const QString name = reader.name().toString(); + if (name == QLatin1String("Title") || name == QLatin1String("SubTitle")) { + pageElements.insert(name, readTitles(reader)); + } else { + pageElements.insert(name, reader.readElementText(QXmlStreamReader::SkipChildElements)); + } + } + hash.insert(pageName, pageElements); + } + } + return hash; +} + + +// -- Settings::Private + +class Settings::Private : public QSharedData +{ +public: + Private() + : m_replacementRepos(false) + {} + + QVariantHash m_data; + bool m_replacementRepos; + + QString makeAbsolutePath(const QString &path) const + { + if (QFileInfo(path).isAbsolute()) + return path; + return m_data.value(scPrefix).toString() + QLatin1String("/") + path; + } +}; + + +// -- Settings + +Settings::Settings() + : d(new Private) +{ +} + +Settings::~Settings() +{ +} + +Settings::Settings(const Settings &other) + : d(other.d) +{ +} + +Settings& Settings::operator=(const Settings &other) +{ + Settings copy(other); + std::swap(d, copy.d); + return *this; +} + +/* static */ +Settings Settings::fromFileAndPrefix(const QString &path, const QString &prefix) +{ + QFile file(path); + QFile overrideConfig(QLatin1String(":/overrideconfig.xml")); + + if (overrideConfig.exists()) + file.setFileName(overrideConfig.fileName()); + + if (!file.open(QIODevice::ReadOnly)) + throw Error(tr("Could not open settings file %1 for reading: %2").arg(path, file.errorString())); + + QXmlStreamReader reader(&file); + if (reader.readNextStartElement()) { + if (reader.name() != QLatin1String("Installer")) + throw Error(tr("%1 is not valid: Installer root node expected.").arg(path)); + } + + QStringList blackList; + blackList << scRemoteRepositories << scSigningCertificate << scPages; + + Settings s; + s.d->m_data.insert(scPrefix, prefix); + while (reader.readNextStartElement()) { + const QString name = reader.name().toString(); + if (blackList.contains(name)) { + if (name == scSigningCertificate) + s.d->m_data.insertMulti(name, s.d->makeAbsolutePath(reader.readElementText())); + + if (name == scRemoteRepositories) + s.addDefaultRepositories(readRepositories(reader, true)); + + if (name == scPages) { + QHash<QString, QVariantHash> pages = readPages(reader); + const QStringList &keys = pages.keys(); + foreach (const QString &key, keys) + s.d->m_data.insert(key, pages.value(key)); + } + } else { + if (s.d->m_data.contains(name)) + throw Error(tr("Multiple %1 elements found, but only one allowed.").arg(name)); + s.d->m_data.insert(name, reader.readElementText(QXmlStreamReader::SkipChildElements)); + } + } + + if (reader.error() != QXmlStreamReader::NoError) { + throw Error(QString::fromLatin1("Xml parse error in %1: %2 Line: %3, Column: %4").arg(path) + .arg(reader.errorString()).arg(reader.lineNumber()).arg(reader.columnNumber())); + } + + // Add some possible missing values + if (!s.d->m_data.contains(scRemoveTargetDir)) + s.d->m_data.insert(scRemoveTargetDir, scTrue); + if (!s.d->m_data.contains(scUninstallerName)) + s.d->m_data.insert(scUninstallerName, QLatin1String("uninstall")); + if (!s.d->m_data.contains(scTargetConfigurationFile)) + s.d->m_data.insert(scTargetConfigurationFile, QLatin1String("components.xml")); + if (!s.d->m_data.contains(scUninstallerIniFile)) + s.d->m_data.insert(scUninstallerIniFile, s.uninstallerName() + QLatin1String(".ini")); + + return s; +} + +QString Settings::logo() const +{ + return d->makeAbsolutePath(d->m_data.value(scLogo).toString()); +} + +QString Settings::logoSmall() const +{ + return d->makeAbsolutePath(d->m_data.value(scLogoSmall).toString()); +} + +QString Settings::title() const +{ + return d->m_data.value(scTitle).toString(); +} + +QString Settings::applicationName() const +{ + return d->m_data.value(scName).toString(); +} + +QString Settings::applicationVersion() const +{ + return d->m_data.value(scVersion).toString(); +} + +QString Settings::publisher() const +{ + return d->m_data.value(scPublisher).toString(); +} + +QString Settings::url() const +{ + return d->m_data.value(scProductUrl).toString(); +} + +QString Settings::watermark() const +{ + return d->makeAbsolutePath(d->m_data.value(scWatermark).toString()); +} + +QString Settings::background() const +{ + return d->makeAbsolutePath(d->m_data.value(scBackground).toString()); +} + +QString Settings::icon() const +{ + const QString icon = d->makeAbsolutePath(d->m_data.value(scIcon).toString()); +#if defined(Q_WS_MAC) + return icon + QLatin1String(".icns"); +#elif defined(Q_WS_WIN) + return icon + QLatin1String(".ico"); +#endif + return icon + QLatin1String(".png"); +} + +QString Settings::removeTargetDir() const +{ + return d->m_data.value(scRemoveTargetDir).toString(); +} + +QString Settings::uninstallerName() const +{ + return d->m_data.value(scUninstallerName).toString(); +} + +QString Settings::uninstallerIniFile() const +{ + return d->m_data.value(scUninstallerIniFile).toString(); +} + +QString Settings::runProgram() const +{ + return d->m_data.value(scRunProgram).toString(); +} + +QString Settings::runProgramDescription() const +{ + return d->m_data.value(scRunProgramDescription).toString(); +} + +QString Settings::startMenuDir() const +{ + return d->m_data.value(scStartMenuDir).toString(); +} + +QString Settings::targetDir() const +{ + return d->m_data.value(scTargetDir).toString(); +} + +QString Settings::adminTargetDir() const +{ + return d->m_data.value(scAdminTargetDir).toString(); +} + +QString Settings::configurationFileName() const +{ + return d->m_data.value(scTargetConfigurationFile).toString(); +} + +QStringList Settings::certificateFiles() const +{ + return d->m_data.value(scSigningCertificate).toStringList(); +} + +bool Settings::allowNoneAsciiCharacters() const +{ + return d->m_data.value(scAllowNonAsciiCharacters).toBool(); +} + +bool Settings::hasReplacementRepos() const +{ + return d->m_replacementRepos; +} + +QSet<Repository> Settings::repositories() const +{ + if (d->m_replacementRepos) + return variantListToSet<Repository>(d->m_data.values(scTmpRepositories)); + + return variantListToSet<Repository>(d->m_data.values(scRepositories) + + d->m_data.values(scUserRepositories) + d->m_data.values(scTmpRepositories)); +} + +QSet<Repository> Settings::defaultRepositories() const +{ + return variantListToSet<Repository>(d->m_data.values(scRepositories)); +} + +void Settings::setDefaultRepositories(const QSet<Repository> &repositories) +{ + d->m_data.remove(scRepositories); + addDefaultRepositories(repositories); +} + +void Settings::addDefaultRepositories(const QSet<Repository> &repositories) +{ + foreach (const Repository &repository, repositories) + d->m_data.insertMulti(scRepositories, QVariant().fromValue(repository)); +} + +Settings::Update +Settings::updateDefaultRepositories(const QHash<QString, QPair<Repository, Repository> > &updates) +{ + if (updates.isEmpty()) + return Settings::NoUpdatesApplied; + + QHash <QUrl, Repository> defaultRepos; + foreach (const QVariant &variant, d->m_data.values(scRepositories)) { + const Repository repository = variant.value<Repository>(); + defaultRepos.insert(repository.url(), repository); + } + + bool update = false; + QList<QPair<Repository, Repository> > values = updates.values(QLatin1String("replace")); + for (int a = 0; a < values.count(); ++a) { + const QPair<Repository, Repository> data = values.at(a); + if (defaultRepos.contains(data.second.url())) { + update = true; + defaultRepos.remove(data.second.url()); + defaultRepos.insert(data.first.url(), data.first); + } + } + + values = updates.values(QLatin1String("remove")); + for (int a = 0; a < values.count(); ++a) { + const QPair<Repository, Repository> data = values.at(a); + if (defaultRepos.contains(data.first.url())) { + update = true; + defaultRepos.remove(data.first.url()); + } + } + + values = updates.values(QLatin1String("add")); + for (int a = 0; a < values.count(); ++a) { + const QPair<Repository, Repository> data = values.at(a); + if (!defaultRepos.contains(data.first.url())) { + update = true; + defaultRepos.insert(data.first.url(), data.first); + } + } + + if (update) + setDefaultRepositories(defaultRepos.values().toSet()); + return update ? Settings::UpdatesApplied : Settings::NoUpdatesApplied; +} + +QSet<Repository> Settings::temporaryRepositories() const +{ + return variantListToSet<Repository>(d->m_data.values(scTmpRepositories)); +} + +void Settings::setTemporaryRepositories(const QSet<Repository> &repositories, bool replace) +{ + d->m_data.remove(scTmpRepositories); + addTemporaryRepositories(repositories, replace); +} + +void Settings::addTemporaryRepositories(const QSet<Repository> &repositories, bool replace) +{ + d->m_replacementRepos = replace; + foreach (const Repository &repository, repositories) + d->m_data.insertMulti(scTmpRepositories, QVariant().fromValue(repository)); +} + +QSet<Repository> Settings::userRepositories() const +{ + return variantListToSet<Repository>(d->m_data.values(scUserRepositories)); +} + +void Settings::setUserRepositories(const QSet<Repository> &repositories) +{ + d->m_data.remove(scUserRepositories); + addUserRepositories(repositories); +} + +void Settings::addUserRepositories(const QSet<Repository> &repositories) +{ + foreach (const Repository &repository, repositories) + d->m_data.insertMulti(scUserRepositories, QVariant().fromValue(repository)); +} + +QVariant Settings::value(const QString &key, const QVariant &defaultValue) const +{ + return d->m_data.value(key, defaultValue); +} + +QVariantList Settings::values(const QString &key, const QVariantList &defaultValue) const +{ + QVariantList list = d->m_data.values(key); + return list.isEmpty() ? defaultValue : list; +} + +QVariantHash Settings::titlesForPage(const QString &pageName) const +{ + const QVariantHash hash = d->m_data.value(pageName).toHash(); + const QVariant variant = hash.value(QLatin1String("Title"), QVariant()); + if (!variant.canConvert<QVariantHash>()) + return QVariantHash(); + return variant.value<QVariantHash>(); +} + +QVariantHash Settings::subTitlesForPage(const QString &pageName) const +{ + const QVariantHash hash = d->m_data.value(pageName).toHash(); + const QVariant variant = hash.value(QLatin1String("SubTitle"), QVariant()); + if (!variant.canConvert<QVariantHash>()) + return QVariantHash(); + return variant.value<QVariantHash>(); +} + +Settings::ProxyType Settings::proxyType() const +{ + return Settings::ProxyType(d->m_data.value(scProxyType, Settings::NoProxy).toInt()); +} + +void Settings::setProxyType(Settings::ProxyType type) +{ + d->m_data.insert(scProxyType, type); +} + +QNetworkProxy Settings::ftpProxy() const +{ + const QVariant variant = d->m_data.value(scFtpProxy); + if (variant.canConvert<QNetworkProxy>()) + return variant.value<QNetworkProxy>(); + return QNetworkProxy(); +} + +void Settings::setFtpProxy(const QNetworkProxy &proxy) +{ + d->m_data.insert(scFtpProxy, QVariant::fromValue(proxy)); +} + +QNetworkProxy Settings::httpProxy() const +{ + const QVariant variant = d->m_data.value(scHttpProxy); + if (variant.canConvert<QNetworkProxy>()) + return variant.value<QNetworkProxy>(); + return QNetworkProxy(); +} + +void Settings::setHttpProxy(const QNetworkProxy &proxy) +{ + d->m_data.insert(scHttpProxy, QVariant::fromValue(proxy)); +} diff --git a/src/libs/installer/settings.h b/src/libs/installer/settings.h new file mode 100644 index 000000000..5fb60a7c9 --- /dev/null +++ b/src/libs/installer/settings.h @@ -0,0 +1,141 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef SETTINGS_H +#define SETTINGS_H + +#include "constants.h" +#include "installer_global.h" + +#include <QtCore/QCoreApplication> +#include <QtCore/QSharedDataPointer> +#include <QtCore/QVariant> + +#include <QtNetwork/QNetworkProxy> + +Q_DECLARE_METATYPE(QNetworkProxy) + +namespace QInstaller { +class Repository; + +class INSTALLER_EXPORT Settings +{ + Q_DECLARE_TR_FUNCTIONS(Settings) + +public: + enum Update { + UpdatesApplied, + NoUpdatesApplied + }; + + enum ProxyType { + NoProxy, + SystemProxy, + UserDefinedProxy + }; + + explicit Settings(); + ~Settings(); + + Settings(const Settings &other); + Settings &operator=(const Settings &other); + + static Settings fromFileAndPrefix(const QString &path, const QString &prefix); + + QString logo() const; + QString logoSmall() const; + QString title() const; + QString publisher() const; + QString url() const; + QString watermark() const; + QString background() const; + QString icon() const; + + QString applicationName() const; + QString applicationVersion() const; + + QString runProgram() const; + QString runProgramDescription() const; + QString startMenuDir() const; + QString targetDir() const; + QString adminTargetDir() const; + + QString removeTargetDir() const; + QString uninstallerName() const; + QString uninstallerIniFile() const; + + QString configurationFileName() const; + + bool hasReplacementRepos() const; + QSet<Repository> repositories() const; + + QSet<Repository> defaultRepositories() const; + void setDefaultRepositories(const QSet<Repository> &repositories); + void addDefaultRepositories(const QSet<Repository> &repositories); + Settings::Update updateDefaultRepositories(const QHash<QString, QPair<Repository, Repository> > &updates); + + QSet<Repository> temporaryRepositories() const; + void setTemporaryRepositories(const QSet<Repository> &repositories, bool replace); + void addTemporaryRepositories(const QSet<Repository> &repositories, bool replace); + + QSet<Repository> userRepositories() const; + void setUserRepositories(const QSet<Repository> &repositories); + void addUserRepositories(const QSet<Repository> &repositories); + + QStringList certificateFiles() const; + bool allowNoneAsciiCharacters() const; + + QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; + QVariantList values(const QString &key, const QVariantList &defaultValue = QVariantList()) const; + + QVariantHash titlesForPage(const QString &pageName) const; + QVariantHash subTitlesForPage(const QString &pageName) const; + + Settings::ProxyType proxyType() const; + void setProxyType(Settings::ProxyType type); + + QNetworkProxy ftpProxy() const; + void setFtpProxy(const QNetworkProxy &proxy); + + QNetworkProxy httpProxy() const; + void setHttpProxy(const QNetworkProxy &proxy); + +private: + class Private; + QSharedDataPointer<Private> d; +}; + +} + +Q_DECLARE_METATYPE(QInstaller::Settings) + +#endif // SETTINGS_H diff --git a/src/libs/installer/simplemovefileoperation.cpp b/src/libs/installer/simplemovefileoperation.cpp new file mode 100644 index 000000000..f99915600 --- /dev/null +++ b/src/libs/installer/simplemovefileoperation.cpp @@ -0,0 +1,113 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "simplemovefileoperation.h" + +#include <QtCore/QFileInfo> + +namespace QInstaller { + +SimpleMoveFileOperation::SimpleMoveFileOperation() +{ + setName(QLatin1String("SimpleMoveFile")); +} + +void SimpleMoveFileOperation::backup() +{ +} + +bool SimpleMoveFileOperation::performOperation() +{ + const QStringList args = arguments(); + if (args.count() != 2) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in %0: %1 arguments given, exactly 2 expected.").arg(name()) + .arg(args.count())); + return false; + } + + const QString source = args.at(0); + const QString target = args.at(1); + + if (source.isEmpty() || target.isEmpty()) { + setError(UserDefinedError); + setErrorString(tr("None of the arguments can be empty: source(%1), target(%2).") + .arg(source, target)); + return false; + } + + // If destination file exists, then we cannot use QFile::copy() because it does not overwrite an existing + // file. So we remove the destination file. + QFile file(target); + if (file.exists()) { + if (!file.remove()) { + setError(UserDefinedError); + setErrorString(tr("Can not copy source(%1) to target(%2), because target exists and is " + "not removable.").arg(source, target)); + return false; + } + } + + file.setFileName(source); + if (!file.rename(target)) { + setError(UserDefinedError); + setErrorString(tr("Can not move source(%1) to target(%2): %3").arg(source, target, + file.errorString())); + return false; + } + + emit outputTextChanged(tr("Move %1 to %2.").arg(source, target)); + return true; +} + +bool SimpleMoveFileOperation::undoOperation() +{ + const QString source = arguments().at(0); + const QString target = arguments().at(1); + + QFile(target).rename(source); + emit outputTextChanged(tr("Move %1 to %2.").arg(target, source)); + + return true; +} + +bool SimpleMoveFileOperation::testOperation() +{ + return true; +} + +Operation *SimpleMoveFileOperation::clone() const +{ + return new SimpleMoveFileOperation(); +} + +} // namespace QInstaller diff --git a/src/libs/installer/simplemovefileoperation.h b/src/libs/installer/simplemovefileoperation.h new file mode 100644 index 000000000..97b3391ed --- /dev/null +++ b/src/libs/installer/simplemovefileoperation.h @@ -0,0 +1,61 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef SIMPLEMOVEFILEOPERATION_H +#define SIMPLEMOVEFILEOPERATION_H + +#include "qinstallerglobal.h" + +#include <QtCore/QObject> + +namespace QInstaller { + +class INSTALLER_EXPORT SimpleMoveFileOperation : public QObject, public Operation +{ + Q_OBJECT + +public: + SimpleMoveFileOperation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation *clone() const; + +Q_SIGNALS: + void outputTextChanged(const QString &progress); +}; + +} + +#endif //SIMPLEMOVEFILEOPERATION_H diff --git a/src/libs/installer/templates.cpp b/src/libs/installer/templates.cpp new file mode 100644 index 000000000..90eed17e3 --- /dev/null +++ b/src/libs/installer/templates.cpp @@ -0,0 +1,176 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include<QtCore/QIODevice> + +template<typename T> +QDataStream &operator>>(QDataStream &stream, T &state) +{ + int s; + stream >> s; + state = static_cast<T> (s); + return stream; +} + +template<typename UNUSED> +void callRemoteVoidMethod(QDataStream &stream, const QString &name) +{ + stream.device()->readAll(); + stream << name; + stream.device()->waitForBytesWritten(-1); + if (!stream.device()->bytesAvailable()) + stream.device()->waitForReadyRead(-1); + quint32 test; + stream >> test; + stream.device()->readAll(); + return; +} + +template<typename T> +void callRemoteVoidMethod(QDataStream & stream, const QString &name, const T ¶m1) +{ + stream.device()->readAll(); + stream << name; + stream << param1; + stream.device()->waitForBytesWritten(-1); + if (!stream.device()->bytesAvailable()) + stream.device()->waitForReadyRead(-1); + quint32 test; + stream >> test; + stream.device()->readAll(); + return; +} + +template<typename T1, typename T2> +void callRemoteVoidMethod(QDataStream &stream, const QString &name, const T1 ¶m1, const T2 ¶m2) +{ + stream.device()->readAll(); + stream << name; + stream << param1; + stream << param2; + stream.device()->waitForBytesWritten(-1); + if (!stream.device()->bytesAvailable()) + stream.device()->waitForReadyRead(-1); + quint32 test; + stream >> test; + stream.device()->readAll(); + return; +} + +template<typename T1, typename T2, typename T3> +void callRemoteVoidMethod(QDataStream &stream, const QString &name, const T1 ¶m1, const T2 ¶m2, + const T3 & param3) +{ + stream.device()->readAll(); + stream << name; + stream << param1; + stream << param2; + stream << param3; + stream.device()->waitForBytesWritten(-1); + if (!stream.device()->bytesAvailable()) + stream.device()->waitForReadyRead(-1); + quint32 test; + stream >> test; + stream.device()->readAll(); + return; +} + +template<typename RESULT> +RESULT callRemoteMethod(QDataStream &stream, const QString &name) +{ + stream.device()->readAll(); + stream << name; + stream.device()->waitForBytesWritten(-1); + if (!stream.device()->bytesAvailable()) + stream.device()->waitForReadyRead(-1); + quint32 test; + stream >> test; + RESULT result; + stream >> result; + stream.device()->readAll(); + return result; +} + +template<typename RESULT, typename T> +RESULT callRemoteMethod(QDataStream &stream, const QString &name, const T ¶m1) +{ + stream.device()->readAll(); + stream << name; + stream << param1; + stream.device()->waitForBytesWritten(-1); + if (!stream.device()->bytesAvailable()) + stream.device()->waitForReadyRead(-1); + quint32 test; + stream >> test; + RESULT result; + stream >> result; + stream.device()->readAll(); + return result; +} + +template<typename RESULT, typename T1, typename T2> +RESULT callRemoteMethod(QDataStream &stream, const QString &name, const T1 & param1, const T2 ¶m2) +{ + stream.device()->readAll(); + stream << name; + stream << param1; + stream << param2; + stream.device()->waitForBytesWritten(-1); + if (!stream.device()->bytesAvailable()) + stream.device()->waitForReadyRead(-1); + quint32 test; + stream >> test; + RESULT result; + stream >> result; + stream.device()->readAll(); + return result; +} + +template<typename RESULT, typename T1, typename T2, typename T3> +RESULT callRemoteMethod(QDataStream &stream, const QString &name, const T1 ¶m1, const T2 ¶m2, + const T3 ¶m3) +{ + stream.device()->readAll(); + stream << name; + stream << param1; + stream << param2; + stream << param3; + stream.device()->waitForBytesWritten(-1); + if (!stream.device()->bytesAvailable()) + stream.device()->waitForReadyRead(-1); + quint32 test; + stream >> test; + RESULT result; + stream >> result; + stream.device()->readAll(); + return result; +} diff --git a/src/libs/installer/updatecreatorsettingsfrom21to22operation.cpp b/src/libs/installer/updatecreatorsettingsfrom21to22operation.cpp new file mode 100644 index 000000000..480060496 --- /dev/null +++ b/src/libs/installer/updatecreatorsettingsfrom21to22operation.cpp @@ -0,0 +1,325 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "updatecreatorsettingsfrom21to22operation.h" + +#include "constants.h" +#include "registerdefaultdebuggeroperation.h" +#include "registertoolchainoperation.h" +#include "qtcreatorpersistentsettings.h" +#include "packagemanagercore.h" +#include "qtcreator_constants.h" + +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QFileInfo> +#include <QtCore/QSettings> +#include <QtCore/QString> + +using namespace QInstaller; + +using namespace ProjectExplorer; + +QStringList getQmakePathesOfAllInstallerRegisteredQtVersions(const QSettings &settings) +{ + QStringList qmakePathes; + + QStringList oldNewQtVersions = settings.value(QLatin1String("NewQtVersions")).toString().split( + QLatin1String(";")); + + foreach (const QString &qtVersion, oldNewQtVersions) { + QStringList splitedQtConfiguration = qtVersion.split(QLatin1String("=")); + if (splitedQtConfiguration.count() > 1 + && splitedQtConfiguration.at(1).contains(QLatin1String("qmake"), Qt::CaseInsensitive)) { + QString qmakePath = splitedQtConfiguration.at(1); + qmakePathes.append(qmakePath); + } + } + return qmakePathes; +} + +bool removeInstallerRegisteredQtVersions(QSettings &settings, const QStringList &qmakePathes) +{ + return true; + qDebug() << Q_FUNC_INFO << settings.fileName(); + settings.beginGroup(QLatin1String(QtVersionsSectionName)); + int qtVersionSizeValue = settings.value(QLatin1String("size")).toInt(); + qDebug() << "qtVersionSizeValue:" << qtVersionSizeValue; + + //read all settings for Qt Versions + QHash<QString, QVariant> oldSettingsAsHash; + foreach (const QString &key, settings.allKeys()) + oldSettingsAsHash.insert(key, settings.value(key)); + qDebug() << "settings.allKeys():" << settings.allKeys(); + + //get the installer added Qt Version settings ids + QList<int> toRemoveIds; + QHashIterator<QString, QVariant> it(oldSettingsAsHash); + while (it.hasNext()) { + it.next(); + if (it.key().endsWith(QLatin1String("QMakePath")) && !it.value().toString().isEmpty()) { + foreach (const QString &toRemoveQmakePath, qmakePathes) { + if (QFileInfo(it.value().toString()) == QFileInfo(toRemoveQmakePath)) { + int firstNoDigitCharIndex = it.key().indexOf(QRegExp(QLatin1String("[^0-9]"))); + QString numberAtTheBeginning = it.key().left(firstNoDigitCharIndex); + toRemoveIds << numberAtTheBeginning.toInt(); + } + } + } + } + qDebug() << "toRemoveIds:" << toRemoveIds; + + //now write only the other Qt Versions to QtCreator settings + it.toFront(); + QHash<int, int> qtVersionIdMapper; //old, new + int newVersionId = 1; + while (it.hasNext()) { + it.next(); + settings.remove(it.key()); + int firstNoDigitCharIndex = it.key().indexOf(QRegExp(QLatin1String("[^0-9]"))); + QString numberAtTheBeginningAsString = it.key().left(firstNoDigitCharIndex); + QString restOfTheKey = it.key().mid(firstNoDigitCharIndex); + bool isNumber = false; + //check that it is a nummer - for example "size" value of the settings array is not + int numberAtTheBeginning = numberAtTheBeginningAsString.toInt(&isNumber); + if (isNumber && !toRemoveIds.contains(numberAtTheBeginning)) { + if (!qtVersionIdMapper.contains(numberAtTheBeginning)) { + qtVersionIdMapper.insert(numberAtTheBeginning, newVersionId); + newVersionId++; + } + QString newKey = QString::number(qtVersionIdMapper.value(numberAtTheBeginning)) + restOfTheKey; + if (newKey.endsWith(QLatin1String("Id"))) { + settings.setValue(newKey, qtVersionIdMapper.value(numberAtTheBeginning)); + } else { + settings.setValue(newKey, it.value()); + } + } + } + + settings.setValue(QLatin1String("size"), qtVersionIdMapper.count()); + settings.endGroup(); //QtVersionsSectionName + + if (qtVersionIdMapper.count() != qtVersionSizeValue - toRemoveIds.count()) { + return false; + } + return true; +} + +bool convertQtInstallerSettings(QSettings &settings, const QString &toolChainsXmlFilePath, + QInstaller::PackageManagerCore *const core) +{ + QStringList oldNewQtVersions = settings.value(QLatin1String("NewQtVersions")).toString().split( + QLatin1String(";")); + + QSet<QString> mingwToolChains; + QSet<QString> gcceToolChains; + QString newQtVersions; + foreach (const QString &qtVersion, oldNewQtVersions) { + QStringList splitedQtConfiguration = qtVersion.split(QLatin1String("=")); + if (splitedQtConfiguration.count() == 8) { + int positionCounter = 0; + const QString &versionName = splitedQtConfiguration.at(positionCounter++); + const QString &qmakePath = splitedQtConfiguration.at(positionCounter++); + mingwToolChains.insert(splitedQtConfiguration.at(positionCounter++)); + QString systemRoot = splitedQtConfiguration.at(positionCounter++); + QString gccePath = splitedQtConfiguration.at(positionCounter++); + gcceToolChains.insert(gccePath); + QString carbidePath = splitedQtConfiguration.at(positionCounter++); + Q_UNUSED(carbidePath) + QString msvcPath = splitedQtConfiguration.at(positionCounter++); + Q_UNUSED(msvcPath) + QString sbsPath = splitedQtConfiguration.at(positionCounter++); + + QString addedQtVersion = versionName; + + addedQtVersion += QLatin1Char('=') + qmakePath; + addedQtVersion += QLatin1Char('=') + systemRoot; + addedQtVersion += QLatin1Char('=') + sbsPath; + newQtVersions.append(addedQtVersion + QLatin1Char(';')); + } else { + newQtVersions.append(qtVersion + QLatin1Char(';')); + } + } + settings.setValue(QLatin1String("NewQtVersions"), newQtVersions); + + QtCreatorPersistentSettings creatorToolChainSettings; + + if (!creatorToolChainSettings.init(toolChainsXmlFilePath)) + return false; + + foreach (const QString &mingwPath, mingwToolChains) { + if (mingwPath.isEmpty()) + continue; + QInstaller::RegisterToolChainOperation operation; + operation.setValue(QLatin1String("installer"), QVariant::fromValue(core)); + operation.setArguments(QStringList() + << QLatin1String("GccToolChain") + << QLatin1String("ProjectExplorer.ToolChain.Mingw") + << QLatin1String("Mingw as a GCC for Windows targets") + << QLatin1String("x86-windows-msys-pe-32bit") + << mingwPath + QLatin1String("\\bin\\g++.exe") + << creatorToolChainSettings.abiToDebuggerHash().value(QLatin1String + ("x86-windows-msys-pe-32bit")) + ); + bool result = operation.performOperation(); + Q_UNUSED(result); + Q_ASSERT(result); + } + foreach (const QString gccePath, gcceToolChains) { + if (gccePath.isEmpty()) + continue; + QInstaller::RegisterToolChainOperation operation; + operation.setValue(QLatin1String("installer"), QVariant::fromValue(core)); + operation.setArguments(QStringList() + << QLatin1String("GccToolChain") + << QLatin1String("Qt4ProjectManager.ToolChain.GCCE") + << QLatin1String("GCCE 4 for Symbian targets") + << QLatin1String("arm-symbian-device-elf-32bit") + << gccePath + QLatin1String("\\bin\\arm-none-symbianelf-g++.exe") + << creatorToolChainSettings.abiToDebuggerHash().value(QLatin1String( + "arm-symbian-device-elf-32bit")) + ); + bool result = operation.performOperation(); + Q_UNUSED(result); + Q_ASSERT(result); + } + return true; +} + +void convertDefaultGDBInstallerSettings(QSettings &settings, QInstaller::PackageManagerCore *const core) +{ + settings.beginGroup(QLatin1String("GdbBinaries21")); + + //read all settings for GDBs + QHash<QString, QString> abiToDefaultDebuggerHash; + foreach (const QString &key, settings.allKeys()) { + QString oldValue = settings.value(key).toString(); + QString gdbBinaryPath = oldValue.left(oldValue.indexOf(QLatin1String(","))); + + QString gdbTypesAsCommaSeperatedString = oldValue.mid(oldValue.indexOf(QLatin1String(","))); + QStringList gdbTypeList = gdbTypesAsCommaSeperatedString.split(QLatin1String(",")); + foreach (const QString &gdbType, gdbTypeList) { + if (gdbType == QLatin1String("0")) { + abiToDefaultDebuggerHash.insert(QLatin1String("x86-linux-generic-elf-64bit"), gdbBinaryPath); + abiToDefaultDebuggerHash.insert(QLatin1String("x86-linux-generic-elf-32bit"), gdbBinaryPath); + } + if (gdbType == QLatin1String("2")) { + abiToDefaultDebuggerHash.insert(QLatin1String("x86-windows-msys-pe-32bit"), gdbBinaryPath); + } + if (gdbType == QLatin1String("6")) { + abiToDefaultDebuggerHash.insert(QLatin1String("arm-symbian-device-elf-32bit"), gdbBinaryPath); + } + if (gdbType == QLatin1String("9")) { + abiToDefaultDebuggerHash.insert(QLatin1String("arm-linux-harmattan-elf-32bit"), gdbBinaryPath); + abiToDefaultDebuggerHash.insert(QLatin1String("arm-linux-maemo-elf-32bit"), gdbBinaryPath); + abiToDefaultDebuggerHash.insert(QLatin1String("arm-linux-meego-elf-32bit"), gdbBinaryPath); + } + } + } + QInstaller::RegisterDefaultDebuggerOperation operation; + operation.setValue(QLatin1String("installer"), QVariant::fromValue(core)); + + QHashIterator<QString, QString> it(abiToDefaultDebuggerHash); + while (it.hasNext()) { + it.next(); + operation.setArguments(QStringList() << it.key() << it.value()); + bool result = operation.performOperation(); + Q_UNUSED(result); + Q_ASSERT(result); + } + + settings.endGroup(); //"GdbBinaries21" +} + +UpdateCreatorSettingsFrom21To22Operation::UpdateCreatorSettingsFrom21To22Operation() +{ + setName(QLatin1String("UpdateCreatorSettingsFrom21To22")); +} + +void UpdateCreatorSettingsFrom21To22Operation::backup() +{ +} + +bool UpdateCreatorSettingsFrom21To22Operation::performOperation() +{ + const QStringList args = arguments(); + + if (args.count() != 0) { + setError(InvalidArguments); + setErrorString(tr("Invalid arguments in %0: %1 arguments given, exactly 0 expected.") + .arg(name()).arg(args.count())); + return false; + } + + PackageManagerCore *const core = qVariantValue<PackageManagerCore*>(value(QLatin1String("installer"))); + if (!core) { + setError(UserDefinedError); + setErrorString(tr("Needed installer object in %1 operation is empty.").arg(name())); + return false; + } + const QString &rootInstallPath = core->value(scTargetDir); + + QString toolChainsXmlFilePath = rootInstallPath + QLatin1String(ToolChainSettingsSuffixPath); + + QSettings sdkSettings(rootInstallPath + QLatin1String(QtCreatorSettingsSuffixPath), + QSettings::IniFormat); + + convertDefaultGDBInstallerSettings(sdkSettings, core); + + QString userSettingsFileName = core->value(QLatin1String("QtCreatorSettingsFile")); + if (QFile::exists(userSettingsFileName)) { + QSettings userSettings(userSettingsFileName, QSettings::IniFormat); + QStringList qmakePathes = getQmakePathesOfAllInstallerRegisteredQtVersions(sdkSettings); + if (!removeInstallerRegisteredQtVersions(userSettings, qmakePathes)) { + setError(UserDefinedError); + setErrorString(tr("Can not remove previous registered Qt Versions in %1 operation.").arg(name())); + return false; + } + } + + return convertQtInstallerSettings(sdkSettings, toolChainsXmlFilePath, core); +} + +bool UpdateCreatorSettingsFrom21To22Operation::undoOperation() +{ + return true; +} + +bool UpdateCreatorSettingsFrom21To22Operation::testOperation() +{ + return true; +} + +Operation *UpdateCreatorSettingsFrom21To22Operation::clone() const +{ + return new UpdateCreatorSettingsFrom21To22Operation(); +} diff --git a/src/libs/installer/updatecreatorsettingsfrom21to22operation.h b/src/libs/installer/updatecreatorsettingsfrom21to22operation.h new file mode 100644 index 000000000..f0e2b343c --- /dev/null +++ b/src/libs/installer/updatecreatorsettingsfrom21to22operation.h @@ -0,0 +1,54 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef UPDATECREATORSETTINGSFROM21TO22OPERATION_H +#define UPDATECREATORSETTINGSFROM21TO22OPERATION_H + +#include "qinstallerglobal.h" + +namespace QInstaller { + +class UpdateCreatorSettingsFrom21To22Operation : public Operation +{ +public: + UpdateCreatorSettingsFrom21To22Operation(); + + void backup(); + bool performOperation(); + bool undoOperation(); + bool testOperation(); + Operation *clone() const; +}; + +} // namespace QInstaller + +#endif // UPDATECREATORSETTINGSFROM21TO22OPERATION_H diff --git a/src/libs/installer/updater.cpp b/src/libs/installer/updater.cpp new file mode 100644 index 000000000..ec0564a8f --- /dev/null +++ b/src/libs/installer/updater.cpp @@ -0,0 +1,97 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ +#include "updater.h" + +#include "binaryformat.h" +#include "component.h" +#include "init.h" +#include "packagemanagercore.h" +#include "utils.h" + +#include <QtCore/QDebug> + +#include <QtXml/QDomDocument> + +#include <iostream> + +using namespace QInstaller; +using namespace QInstallerCreator; + + +Updater::Updater() +{ + QInstaller::init(); +} + +void Updater::setVerbose(bool verbose) +{ + QInstaller::setVerbose(verbose); +} + +bool Updater::checkForUpdates() +{ + BinaryContent content = BinaryContent::readAndRegisterFromApplicationFile(); + if (content.magicMarker() == MagicInstallerMarker) { + qDebug() << "Impossible to use an installer to check for updates!"; + return false; + } + + PackageManagerCore core(content.magicMarker(), content.performedOperations()); + core.setUpdater(); + PackageManagerCore::setVirtualComponentsVisible(true); + + if (!core.fetchRemotePackagesTree()) + return false; + + const QList<QInstaller::Component *> components = core.updaterComponents(); + + if (components.isEmpty()) { + qDebug() << "There are currently no updates available."; + return false; + } + + QDomDocument doc; + QDomElement root = doc.createElement(QLatin1String("updates")); + doc.appendChild(root); + + QList<QInstaller::Component *>::const_iterator it; + for (it = components.begin(); it != components.end(); ++it) { + QDomElement update = doc.createElement(QLatin1String("update")); + update.setAttribute(QLatin1String("name"), (*it)->value(scDisplayName)); + update.setAttribute(QLatin1String("version"), (*it)->value(scRemoteVersion)); + update.setAttribute(QLatin1String("size"), (*it)->value(scUncompressedSize)); + root.appendChild(update); + } + + std::cout << doc.toString(4) << std::endl; + return true; +} diff --git a/src/libs/installer/updater.h b/src/libs/installer/updater.h new file mode 100644 index 000000000..8d6423250 --- /dev/null +++ b/src/libs/installer/updater.h @@ -0,0 +1,55 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef UPDATER_H +#define UPDATER_H + +#include "installer_global.h" + +#include <QtCore/QObject> + +namespace QInstaller { + +class INSTALLER_EXPORT Updater : public QObject +{ + Q_OBJECT + +public: + explicit Updater(); + + bool checkForUpdates(); + void setVerbose(bool verbose); +}; + +} // namespace QInstaller + +#endif diff --git a/src/libs/installer/updatesettings.cpp b/src/libs/installer/updatesettings.cpp new file mode 100644 index 000000000..f06a1fa87 --- /dev/null +++ b/src/libs/installer/updatesettings.cpp @@ -0,0 +1,164 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ +#include "updatesettings.h" + +#include "errors.h" +#include "repository.h" +#include "settings.h" + +#include <QtCore/QDateTime> +#include <QtCore/QSettings> +#include <QtCore/QStringList> + +using namespace QInstaller; + +class UpdateSettings::Private +{ +public: + Private(UpdateSettings* qq) + : q(qq) { } + +private: + UpdateSettings *const q; + +public: + QSettings &settings() + { + return externalSettings ? *externalSettings : internalSettings; + } + + static void setExternalSettings(QSettings *settings) + { + externalSettings = settings; + } + +private: + QSettings internalSettings; + static QSettings *externalSettings; +}; + +QSettings *UpdateSettings::Private::externalSettings = 0; + + +// -- UpdateSettings + +UpdateSettings::UpdateSettings() + : d(new Private(this)) +{ + d->settings().sync(); +} + +UpdateSettings::~UpdateSettings() +{ + d->settings().sync(); + delete d; +} + +/* static */ +void UpdateSettings::setSettingsSource(QSettings *settings) +{ + Private::setExternalSettings(settings); +} + +int UpdateSettings::updateInterval() const +{ + return d->settings().value(QLatin1String("updatesettings/interval"), static_cast<int>(Weekly)).toInt(); +} + +void UpdateSettings::setUpdateInterval(int seconds) +{ + d->settings().setValue(QLatin1String("updatesettings/interval"), seconds); +} + +QString UpdateSettings::lastResult() const +{ + return d->settings().value(QLatin1String("updatesettings/lastresult")).toString(); +} + +void UpdateSettings::setLastResult(const QString &lastResult) +{ + d->settings().setValue(QLatin1String("updatesettings/lastresult"), lastResult); +} + +QDateTime UpdateSettings::lastCheck() const +{ + return d->settings().value(QLatin1String("updatesettings/lastcheck")).toDateTime(); +} + +void UpdateSettings::setLastCheck(const QDateTime &lastCheck) +{ + d->settings().setValue(QLatin1String("updatesettings/lastcheck"), lastCheck); +} + +bool UpdateSettings::checkOnlyImportantUpdates() const +{ + return d->settings().value(QLatin1String("updatesettings/onlyimportant"), false).toBool(); +} + +void UpdateSettings::setCheckOnlyImportantUpdates(bool checkOnlyImportantUpdates) +{ + d->settings().setValue(QLatin1String("updatesettings/onlyimportant"), checkOnlyImportantUpdates); +} + +QSet<Repository> UpdateSettings::repositories() const +{ + QSettings &settings = d->settings(); + const int count = settings.beginReadArray(QLatin1String("updatesettings/repositories")); + + QSet<Repository> result; + for (int i = 0; i < count; ++i) { + settings.setArrayIndex(i); + result.insert(Repository(d->settings().value(QLatin1String("url")).toUrl(), false)); + } + settings.endArray(); + + try { + if(result.isEmpty()) { + result = Settings::fromFileAndPrefix(QLatin1String(":/metadata/installer-config/config.xml"), + QLatin1String(":/metadata/installer-config/")).userRepositories(); + } + } catch (const Error &error) { + qDebug("Could not parse config: %s", qPrintable(error.message())); + } + return result; +} + +void UpdateSettings::setRepositories(const QSet<Repository> &repositories) +{ + QSet<Repository>::ConstIterator it = repositories.constBegin(); + d->settings().beginWriteArray(QLatin1String("updatesettings/repositories")); + for (int i = 0; i < repositories.count(); ++i, ++it) { + d->settings().setArrayIndex(i); + d->settings().setValue(QLatin1String("url"), (*it).url()); + } + d->settings().endArray(); +} diff --git a/src/libs/installer/updatesettings.h b/src/libs/installer/updatesettings.h new file mode 100644 index 000000000..9994e20d6 --- /dev/null +++ b/src/libs/installer/updatesettings.h @@ -0,0 +1,85 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef UPDATESETTINGS_H +#define UPDATESETTINGS_H + +#include "installer_global.h" + +QT_BEGIN_NAMESPACE +class QDateTime; +template<typename T> +class QSet; +class QSettings; +QT_END_NAMESPACE + +namespace QInstaller { + +class Repository; + +class INSTALLER_EXPORT UpdateSettings +{ +public: + UpdateSettings(); + ~UpdateSettings(); + + enum Interval { + Daily = 86400, + Weekly = Daily * 7, + Monthly = Daily * 30 + }; + + static void setSettingsSource(QSettings *settings); + + int updateInterval() const; + void setUpdateInterval(int seconds); + + QString lastResult() const; + void setLastResult(const QString &lastResult); + + QDateTime lastCheck() const; + void setLastCheck(const QDateTime &lastCheck); + + bool checkOnlyImportantUpdates() const; + void setCheckOnlyImportantUpdates(bool checkOnlyImportantUpdates); + + QSet<Repository> repositories() const; + void setRepositories(const QSet<Repository> &repositories); + +private: + class Private; + Private *const d; +}; + +} + +#endif diff --git a/src/libs/installer/utils.cpp b/src/libs/installer/utils.cpp new file mode 100644 index 000000000..4bd93430f --- /dev/null +++ b/src/libs/installer/utils.cpp @@ -0,0 +1,341 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "utils.h" + +#include <QtCore/QDateTime> +#include <QtCore/QDir> +#include <QtCore/QProcessEnvironment> +#include <QtCore/QVector> + +#if defined(Q_OS_WIN32) || defined(Q_OS_WINCE) +# include "qt_windows.h" +#endif + +#include <fstream> +#include <iostream> +#include <sstream> + +static bool verb = false; + +void QInstaller::setVerbose(bool v) +{ + verb = v; +} + +bool QInstaller::isVerbose() +{ + return verb; +} + +#ifdef Q_WS_WIN +void qWinMsgHandler(QtMsgType t, const char *str); + +class debugstream : public std::ostream +{ + class buf : public std::stringbuf + { + public: + buf() {} + + int sync() + { + std::string s = str(); + if (s[s.length() - 1] == '\n' ) + s[s.length() - 1] = '\0'; // remove \n + qWinMsgHandler(QtDebugMsg, s.c_str()); + std::cout << s << std::endl; + str(std::string()); + return 0; + } + }; +public: + debugstream() : std::ostream(&b) {} +private: + buf b; +}; +#endif + +std::ostream &QInstaller::stdverbose() +{ + static std::fstream null; +#ifdef Q_WS_WIN + static debugstream stream; +#else + static std::ostream& stream = std::cout; +#endif + if (verb) + return stream; + return null; +} + +std::ostream &QInstaller::operator<<(std::ostream &os, const QString &string) +{ + return os << qPrintable(string); +} + +//TODO from kdupdaterfiledownloader.cpp, use that one once merged +QByteArray QInstaller::calculateHash(QIODevice *device, QCryptographicHash::Algorithm algo) +{ + Q_ASSERT(device); + QCryptographicHash hash(algo); + QByteArray buffer; + buffer.resize(512 * 1024); + while (true) { + const qint64 numRead = device->read(buffer.data(), buffer.size()); + if (numRead <= 0) + return hash.result(); + hash.addData(buffer.constData(), numRead); + } + return QByteArray(); // never reached +} + + +QString QInstaller::replaceVariables(const QHash<QString, QString> &vars, const QString &str) +{ + QString res; + int pos = 0; + while (true) { + int pos1 = str.indexOf(QLatin1Char('@'), pos); + if (pos1 == -1) + break; + int pos2 = str.indexOf(QLatin1Char('@'), pos1 + 1); + if (pos2 == -1) + break; + res += str.mid(pos, pos1 - pos); + QString name = str.mid(pos1 + 1, pos2 - pos1 - 1); + res += vars.value(name); + pos = pos2 + 1; + } + res += str.mid(pos); + return res; +} + +QString QInstaller::replaceWindowsEnvironmentVariables(const QString &str) +{ + const QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + QString res; + int pos = 0; + while (true) { + int pos1 = str.indexOf(QLatin1Char( '%'), pos); + if (pos1 == -1) + break; + int pos2 = str.indexOf(QLatin1Char( '%'), pos1 + 1); + if (pos2 == -1) + break; + res += str.mid(pos, pos1 - pos); + QString name = str.mid(pos1 + 1, pos2 - pos1 - 1); + res += env.value(name); + pos = pos2 + 1; + } + res += str.mid(pos); + return res; +} + +QInstaller::VerboseWriter::VerboseWriter(QObject *parent) : QObject(parent) +{ + preFileBuffer.open(QIODevice::ReadWrite); + stream.setDevice(&preFileBuffer); +} + +QInstaller::VerboseWriter::~VerboseWriter() +{ + stream.flush(); + if (logFileName.isEmpty()) // binarycreator + return; + //if the installer installed nothing - there is no target directory - where the logfile can be saved + if (!QFileInfo(logFileName).absoluteDir().exists()) + return; + + QFile output(logFileName); + if (output.open(QIODevice::ReadWrite | QIODevice::Append)) { + QString logInfo; + logInfo += QLatin1String("*************************************"); + logInfo += QLatin1String("Invoked:") + QDateTime::currentDateTime().toString(); + output.write(logInfo.toLocal8Bit()); + output.write(preFileBuffer.data()); + output.close(); + } + stream.setDevice(0); +} + +void QInstaller::VerboseWriter::setOutputStream(const QString &fileName) +{ + logFileName = fileName; +} + + +Q_GLOBAL_STATIC(QInstaller::VerboseWriter, verboseWriter) + +QInstaller::VerboseWriter *QInstaller::VerboseWriter::instance() +{ + return verboseWriter(); +} + +QInstaller::VerboseWriter &QInstaller::verbose() +{ + return *verboseWriter(); +} + +#ifdef Q_OS_WIN +// taken from qcoreapplication_p.h +template<typename Char> +static QVector<Char*> qWinCmdLine(Char *cmdParam, int length, int &argc) +{ + QVector<Char*> argv(8); + Char *p = cmdParam; + Char *p_end = p + length; + + argc = 0; + + while (*p && p < p_end) { // parse cmd line arguments + while (QChar((short)(*p)).isSpace()) // skip white space + p++; + if (*p && p < p_end) { // arg starts + int quote; + Char *start, *r; + if (*p == Char('\"') || *p == Char('\'')) { // " or ' quote + quote = *p; + start = ++p; + } else { + quote = 0; + start = p; + } + r = start; + while (*p && p < p_end) { + if (quote) { + if (*p == quote) { + p++; + if (QChar((short)(*p)).isSpace()) + break; + quote = 0; + } + } + if (*p == '\\') { // escape char? + p++; + if (*p == Char('\"') || *p == Char('\'')) + ; // yes + else + p--; // treat \ literally + } else { + if (!quote && (*p == Char('\"') || *p == Char('\''))) { // " or ' quote + quote = *p++; + continue; + } else if (QChar((short)(*p)).isSpace() && !quote) + break; + } + if (*p) + *r++ = *p++; + } + if (*p && p < p_end) + p++; + *r = Char('\0'); + + if (argc >= (int)argv.size()-1) // expand array + argv.resize(argv.size()*2); + argv[argc++] = start; + } + } + argv[argc] = 0; + + return argv; +} + +QStringList QInstaller::parseCommandLineArgs(int argc, char **argv) +{ + Q_UNUSED(argc) + Q_UNUSED(argv) + + QStringList arguments; + QString cmdLine = QString::fromWCharArray(GetCommandLine()); + + QVector<wchar_t*> args = qWinCmdLine<wchar_t>((wchar_t *)cmdLine.utf16(), cmdLine.length(), argc); + for (int a = 0; a < argc; ++a) + arguments << QString::fromWCharArray(args[a]); + return arguments; +} +#else +QStringList QInstaller::parseCommandLineArgs(int argc, char **argv) +{ + QStringList arguments; + for (int a = 0; a < argc; ++a) + arguments << QString::fromLocal8Bit(argv[a]); + return arguments; +} +#endif + +#ifdef Q_OS_WIN +// taken 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; +} + +QString QInstaller::createCommandline(const QString &program, const QStringList &arguments) +{ + return qt_create_commandline(program, arguments); +} +#endif diff --git a/src/libs/installer/utils.h b/src/libs/installer/utils.h new file mode 100644 index 000000000..cbf2e95e7 --- /dev/null +++ b/src/libs/installer/utils.h @@ -0,0 +1,92 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef QINSTALLER_UTILS_H +#define QINSTALLER_UTILS_H + +#include "installer_global.h" + +#include <QtCore/QBuffer> +#include <QtCore/QCryptographicHash> +#include <QtCore/QHash> +#include <QtCore/QUrl> +#include <QtCore/QTextStream> + +#include <ostream> + +QT_BEGIN_NAMESPACE +class QIODevice; +QT_END_NAMESPACE + +namespace QInstaller { + + QByteArray INSTALLER_EXPORT calculateHash(QIODevice *device, QCryptographicHash::Algorithm algo); + + QString INSTALLER_EXPORT replaceVariables(const QHash<QString,QString> &vars, const QString &str); + QString INSTALLER_EXPORT replaceWindowsEnvironmentVariables(const QString &str); + QStringList INSTALLER_EXPORT parseCommandLineArgs(int argc, char **argv); +#ifdef Q_OS_WIN + QString createCommandline(const QString &program, const QStringList &arguments); +#endif + + void INSTALLER_EXPORT setVerbose(bool v); + bool INSTALLER_EXPORT isVerbose(); + + INSTALLER_EXPORT std::ostream& stdverbose(); + INSTALLER_EXPORT std::ostream& operator<<(std::ostream &os, const QString &string); + + class VerboseWriter; + INSTALLER_EXPORT VerboseWriter &verbose(); + + class INSTALLER_EXPORT VerboseWriter : public QObject + { + Q_OBJECT + public: + VerboseWriter(QObject *parent = 0); + ~VerboseWriter(); + + static VerboseWriter *instance(); + + inline VerboseWriter &operator<<(const char *t) { stdverbose() << t; stream << t; return *this; } + inline VerboseWriter &operator<<(std::ostream& (*f)(std::ostream &s)) { stdverbose() << *f; stream << "\n"; return *this; } + public slots: + void setOutputStream(const QString &fileName); + + private: + QTextStream stream; + QBuffer preFileBuffer; + QString logFileName; + }; + +} + +#endif // QINSTALLER_UTILS_H diff --git a/src/libs/installer/zipjob.cpp b/src/libs/installer/zipjob.cpp new file mode 100644 index 000000000..bcc617d31 --- /dev/null +++ b/src/libs/installer/zipjob.cpp @@ -0,0 +1,206 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ +#include <zipjob.h> + +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QMetaType> +#include <QtCore/QStringList> + +#include <cassert> +#include <climits> + +class ZipJob::Private +{ +public: + Private() : outputDevice(0), process(0) {} + + QIODevice *outputDevice; + QDir workingDir; + QProcess *process; + QStringList filesToArchive; +}; + +Q_DECLARE_METATYPE(QProcess::ExitStatus) + +ZipJob::ZipJob() + : d(new Private()) +{ + qRegisterMetaType<QProcess::ExitStatus>(); +} + +ZipJob::~ZipJob() +{ + delete d; +} + +void ZipJob::run() +{ + assert(!d->process); + d->process = new QProcess; + d->process->setWorkingDirectory(d->workingDir.absolutePath()); + QStringList args; + args << QLatin1String( "-" ) << QLatin1String( "-r" ) << d->filesToArchive; + connect(d->process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(processError(QProcess::ProcessError))); + connect(d->process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(processFinished(int,QProcess::ExitStatus))); + connect(d->process, SIGNAL(readyReadStandardOutput()), this, SLOT(processReadyReadStandardOutput())); + + d->process->start(QLatin1String("zip"), args); + if (!d->process->waitForStarted()) { + //TODO handle + } + + if (!d->process->waitForFinished(INT_MAX)) { + //TODO handle + } + + delete d->process; + d->process = 0; + // emit result +} + +void ZipJob::processError(QProcess::ProcessError) +{ + emit error(); +} + +void ZipJob::processFinished(int, QProcess::ExitStatus) +{ + emit finished(); +} + +void ZipJob::processReadyReadStandardOutput() +{ + const QByteArray buf = d->process->readAll(); + const qint64 toWrite = buf.size(); + qint64 written = 0; + while (written < toWrite) { + const qint64 num = d->outputDevice->write(buf.constData() + written, toWrite - written); + if (num < 0) { + //TODO: handle error + return; + } + written += num; + } +} + +void ZipJob::setOutputDevice(QIODevice *device) +{ + d->outputDevice = device; +} + +void ZipJob::setWorkingDirectory(const QDir &dir) +{ + d->workingDir = dir; +} + +void ZipJob::setFilesToArchive(const QStringList &files) +{ + d->filesToArchive = files; +} + +class UnzipJob::Private +{ +public: + Private() : inputDevice(0) {} + + QIODevice *inputDevice; + QString outputPath; + QStringList filesToExtract; +}; + +UnzipJob::UnzipJob() + : d(new Private()) +{ + qRegisterMetaType<QProcess::ExitStatus>(); +} + +UnzipJob::~UnzipJob() +{ + delete d; +} + +void UnzipJob::setInputDevice(QIODevice *device) +{ + d->inputDevice = device; +} + +void UnzipJob::setOutputPath(const QString &path) +{ + d->outputPath = path; +} + +void UnzipJob::processError(QProcess::ProcessError) +{ + emit error(); +} + +void UnzipJob::run() +{ + QProcess process; + // TODO: this won't work on Windows... grmpfl, but on Mac and Linux, at least... + QStringList args; + args << QLatin1String( "/dev/stdin" ); + if (!d->filesToExtract.isEmpty()) + args << QLatin1String("-x") << d->filesToExtract; + process.setWorkingDirectory(d->outputPath); + process.start(QLatin1String("unzip"), args); + connect(&process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(processError(QProcess::ProcessError))); + connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(processFinished(int, QProcess::ExitStatus ))); + if (!process.waitForStarted()) { + // TODO handle + return; + } + + const int bufferSize = 4096; + QByteArray buffer; + while (d->inputDevice->bytesAvailable() > 0 || d->inputDevice->waitForReadyRead(INT_MAX)) { + buffer = d->inputDevice->read(bufferSize); + process.write(buffer); + process.waitForBytesWritten(INT_MAX); + } + process.closeWriteChannel(); + + if (!process.waitForFinished(INT_MAX)) { + // TODO handle + } +} + +void UnzipJob::processFinished(int, QProcess::ExitStatus) +{ + emit finished(); +} + +void UnzipJob::setFilesToExtract(const QStringList &files) +{ + d->filesToExtract = files; +} diff --git a/src/libs/installer/zipjob.h b/src/libs/installer/zipjob.h new file mode 100644 index 000000000..72ab40796 --- /dev/null +++ b/src/libs/installer/zipjob.h @@ -0,0 +1,100 @@ +/************************************************************************** +** +** This file is part of Installer Framework +** +** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef ZIPJOB_H +#define ZIPJOB_H + +#include <QProcess> +#include <QRunnable> + +QT_BEGIN_NAMESPACE +class QDir; +class QIODevice; +class QStringList; +QT_END_NAMESPACE + +class ZipJob : public QObject, public QRunnable +{ + Q_OBJECT + +public: + ZipJob(); + ~ZipJob(); + + void setOutputDevice(QIODevice *device); + void setWorkingDirectory(const QDir &dir); + void setFilesToArchive(const QStringList &files); + + void run(); + +Q_SIGNALS: + void finished(); + void error(); + +private Q_SLOTS: + void processError(QProcess::ProcessError); + void processFinished(int, QProcess::ExitStatus); + void processReadyReadStandardOutput(); + +private: + class Private; + Private *const d; +}; + +class UnzipJob : public QObject, public QRunnable +{ + Q_OBJECT + +public: + UnzipJob(); + ~UnzipJob(); + + void setInputDevice(QIODevice *device); + void setOutputPath(const QString &path); + void setFilesToExtract(const QStringList &files); + + void run(); + +Q_SIGNALS: + void finished(); + void error(); + +private Q_SLOTS: + void processError(QProcess::ProcessError); + void processFinished(int, QProcess::ExitStatus); + +private: + class Private; + Private *const d; +}; + +#endif // ZIPJOB_H |