2011-04-13 11:30:05 +02:00

1814 lines
61 KiB
C++

/**************************************************************************
**
** This file is part of Qt SDK**
**
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).*
**
** Contact: Nokia Corporation qt-info@nokia.com**
**
** No Commercial Usage
**
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** 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).
**
**************************************************************************/
#include "qinstaller.h"
#include "adminauthorization.h"
#include "common/binaryformat.h"
#include "common/errors.h"
#include "common/installersettings.h"
#include "common/utils.h"
#include "downloadarchivesjob.h"
#include "fsengineclient.h"
#include "getrepositoriesmetainfojob.h"
#include "messageboxhandler.h"
#include "progresscoordinator.h"
#include "qinstaller_p.h"
#include "qinstallercomponent.h"
#include "qinstallerglobal.h"
#include <QtCore/QTemporaryFile>
#include <QtGui/QDesktopServices>
#include <QtScript/QScriptEngine>
#include <QtScript/QScriptContext>
#include <KDToolsCore/KDSysInfo>
#ifdef Q_OS_WIN
#include "qt_windows.h"
#endif
using namespace QInstaller;
static QFont s_virtualComponentsFont;
static bool s_virtualComponentsVisible = 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();
}
/*!
Appends \a comp preceded by its dependencies to \a components. Makes sure components contains
every component only once.
\internal
*/
static void appendComponentAndMissingDependencies(QList<Component*>& components, Component* comp)
{
if (comp == 0)
return;
const QList<Component*> deps = comp->installer()->missingDependencies(comp);
foreach (Component *component, deps)
appendComponentAndMissingDependencies(components, component);
if (!components.contains(comp))
components.push_back(comp);
}
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;
return Installer::versionMatches(component->value(QLatin1String("Version")), version);
}
static Component* subComponentByName(const Installer *installer, const QString &name,
const QString &version = QString(), Component *check = 0)
{
if (check != 0 && componentMatches(check, name, version))
return check;
const QList<Component*> rootComponents = check == 0 ? installer->components(false, AllMode)
: check->childComponents(false, AllMode);
foreach (QInstaller::Component* component, rootComponents) {
Component* const result = subComponentByName(installer, name, version, component);
if (result != 0)
return result;
}
const QList<Component*> updaterComponents = check == 0
? installer->components(false, UpdaterMode) : check->childComponents(false, UpdaterMode);
foreach (QInstaller::Component* component, updaterComponents) {
Component* const result = subComponentByName(installer, name, version, component);
if (result != 0)
return result;
}
return 0;
}
/*!
Scriptable version of Installer::componentByName(QString).
\sa Installer::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
Installer* const installer = dynamic_cast< Installer* >(engine->globalObject()
.property(QLatin1String("installer")).toQObject());
const QString name = context->argument(0).toString();
Component* const c = installer->componentByName(name);
return engine->newQObject(c);
}
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 errorString(QLatin1String("%1 %2\n%3"));
QString errorString(QLatin1String("\t\t%1\n%2"));
//if (!context.isEmpty())
// errorString.prepend(context + QLatin1String(": "));
//usually the linenumber is in the backtrace
errorString = errorString.arg(/*QString::number(scriptEngine->uncaughtExceptionLineNumber()),*/
scriptEngine->uncaughtException().toString(),
scriptEngine->uncaughtExceptionBacktrace().join(QLatin1String("\n")));
return errorString;
}
/*!
\class QInstaller::Installer
Installer forms the core of the installation and uninstallation system.
*/
/*!
\enum QInstaller::Installer::WizardPage
WizardPage is used to number the different pages known to the Installer GUI.
*/
/*!
\var QInstaller::Installer::Introduction
Introduction page.
*/
/*!
\var QInstaller::Installer::LicenseCheck
License check page
*/
/*!
\var QInstaller::Installer::TargetDirectory
Target directory selection page
*/
/*!
\var QInstaller::Installer::ComponentSelection
%Component selection page
*/
/*!
\var QInstaller::Installer::StartMenuSelection
Start menu directory selection page - Microsoft Windows only
*/
/*!
\var QInstaller::Installer::ReadyForInstallation
"Ready for Installation" page
*/
/*!
\var QInstaller::Installer::PerformInstallation
Page shown while performing the installation
*/
/*!
\var QInstaller::Installer::InstallationFinished
Page shown when the installation was finished
*/
/*!
\var QInstaller::Installer::End
Non-existing page - this value has to be used if you want to insert a page after \a InstallationFinished
*/
KDUpdater::Application& Installer::updaterApplication() const
{
return *d->m_app;
}
void Installer::setUpdaterApplication(KDUpdater::Application *app)
{
d->m_app = app;
}
void Installer::writeUninstaller()
{
if (d->m_needToWriteUninstaller) {
bool error = false;
QString errorMsg;
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_app->packagesInfo()->writeToDisk();
if (gainedAdminRights)
dropAdminRights();
d->m_needToWriteUninstaller = false;
} catch (const Error& e) {
error = true;
errorMsg = e.message();
}
if (error) {
MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(),
QLatin1String("WriteError"), tr("Error writing Uninstaller"), errorMsg,
QMessageBox::Ok, QMessageBox::Ok);
}
}
}
void Installer::reset(const QHash<QString, QString> &params)
{
d->m_completeUninstall = false;
d->m_forceRestart = false;
d->m_status = Installer::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 Installer::setCompleteUninstallation(bool complete)
{
d->m_completeUninstall = complete;
d->m_packageManagingMode = !d->m_completeUninstall;
}
void Installer::autoAcceptMessageBoxes()
{
MessageBoxHandler::instance()->setDefaultAction(MessageBoxHandler::Accept);
}
void Installer::autoRejectMessageBoxes()
{
MessageBoxHandler::instance()->setDefaultAction(MessageBoxHandler::Reject);
}
void Installer::setMessageBoxAutomaticAnswer(const QString &identifier, int button)
{
MessageBoxHandler::instance()->setAutomaticAnswer(identifier,
static_cast<QMessageBox::Button>(button));
}
// TODO: figure out why we have this function at all
void Installer::installSelectedComponents()
{
d->setStatus(Installer::Running);
// download
double downloadPartProgressSize = double(1)/3;
double componentsInstallPartProgressSize = double(2)/3;
// get the list of packages we need to install in proper order and do it for the updater
// TODO: why only updater mode???
const int downloadedArchivesCount = downloadNeededArchives(UpdaterMode, downloadPartProgressSize);
//if there was no download we have the whole progress for installing components
if (!downloadedArchivesCount) {
//componentsInstallPartProgressSize + downloadPartProgressSize;
componentsInstallPartProgressSize = double(1);
}
// get the list of packages we need to install in proper order
const QList<Component*> components = componentsToInstall(runMode());
if (!isInstaller() && !QFileInfo(installerBinaryPath()).isWritable())
gainAdminRights();
d->stopProcessesForUpdates(components);
int progressOperationCount = d->countProgressOperations(components);
double progressOperationSize = componentsInstallPartProgressSize / progressOperationCount;
// TODO: devide this in undo steps and install steps (2 "for" loops) for better progress calculation
foreach (Component* const currentComponent, components) {
if (d->statusCanceledOrFailed())
throw Error(tr("Installation canceled by user"));
ProgressCoordninator::instance()->emitLabelAndDetailTextChanged(tr("\nRemoving the old "
"version of: %1").arg(currentComponent->name()));
if ((isUpdater() || isPackageManager()) && currentComponent->removeBeforeUpdate()) {
QString replacesAsString = currentComponent->value(QLatin1String("Replaces"));
QStringList possibleNames(replacesAsString.split(QLatin1String(","),
QString::SkipEmptyParts));
possibleNames.append(currentComponent->name());
// undo all operations done by this component upon installation
for (int i = d->m_performedOperationsOld.count() - 1; i >= 0; --i) {
KDUpdater::UpdateOperation* const op = d->m_performedOperationsOld[i];
if (!possibleNames.contains(op->value(QLatin1String("component")).toString()))
continue;
const bool becameAdmin = !d->m_FSEngineClientHandler->isActive()
&& op->value(QLatin1String("admin")).toBool() && gainAdminRights();
InstallerPrivate::performOperationThreaded(op, InstallerPrivate::Undo);
if (becameAdmin)
dropAdminRights();
d->m_performedOperationsOld.remove(i);
delete op;
}
foreach(const QString possilbeName, possibleNames)
d->m_app->packagesInfo()->removePackage(possilbeName);
d->m_app->packagesInfo()->writeToDisk();
}
ProgressCoordninator::instance()->emitLabelAndDetailTextChanged(
tr("\nInstalling the new version of: %1").arg(currentComponent->name()));
installComponent(currentComponent, progressOperationSize);
//commit all operations for this allready updated/installed component
//so an undo during the installComponent function only undos the uncomplete installed one
d->commitSessionOperations();
d->m_needToWriteUninstaller = true;
}
d->setStatus(Installer::Success);
ProgressCoordninator::instance()->emitLabelAndDetailTextChanged(tr("\nUpdate finished!"));
emit updateFinished();
}
quint64 size(QInstaller::Component *component, const QString &value)
{
if (!component->isSelected() || component->isInstalled())
return quint64(0);
return component->value(value).toLongLong();
}
quint64 Installer::requiredDiskSpace() const
{
quint64 result = 0;
const QList<Component*> availableComponents = components(true, runMode());
foreach (QInstaller::Component *component, availableComponents)
result += size(component, QLatin1String("UncompressedSize"));
return result;
}
quint64 Installer::requiredTemporaryDiskSpace() const
{
quint64 result = 0;
const QList<Component*> availableComponents = components(true, runMode());
foreach (QInstaller::Component *component, availableComponents)
result += size(component, QLatin1String("CompressedSize"));
return result;
}
/*!
Returns the will be downloaded archives count
*/
int Installer::downloadNeededArchives(RunMode runMode, double partProgressSize)
{
Q_ASSERT(partProgressSize >= 0 && partProgressSize <= 1);
QList<QPair<QString, QString> > archivesToDownload;
QList<Component*> neededComponents = componentsToInstall(runMode);
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;
ProgressCoordninator::instance()->emitLabelAndDetailTextChanged(tr("\nDownloading packages..."));
// don't have it on the stack, since it keeps the temporary files
DownloadArchivesJob* const archivesJob =
new DownloadArchivesJob(d->m_installerSettings->publicKey(), this);
archivesJob->setArchivesToDownload(archivesToDownload);
archivesJob->setAutoDelete(false);
connect(archivesJob, SIGNAL(outputTextChanged(QString)), ProgressCoordninator::instance(),
SLOT(emitLabelAndDetailTextChanged(QString)));
ProgressCoordninator::instance()->registerPartProgress(archivesJob,
SIGNAL(progressChanged(double)), partProgressSize);
connect(this, SIGNAL(installationInterrupted()), archivesJob, SLOT(cancel()));
archivesJob->start();
archivesJob->waitForFinished();
if (archivesJob->error() == KDJob::Canceled)
interrupt();
else if (archivesJob->error() != DownloadArchivesJob::NoError)
throw Error(archivesJob->errorString());
if (d->statusCanceledOrFailed())
throw Error(tr("Installation canceled by user"));
return archivesToDownload.count();
}
void Installer::installComponent(Component *component, double progressOperationSize)
{
Q_ASSERT(progressOperationSize);
d->setStatus(Installer::Running);
const QList<KDUpdater::UpdateOperation*> operations = component->operations();
// show only component which are doing something, MinimumProgress is only for progress
// calculation safeness
if (operations.count() > 1
|| (operations.count() == 1 && operations.at(0)->name() != QLatin1String("MinimumProgress"))) {
ProgressCoordninator::instance()->emitLabelAndDetailTextChanged(tr("\nInstalling component %1")
.arg(component->displayName()));
}
if (!component->operationsCreatedSuccessfully())
setCanceled();
QList<KDUpdater::UpdateOperation*>::const_iterator op;
for (op = operations.begin(); op != operations.end(); ++op) {
if (d->statusCanceledOrFailed())
throw Error(tr("Installation canceled by user"));
KDUpdater::UpdateOperation* const operation = *op;
d->connectOperationToInstaller(operation, progressOperationSize);
// maybe this operations wants us to be admin...
const bool becameAdmin = !d->m_FSEngineClientHandler->isActive()
&& operation->value(QLatin1String("admin")).toBool() && gainAdminRights();
// perform the operation
if (becameAdmin)
verbose() << operation->name() << " as admin: " << becameAdmin << std::endl;
// allow the operation to backup stuff before performing the operation
InstallerPrivate::performOperationThreaded(operation, InstallerPrivate::Backup);
bool ignoreError = false;
bool ok = InstallerPrivate::performOperationThreaded(operation);
while (!ok && !ignoreError && status() != Installer::Canceled) {
verbose() << QString(QLatin1String("operation '%1' with arguments: '%2' failed: %3"))
.arg(operation->name(), operation->arguments().join(QLatin1String("; ")),
operation->errorString()) << std::endl;;
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 = InstallerPrivate::performOperationThreaded(operation);
else if (button == QMessageBox::Ignore)
ignoreError = true;
else if (button == QMessageBox::Cancel)
interrupt();
}
if (ok || operation->error() > KDUpdater::UpdateOperation::InvalidArguments) {
// remember that the operation was performed what allows us to undo it if a
// following operaton fails or if this operation failed but still needs
// an undo call to cleanup.
d->addPerformed(operation);
operation->setValue(QLatin1String("component"), component->name());
}
if (becameAdmin)
dropAdminRights();
if (!ok && !ignoreError)
throw Error(operation->errorString());
if (component->value(QLatin1String("Important"), QLatin1String("false")) == QLatin1String("true"))
d->m_forceRestart = true;
}
d->registerPathesForUninstallation(component->pathesForUninstallation(), component->name());
if (!component->stopProcessForUpdateRequests().isEmpty()) {
KDUpdater::UpdateOperation *stopProcessForUpdatesOp =
KDUpdater::UpdateOperationFactory::instance().create(QLatin1String("FakeStopProcessForUpdate"));
const QStringList arguments(component->stopProcessForUpdateRequests().join(QLatin1String(",")));
stopProcessForUpdatesOp->setArguments(arguments);
d->addPerformed(stopProcessForUpdatesOp);
stopProcessForUpdatesOp->setValue(QLatin1String("component"), component->name());
}
// now mark the component as installed
KDUpdater::PackagesInfo* const packages = d->m_app->packagesInfo();
const bool forcedInstall =
component->value(QLatin1String("ForcedInstallation")).toLower() == QLatin1String("true")
? true : false;
const bool virtualComponent =
component->value(QLatin1String ("Virtual")).toLower() == QLatin1String("true") ? true : false;
packages->installPackage(component->value(QLatin1String("Name")),
component->value(QLatin1String("Version")), component->value(QLatin1String("DisplayName")),
component->value(QLatin1String("Description")), component->dependencies(), forcedInstall,
virtualComponent, component->value(QLatin1String ("UncompressedSize")).toULongLong());
component->setInstalled();
component->markAsPerformedInstallation();
}
/*!
If a component marked as important was installed during update
process true is returned.
*/
bool Installer::needsRestart() const
{
return d->m_forceRestart;
}
void Installer::rollBackInstallation()
{
emit titleMessageChanged(tr("Cancelling the Installer"));
// rolling back
//this unregisters all operation progressChanged connects
ProgressCoordninator::instance()->setUndoMode();
int progressOperationCount =
d->countProgressOperations(d->m_performedOperationsCurrentSession.toList());
double progressOperationSize = double(1) / progressOperationCount;
//reregister all the undooperations with the new size to the ProgressCoordninator
foreach (KDUpdater::UpdateOperation* 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) {
ProgressCoordninator::instance()->registerPartProgress(operationObject,
SIGNAL(progressChanged(double)), progressOperationSize);
}
}
}
while (!d->m_performedOperationsCurrentSession.isEmpty()) {
try {
KDUpdater::UpdateOperation* const operation = d->m_performedOperationsCurrentSession.last();
d->m_performedOperationsCurrentSession.pop_back();
const bool becameAdmin = !d->m_FSEngineClientHandler->isActive()
&& operation->value(QLatin1String("admin")).toBool() && gainAdminRights();
InstallerPrivate::performOperationThreaded(operation, InstallerPrivate::Undo);
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()));
}
}
}
bool Installer::isFileExtensionRegistered(const QString& extension) const
{
QSettings settings(QLatin1String("HKEY_CLASSES_ROOT"), QSettings::NativeFormat);
return settings.value(QString::fromLatin1(".%1/Default").arg(extension)).isValid();
}
// -- QInstaller
Installer::Installer(qint64 magicmaker,
const QVector<KDUpdater::UpdateOperation*>& performedOperations)
: d(new InstallerPrivate(this, magicmaker, performedOperations))
{
qRegisterMetaType< QInstaller::Installer::Status >("QInstaller::Installer::Status");
qRegisterMetaType< QInstaller::Installer::WizardPage >("QInstaller::Installer::WizardPage");
d->initialize();
}
Installer::~Installer()
{
if (!isUninstaller() && !(isInstaller() && status() == Installer::Canceled)) {
QDir targetDir(value(QLatin1String("TargetDir")));
QString logFileName = targetDir.absoluteFilePath(value(QLatin1String("LogFileName"),
QLatin1String("InstallationLog.txt")));
QInstaller::VerboseWriter::instance()->setOutputStream(logFileName);
}
d->m_FSEngineClientHandler->setActive(false);
delete d;
}
QFont Installer::virtualComponentsFont()
{
return s_virtualComponentsFont;
}
void Installer::setVirtualComponentsFont(const QFont &font)
{
s_virtualComponentsFont = font;
}
bool Installer::virtualComponentsVisible()
{
return s_virtualComponentsVisible;
}
void Installer::setVirtualComponentsVisible(bool visible)
{
s_virtualComponentsVisible = visible;
}
RunMode Installer::runMode() const
{
return isUpdater() ? UpdaterMode : AllMode;
}
/*!
Returns a hash containing the installed package name and it's associated package information. If
the application is runing in installer mode or the local components file could not be parsed, the
hash is empty.
*/
QHash<QString, KDUpdater::PackageInfo> Installer::localInstalledPackages()
{
QHash<QString, KDUpdater::PackageInfo> installedPackages;
if (!isInstaller()) {
KDUpdater::PackagesInfo &packagesInfo = *d->m_app->packagesInfo();
if (!setAndParseLocalComponentsFile(packagesInfo)) {
verbose() << tr("Could not parse local components xml file: %1")
.arg(d->localComponentsXmlPath());
return installedPackages;
}
packagesInfo.setApplicationName(d->m_installerSettings->applicationName());
packagesInfo.setApplicationVersion(d->m_installerSettings->applicationVersion());
foreach (const KDUpdater::PackageInfo &info, packagesInfo.packageInfos())
installedPackages.insert(info.name, info);
}
return installedPackages;
}
GetRepositoriesMetaInfoJob* Installer::fetchMetaInformation(const QInstaller::InstallerSettings &settings)
{
GetRepositoriesMetaInfoJob *metaInfoJob = new GetRepositoriesMetaInfoJob(settings.publicKey(), false);
if ((isInstaller() && !isOfflineOnly()) || (isUpdater() || isPackageManager()))
metaInfoJob->setRepositories(settings.repositories());
connect (metaInfoJob, SIGNAL(infoMessage(KDJob*, QString)), this,
SIGNAL(metaJobInfoMessage(KDJob*, QString)));
connect (this, SIGNAL(cancelMetaInfoJob()), metaInfoJob, SLOT(doCancel()),
Qt::QueuedConnection);
try {
metaInfoJob->setAutoDelete(false);
metaInfoJob->start();
metaInfoJob->waitForFinished();
} catch (Error &error) {
verbose() << tr("Could not retrieve meta information: %1").arg(error.message()) << std::endl;
}
return metaInfoJob;
}
bool Installer::addUpdateResourcesFrom(GetRepositoriesMetaInfoJob *metaInfoJob, const InstallerSettings &settings,
bool parseChecksum)
{
const QString &appName = settings.applicationName();
const QStringList tempDirs = metaInfoJob->temporaryDirectories();
foreach (const QString &tmpDir, tempDirs) {
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) {
verbose() << tr("Error opening Updates.xml: ") << e.message() << std::endl;
return false;
}
int line = 0;
int column = 0;
QString error;
QDomDocument doc;
if (!doc.setContent(&updatesFile, &error, &line, &column)) {
verbose() << tr("Parse error in File %4 : %1 at line %2 col %3").arg(error,
QString::number(line), QString::number(column), updatesFile.fileName()) << std::endl;
return false;
}
const QDomNode checksum = doc.documentElement().firstChildElement(QLatin1String("Checksum"));
if (!checksum.isNull()) {
const QDomElement checksumElem = checksum.toElement();
setTestChecksum(checksumElem.text().toLower() == QLatin1String("true"));
}
}
d->m_app->addUpdateSource(appName, appName, QString(), QUrl::fromLocalFile(tmpDir), 1);
}
d->m_app->updateSourcesInfo()->setModified(false);
return true;
}
bool Installer::fetchAllPackages()
{
if (isUninstaller() || isUpdater())
return false;
QHash<QString, KDUpdater::PackageInfo> installedPackages = localInstalledPackages();
QScopedPointer <GetRepositoriesMetaInfoJob> metaInfoJob(fetchMetaInformation(*d->m_installerSettings));
if (metaInfoJob->isCanceled() || metaInfoJob->error() != KDJob::NoError) {
verbose() << tr("Could not retrieve components: %1").arg(metaInfoJob->errorString()) << std::endl;
if (isInstaller())
return false;
}
if (!metaInfoJob->temporaryDirectories().isEmpty()) {
if (!addUpdateResourcesFrom(metaInfoJob.data(), *d->m_installerSettings, true)) {
verbose() << tr("Could not add temorary upade source information.") << std::endl;
return false;
}
}
if (d->m_app->updateSourcesInfo()->updateSourceInfoCount() == 0) {
verbose() << tr("Could not find any update source information.") << std::endl;
return false;
}
KDUpdater::UpdateFinder updateFinder(d->m_app);
updateFinder.setUpdateType(KDUpdater::PackageUpdate | KDUpdater::NewPackage);
updateFinder.run();
const QList<KDUpdater::Update*> &packages = updateFinder.updates();
if (packages.isEmpty()) {
verbose() << tr("Could not retrieve components: %1").arg(updateFinder.errorString());
return false;
}
emit startAllComponentsReset();
qDeleteAll(d->m_rootComponents);
d->m_rootComponents.clear();
QMap<QString, QInstaller::Component*> components;
Data data;
data.components = &components;
data.metaInfoJob = metaInfoJob.data();
data.installedPackages = &installedPackages;
foreach (KDUpdater::Update *package, packages) {
QScopedPointer<QInstaller::Component> component(new QInstaller::Component(this));
data.package = package;
component->loadDataFromUpdate(package);
if (updateComponentData(data, component.data())) {
const QString name = component->name();
components.insert(name, component.take());
}
}
// now append all components to their respective parents
QMap<QString, QInstaller::Component*>::const_iterator it;
for (it = components.begin(); it != components.end(); ++it) {
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 (component->parentComponent() == 0)
appendRootComponent(component, AllMode);
}
// now set the checked state for all components without child
for (int i = 0; i < rootComponentCount(AllMode); ++i) {
QList<Component*> children = rootComponent(i, AllMode)->childs();
foreach (Component *child, children) {
if (child->isCheckable() && !child->isTristate()) {
if (child->isInstalled())
child->setCheckState(Qt::Checked);
}
}
}
// after everything is set up, load the scripts
foreach (QInstaller::Component *component, components)
component->loadComponentScript();
emit finishAllComponentsReset();
return true;
}
bool Installer::fetchUpdaterPackages()
{
if (!isUpdater())
return false;
QHash<QString, KDUpdater::PackageInfo> installedPackages = localInstalledPackages();
QScopedPointer <GetRepositoriesMetaInfoJob> metaInfoJob(fetchMetaInformation(*d->m_installerSettings));
if (metaInfoJob->isCanceled() || metaInfoJob->error() != KDJob::NoError) {
verbose() << tr("Could not retrieve updates: %1").arg(metaInfoJob->errorString()) << std::endl;
return false;
}
if (!metaInfoJob->temporaryDirectories().isEmpty()) {
if (!addUpdateResourcesFrom(metaInfoJob.data(), *d->m_installerSettings, true)) {
verbose() << tr("Could not add temorary upade source information.") << std::endl;
return false;
}
}
if (d->m_app->updateSourcesInfo()->updateSourceInfoCount() == 0) {
verbose() << tr("Could not find any update source information.") << std::endl;
return false;
}
KDUpdater::UpdateFinder updateFinder(d->m_app);
updateFinder.setUpdateType(KDUpdater::PackageUpdate | KDUpdater::NewPackage);
updateFinder.run();
const QList<KDUpdater::Update*> &updates = updateFinder.updates();
if (updates.isEmpty()) {
verbose() << tr("Could not retrieve updates: %1").arg(updateFinder.errorString());
return false;
}
emit startUpdaterComponentsReset();
qDeleteAll(d->m_updaterComponents);
d->m_updaterComponents.clear();
qDeleteAll(d->m_updaterComponentsDeps);
d->m_updaterComponentsDeps.clear();
QMap<QString, QInstaller::Component*> components;
Data data;
data.components = &components;
data.metaInfoJob = metaInfoJob.data();
data.installedPackages = &installedPackages;
bool importantUpdates = false;
foreach (KDUpdater::Update *update, updates) {
QScopedPointer<QInstaller::Component> component(new QInstaller::Component(this));
data.package = update;
component->loadDataFromUpdate(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(QLatin1String("NewComponent")).toString();
if (isNew.toLower() != QLatin1String("true"))
continue;
const QString &name = d->m_updaterComponentsDeps.last()->name();
const KDUpdater::PackageInfo &info = installedPackages.value(name);
// Update for not installed package found, skip it.
if (!installedPackages.contains(name))
continue;
const QString updateVersion = update->data(QLatin1String("Version")).toString();
if (KDUpdater::compareVersion(updateVersion, info.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(QLatin1String("ReleaseDate")).toDate();
if (info.lastUpdateDate > updateDate)
continue;
// this is not a dependency, it is a real update
components.insert(name, d->m_updaterComponentsDeps.takeLast());
}
}
// remove all unimportant components
QList<QInstaller::Component*> updaterComponents = components.values();
if (importantUpdates) {
for (int i = updaterComponents.count() - 1; i >= 0; --i) {
const QString important = updaterComponents.at(i)->value(QLatin1String("Important"));
if (important.toLower() == QLatin1String ("false") || important.isEmpty()) {
delete updaterComponents[i];
updaterComponents.removeAt(i);
}
}
}
// append all components w/o parent to the direct list
foreach (QInstaller::Component *component, updaterComponents)
appendRootComponent(component, UpdaterMode);
// after everything is set up, load the scripts
foreach (QInstaller::Component *component, updaterComponents)
component->loadComponentScript();
emit finishUpdaterComponentsReset();
return true;
}
/*!
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::Installer::WizardPage "WizardPage".
*/
bool Installer::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 Installer::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 Installer::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::Installer::WizardPage "WizardPage".
*/
bool Installer::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 Installer::removeWizardPageItem(Component *component, const QString &name)
{
if (QWidget* const widget = component->userInterface(name)) {
emit wizardWidgetRemovalRequested(widget);
return true;
}
return false;
}
/*!
Sets additional repository for this instance of the installer or updater
Will be removed after invoking it again
*/
void Installer::setTemporaryRepositories(const QList<Repository> &repositories, bool replace)
{
d->m_installerSettings->setTemporaryRepositories(repositories, replace);
}
/*!
checks if the downloader should try to download sha1 checksums for archives
*/
bool Installer::testChecksum() const
{
return d->m_testChecksum;
}
/*!
Defines if the downloader should try to download sha1 checksums for archives
*/
void Installer::setTestChecksum(bool test)
{
d->m_testChecksum = test;
}
/*!
Returns the number of components in the list depending on the run mode \a runMode.
*/
int Installer::rootComponentCount(RunMode runMode) const
{
if (runMode == UpdaterMode)
return d->m_updaterComponents.size();
return d->m_rootComponents.size();
}
/*!
Returns the component at index position i in the components list. i must be a valid index
position in the list (i.e., 0 <= i < rootComponentCount(...)).
*/
Component *Installer::rootComponent(int i, RunMode runMode) const
{
if (runMode == UpdaterMode)
return d->m_updaterComponents.value(i, 0);
return d->m_rootComponents.value(i, 0);
}
/*!
Appends a new root components \a component based on the current run mode \a runMode to the
installers internal lists of components.
*/
void Installer::appendRootComponent(Component *component, RunMode runMode)
{
if (runMode == AllMode)
d->m_rootComponents.append(component);
else
d->m_updaterComponents.append(component);
emit componentAdded(component);
}
/*!
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* Installer::componentByName(const QString &name) const
{
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);
}
QList<Component*> Installer::components(bool recursive, RunMode runMode) const
{
if (runMode == UpdaterMode)
return d->m_updaterComponents;
if (!recursive)
return d->m_rootComponents;
QList<Component*> result;
foreach (QInstaller::Component *component, d->m_rootComponents) {
result.push_back(component);
result += component->childComponents(true, runMode);
}
return result;
}
QList<Component*> Installer::componentsToInstall(RunMode runMode) const
{
QList<Component*> availableComponents = components(true, runMode);
std::sort(availableComponents.begin(), availableComponents.end(),
Component::InstallPriorityLessThan());
QList<Component*> componentsToInstall;
foreach (QInstaller::Component *component, availableComponents) {
if (!component->installationRequested())
continue;
appendComponentAndMissingDependencies(componentsToInstall, component);
}
return componentsToInstall;
}
/*!
Returns a list of packages depending on \a component.
*/
QList<Component*> Installer::dependees(const Component *component) const
{
QList<Component*> result;
const QList<Component*> allComponents = components(true, AllMode);
foreach (Component* const c, allComponents) {
const QStringList deps = c->value(QString::fromLatin1("Dependencies"))
.split(QChar::fromLatin1(','), QString::SkipEmptyParts);
const QLatin1Char dash('-');
foreach (const QString &dep, deps) {
// the last part is considered to be the version, then
const QString id = dep.contains(dash) ? dep.section(dash, 0, 0) : dep;
const QString version = dep.contains(dash) ? dep.section(dash, 1) : QString();
if (componentMatches(component, id, version))
result.push_back(c);
}
}
return result;
}
/*!
Returns the list of all missing (not installed) dependencies for \a component.
*/
QList<Component*> Installer::missingDependencies(const Component *component) const
{
QList<Component*> result;
const QStringList deps = component->value(QString::fromLatin1("Dependencies"))
.split(QChar::fromLatin1(','), QString::SkipEmptyParts);
const QLatin1Char dash('-');
for (QStringList::const_iterator it = deps.begin(); it != deps.end(); ++it) {
const bool containsVersionString = it->contains(dash);
const QString version = containsVersionString ? it->section(dash, 1) : QString();
const QString name = containsVersionString ? it->section(dash, 0, 0) : *it;
bool installed = false;
QList<Component*> compList = components(true, AllMode);
foreach (const Component* comp, compList) {
if (!name.isEmpty() && comp->name() == name && !version.isEmpty()) {
if (Installer::versionMatches(comp->value(QLatin1String("InstalledVersion")), version))
installed = true;
} else if (comp->name() == name) {
installed = true;
}
}
compList = components(true, UpdaterMode);
foreach (const Component *comp, d->m_updaterComponents) {
if (!name.isEmpty() && comp->name() == name && !version.isEmpty()) {
if (Installer::versionMatches(comp->value(QLatin1String("InstalledVersion")), version))
installed = true;
} else if (comp->name() == name) {
installed = true;
}
}
if (!installed) {
if (Component *comp = componentByName(name))
result.push_back(comp);
}
}
return result;
}
/*!
Returns a list of dependencies for \a component.
If there's a dependency which cannot be fullfilled, the list contains 0 values.
*/
QList<Component*> Installer::dependencies(const Component *component,
QStringList *missingPackageNames) const
{
QList<Component*> result;
const QStringList deps = component->value(QString::fromLatin1("Dependencies"))
.split(QChar::fromLatin1(','), QString::SkipEmptyParts);
foreach (const QString &name, deps) {
Component* comp = componentByName(name);
if (!comp && missingPackageNames)
missingPackageNames->append(name);
else
result.push_back(comp);
}
return result;
}
InstallerSettings Installer::settings() const
{
return *d->m_installerSettings;
}
/*!
This method tries to gain admin rights. On success, it returns true.
*/
bool Installer::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 Installer::dropAdminRights()
{
d->m_FSEngineClientHandler->setActive(false);
}
/*!
Return true, if a process with \a name is running. On Windows, the comparision
is case-insensitive.
*/
bool Installer::isProcessRunning(const QString &name) const
{
return InstallerPrivate::isProcessRunning(name, KDSysInfo::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> Installer::execute(const QString &program, const QStringList &arguments,
const QString &stdIn) const
{
QProcess p;
p.start(program, arguments, stdIn.isNull() ? QIODevice::ReadOnly : QIODevice::ReadWrite);
if (!p.waitForStarted())
return QList< QVariant >();
if (!stdIn.isNull()) {
p.write(stdIn.toLatin1());
p.closeWriteChannel();
}
QEventLoop loop;
connect(&p, SIGNAL(finished(int, QProcess::ExitStatus)), &loop, SLOT(quit()));
loop.exec();
return QList< QVariant >() << QString::fromLatin1(p.readAllStandardOutput()) << p.exitCode();
}
/*!
Returns an environment variable.
*/
QString Installer::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 performns an operation \a name with \a arguments.
\sa Component::addOperation
*/
bool Installer::performOperation(const QString &name, const QStringList &arguments)
{
QScopedPointer<KDUpdater::UpdateOperation> op(KDUpdater::UpdateOperationFactory::instance()
.create(name));
if (!op.data())
return false;
op->setArguments(arguments);
op->backup();
if (!InstallerPrivate::performOperationThreaded(op.data())) {
InstallerPrivate::performOperationThreaded(op.data(), InstallerPrivate::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 comparaters '>', '>=',
'<', '<=' and '='.
*/
bool Installer::versionMatches(const QString &version, const QString &requirement)
{
QRegExp compEx(QLatin1String("([<=>]+)(.*)"));
const QString comparator = compEx.exactMatch(requirement) ? compEx.cap(1) : QString::fromLatin1("=");
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 pathes.
If \a pathes is empty, it gets filled with platform dependent default pathes.
The resulting path is stored in \a library.
This method can be used by scripts to check external dependencies.
*/
QString Installer::findLibrary(const QString &name, const QStringList &pathes)
{
QStringList findPathes = pathes;
#if defined(Q_WS_WIN)
return findPath(QString::fromLatin1("%1.lib").arg(name), findPathes);
#else
if (findPathes.isEmpty()) {
findPathes.push_back(QLatin1String("/lib"));
findPathes.push_back(QLatin1String("/usr/lib"));
findPathes.push_back(QLatin1String("/usr/local/lib"));
findPathes.push_back(QLatin1String("/opt/local/lib"));
}
#if defined(Q_WS_MAC)
const QString dynamic = findPath(QString::fromLatin1("lib%1.dylib").arg(name), findPathes);
#else
const QString dynamic = findPath(QString::fromLatin1("lib%1.so*").arg(name), findPathes);
#endif
if (!dynamic.isEmpty())
return dynamic;
return findPath(QString::fromLatin1("lib%1.a").arg(name), findPathes);
#endif
}
/*!
Tries to find a file name \a name in one of \a pathes.
The resulting path is stored in \a path.
This method can be used by scripts to check external dependencies.
*/
QString Installer::findPath(const QString &name, const QStringList &pathes)
{
foreach (const QString &path, pathes) {
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 Installer::setInstallerBaseBinary(const QString &path)
{
d->m_forceRestart = true;
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 Installer::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 QSettings registry(filename, QSettings::NativeFormat);
if (!filename.isEmpty() && !regKey.isEmpty() && registry.contains(regKey))
return registry.value(regKey).toString();
}
#else
if (key == QLatin1String("TargetDir")) {
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 Installer::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 Installer::containsValue(const QString &key) const
{
return d->m_vars.contains(key);
}
void Installer::setSharedFlag(const QString &key, bool value)
{
d->m_sharedFlags.insert(key, value);
}
bool Installer::sharedFlag(const QString &key) const
{
return d->m_sharedFlags.value(key, false);
}
bool Installer::isVerbose() const
{
return QInstaller::isVerbose();
}
void Installer::setVerbose(bool on)
{
QInstaller::setVerbose(on);
}
Installer::Status Installer::status() const
{
return Installer::Status(d->m_status);
}
/*!
returns true if at least one complete installation/update
was successfull, even if the user cancelled the newest
installation process.
*/
bool Installer::finishedWithSuccess() const
{
return (d->m_status == Installer::Success) || d->m_needToWriteUninstaller;
}
void Installer::interrupt()
{
verbose() << "INTERRUPT INSTALLER" << std::endl;
d->setStatus(Installer::Canceled);
emit installationInterrupted();
}
void Installer::setCanceled()
{
d->setStatus(Installer::Canceled);
}
/*!
Replaces all variables within \a str by their respective values and
returns the result.
*/
QString Installer::replaceVariables(const QString &str) const
{
return d->replaceVariables(str);
}
/*!
Replaces all variables in any of \a str by their respective values and
returns the results.
\overload
*/
QStringList Installer::replaceVariables(const QStringList &str) const
{
QStringList result;
foreach (const QString &s, str)
result.push_back(d->replaceVariables(s));
return result;
}
/*!
Replaces all variables within \a ba by their respective values and
returns the result.
\overload
*/
QByteArray Installer::replaceVariables(const QByteArray &ba) const
{
return d->replaceVariables(ba);
}
/*!
Returns the path to the installer binary.
*/
QString Installer::installerBinaryPath() const
{
return d->installerBinaryPath();
}
/*!
Returns true when this is the installer running.
*/
bool Installer::isInstaller() const
{
return d->isInstaller();
}
/*!
Returns true if this is an offline-only installer.
*/
bool Installer::isOfflineOnly() const
{
QSettings confInternal(QLatin1String(":/config/config-internal.ini"), QSettings::IniFormat);
return confInternal.value(QLatin1String("offlineOnly")).toBool();
}
void Installer::setUninstaller()
{
d->m_magicBinaryMarker = QInstaller::MagicUninstallerMarker;
}
/*!
Returns true when this is the uninstaller running.
*/
bool Installer::isUninstaller() const
{
return d->isUninstaller();
}
void Installer::setUpdater()
{
d->m_magicBinaryMarker = QInstaller::MagicUpdaterMarker;
}
/*!
Returns true when this is neither an installer nor an uninstaller running.
Must be an updater, then.
*/
bool Installer::isUpdater() const
{
return d->isUpdater();
}
void Installer::setPackageManager()
{
d->m_magicBinaryMarker = QInstaller::MagicPackageManagerMarker;
}
/*!
Returns true when this is the package manager running.
*/
bool Installer::isPackageManager() const
{
return d->isPackageManager();
}
/*!
Runs the installer. Returns true on success, false otherwise.
*/
bool Installer::runInstaller()
{
try {
d->runInstaller();
return true;
} catch (...) {
return false;
}
}
/*!
Runs the uninstaller. Returns true on success, false otherwise.
*/
bool Installer::runUninstaller()
{
try {
d->runUninstaller();
return true;
} catch (...) {
return false;
}
}
/*!
Runs the package updater. Returns true on success, false otherwise.
*/
bool Installer::runPackageUpdater()
{
try {
d->runPackageUpdater();
return true;
} catch (...) {
return false;
}
}
/*!
\internal
Calls languangeChanged on all components.
*/
void Installer::languageChanged()
{
const QList<Component*> comps = components(true, runMode());
foreach (Component* component, comps)
component->languageChanged();
}
/*!
Runs the installer or uninstaller, depending on the type of this binary.
*/
bool Installer::run()
{
try {
if (isInstaller())
d->runInstaller();
else if (isUninstaller())
d->runUninstaller();
else if (isPackageManager())
d->runPackageUpdater();
return true;
} catch (const Error &err) {
verbose() << "Caught Installer Error: " << err.message() << std::endl;
return false;
}
}
/*!
Returns the path name of the ininstaller binary.
*/
QString Installer::uninstallerName() const
{
return d->uninstallerName();
}
bool Installer::setAndParseLocalComponentsFile(KDUpdater::PackagesInfo &packagesInfo)
{
packagesInfo.setFileName(d->localComponentsXmlPath());
const QString localComponentsXml = d->localComponentsXmlPath();
// handle errors occured by loading components.xml
QFileInfo componentFileInfo(localComponentsXml);
int silentRetries = d->m_silentRetries;
while (!componentFileInfo.exists()) {
if (silentRetries > 0) {
--silentRetries;
} else {
Status status = handleComponentsFileSetOrParseError(localComponentsXml);
if (status == Installer::Canceled)
return false;
}
packagesInfo.setFileName(localComponentsXml);
}
silentRetries = d->m_silentRetries;
while (packagesInfo.error() != KDUpdater::PackagesInfo::NoError) {
if (silentRetries > 0) {
--silentRetries;
} else {
Status status = handleComponentsFileSetOrParseError(localComponentsXml);
if (status == Installer::Canceled)
return false;
}
packagesInfo.setFileName(localComponentsXml);
}
silentRetries = d->m_silentRetries;
while (packagesInfo.error() != KDUpdater::PackagesInfo::NoError) {
if (silentRetries > 0) {
--silentRetries;
} else {
bool retry = false;
if (packagesInfo.error() != KDUpdater::PackagesInfo::InvalidContentError
&& packagesInfo.error() != KDUpdater::PackagesInfo::InvalidXmlError) {
retry = true;
}
Status status = handleComponentsFileSetOrParseError(componentFileInfo.fileName(),
packagesInfo.errorString(), retry);
if (status == Installer::Canceled)
return false;
}
packagesInfo.setFileName(localComponentsXml);
}
return true;
}
Installer::Status Installer::handleComponentsFileSetOrParseError(const QString &arg1,
const QString &arg2, bool withRetry)
{
QMessageBox::StandardButtons buttons = QMessageBox::Cancel;
if (withRetry)
buttons |= QMessageBox::Retry;
const QMessageBox::StandardButton button =
MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(),
QLatin1String("Error loading component.xml"), tr("Loading error"),
tr(arg2.isEmpty() ? "Could not load %1" : "Could not load %1 : %2").arg(arg1, arg2),
buttons);
if (button == QMessageBox::Cancel) {
d->m_status = Installer::Failure;
return Installer::Canceled;
}
return Installer::Unfinished;
}
bool Installer::updateComponentData(const struct Data &data, Component *component)
{
try {
const QString name = data.package->data(QLatin1String("Name")).toString();
if (data.components->contains(name)) {
qCritical("Could not register component! Component with identifier %s already registered.",
qPrintable(name));
return false;
}
if (data.installedPackages->contains(name)) {
component->setInstalled();
component->setValue(QLatin1String("InstalledVersion"), data.installedPackages->value(name).version);
} else {
component->setUninstalled();
}
const QString &localPath = component->localTempPath();
if (isVerbose()) {
static QString lastLocalPath;
if (lastLocalPath != localPath)
verbose() << "Url is : " << localPath << std::endl;
lastLocalPath = localPath;
}
component->setRepositoryUrl(data.metaInfoJob->repositoryForTemporaryDirectory(localPath).url());
} catch (...) {
return false;
}
return true;
}