diff options
Diffstat (limited to 'src/libs/installer/packagemanagercore.cpp')
-rw-r--r-- | src/libs/installer/packagemanagercore.cpp | 1845 |
1 files changed, 1845 insertions, 0 deletions
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; +} |