diff options
Diffstat (limited to 'installerbuilder/libinstaller/kdtools/KDUpdater/kdupdaterupdateinstaller.cpp')
-rw-r--r-- | installerbuilder/libinstaller/kdtools/KDUpdater/kdupdaterupdateinstaller.cpp | 481 |
1 files changed, 481 insertions, 0 deletions
diff --git a/installerbuilder/libinstaller/kdtools/KDUpdater/kdupdaterupdateinstaller.cpp b/installerbuilder/libinstaller/kdtools/KDUpdater/kdupdaterupdateinstaller.cpp new file mode 100644 index 000000000..3b8d2dbd4 --- /dev/null +++ b/installerbuilder/libinstaller/kdtools/KDUpdater/kdupdaterupdateinstaller.cpp @@ -0,0 +1,481 @@ +/**************************************************************************** +** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. +** +** This file is part of the KD Tools library. +** +** Licensees holding valid commercial KD Tools licenses may use this file in +** accordance with the KD Tools Commercial License Agreement provided with +** the Software. +** +** +** This file may be distributed and/or modified under the terms of the +** GNU Lesser General Public License version 2 and version 3 as published by the +** Free Software Foundation and appearing in the file LICENSE.LGPL included. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** Contact info@kdab.com if any conditions of this licensing are not +** clear to you. +** +**********************************************************************/ + +#include "kdupdaterupdateinstaller.h" +#include "kdupdaterpackagesinfo.h" +#include "kdupdaterapplication.h" +#include "kdupdaterupdate.h" +#include "kdupdaterupdateoperationfactory.h" +#include "kdupdaterupdateoperation.h" +#include "kdupdaterufuncompressor_p.h" + +#include <QCoreApplication> +#include <QFileInfo> +#include <QDir> +#include <QDomDocument> +#include <QDomElement> +#include <QDate> + +/*! + \ingroup kdupdater + \class KDUpdater::UpdateInstaller kdupdaterupdateinstaller.h KDUpdaterUpdateInstaller + \brief Installs updates, given a list of \ref KDUpdater::Update objects + + This class installs updates, given a list of \ref KDUpdater::Update objects via the + \ref installUpdates() method. To install the updates this class performs the following + for each update + + \li Downloads the update files from its source + \li Unpacks update files into a temporary directory + \li Parses and executes UpdateInstructions.xml by making use of \ref KDUpdater::UpdateOperation + objects sourced via \ref KDUpdater::UpdateOperationFactory + + \note All temporary files created during the installation of the update will be destroyed + immediately after the installation is complete. +*/ +class KDUpdater::UpdateInstaller::Private +{ + public: + Private( UpdateInstaller* qq ) : + q( qq ) + {} + + UpdateInstaller* q; + + KDUpdater::Application* application; + int updateDownloadDoneCount; + int updateDownloadProgress; + int totalUpdates; + QStringList toRemoveDirs; + + int totalProgressPc; + int currentProgressPc; + QList<KDUpdater::Update*> updates; + + void resolveArguments(QStringList& args); + + void removeDirectory(const QDir & dir); + void slotUpdateDownloadProgress(int percent); + void slotUpdateDownloadDone(); +}; + +// next two are duplicated from kdupdaterupdatefinder.cpp: + +static int computeProgressPercentage(int min, int max, int percent) +{ + return min + qint64(max-min) * percent / 100 ; +} + +static int computePercent(int done, int total) +{ + return total ? done * Q_INT64_C(100) / total : 0 ; +} + +/*! + Constructs an instance of this class for the \ref KDUpdater::Application passed as + parameter. Only updates meant for the specified application will be installed by this class. +*/ +KDUpdater::UpdateInstaller::UpdateInstaller(KDUpdater::Application* application) + : KDUpdater::Task(QLatin1String( "UpdateInstaller" ), NoCapability, application), + d( new Private( this ) ) +{ + d->application = application; +} + +/*! + Destructor +*/ +KDUpdater::UpdateInstaller::~UpdateInstaller() +{ + delete d; +} + +/*! + Returns the update application for which the update is being installed +*/ +KDUpdater::Application* KDUpdater::UpdateInstaller::application() const +{ + return d->application; +} + +/*! + Use this function to let the installer know what updates are to be installed. The + updates are actually installed when the \ref start() method is called on this class. +*/ +void KDUpdater::UpdateInstaller::setUpdatesToInstall(const QList<KDUpdater::Update*>& updates) +{ + d->updates = updates; +} + +/*! + Returns the updates that would be installed when the next time \ref the start) method is called. +*/ +QList<KDUpdater::Update*> KDUpdater::UpdateInstaller::updatesToInstall() const +{ + return d->updates; +} + +/*! + \internal +*/ +void KDUpdater::UpdateInstaller::doRun() +{ + QList<KDUpdater::Update*>& updates = d->updates; + + // First download all the updates + d->updateDownloadDoneCount = 0; + d->totalUpdates = updates.count(); + + for(int i=0; i<updates.count(); i++) + { + KDUpdater::Update* update = updates[i]; + if( update->application() != d->application ) + continue; + + update->setProperty("_ProgressPc_", 0); + connect(update, SIGNAL(progressValue(int)), this, SLOT(slotUpdateDownloadProgress(int))); + connect(update, SIGNAL(finished()), this, SLOT(slotUpdateDownloadDone())); + connect(update, SIGNAL(stopped()), this, SLOT(slotUpdateDownloadDone())); + update->download(); + } + + d->totalProgressPc = updates.count() * 100; + d->currentProgressPc = 0; + + // Wait until all updates have been downloaded + while(d->updateDownloadDoneCount != updates.count()) + { + QCoreApplication::processEvents(); + + // Normalized progress + int progressPc = computePercent(d->currentProgressPc, d->totalProgressPc); + + // Bring the progress to within 50 percent + progressPc = (progressPc>>1); + + // Report the progress + reportProgress(progressPc, tr("Downloading updates...")); + } + + // Global progress + reportProgress(50, tr("Updates downloaded...")); + + // Save the current working directory of the application + QDir oldCWD = QDir::current(); + + int pcDiff = computePercent(1, updates.count()); + pcDiff = computeProgressPercentage(50, 95, pcDiff) - 50; + + // Now install one update after another. + for(int i=0; i<updates.count(); i++) + { + KDUpdater::Update* update = updates[i]; + + // Global progress + QString msg = tr("Installing %1..").arg(update->name()); + int minPc = pcDiff*i + 50; + int maxPc = minPc + pcDiff; + reportProgress(minPc, msg); + + if( update->application() != d->application ) + continue; + + QDir::setCurrent(oldCWD.absolutePath()); + if (!installUpdate(update, minPc, maxPc)) { + d->application->packagesInfo()->writeToDisk(); + return; + } + } + + d->application->packagesInfo()->writeToDisk(); + + // Global progress + reportProgress(95, tr("Finished installing updates. Now removing temporary files and directories..")); + + // Restore the current working directory of the application + QDir::setCurrent(oldCWD.absolutePath()); + + // Remove all the toRemoveDirs + for(int i=0; i<d->toRemoveDirs.count(); i++) + { + QDir dir( d->toRemoveDirs[i] ); + d->removeDirectory( dir ); + + QString dirName = dir.dirName(); + dir.cdUp(); + dir.rmdir( dirName ); + } + d->toRemoveDirs.clear(); + + // Global progress + reportProgress(100, tr("Removed temporary files and directories")); + reportDone(); +} + +/*! + \internal +*/ +bool KDUpdater::UpdateInstaller::doStop() +{ + return false; +} + +/*! + \internal +*/ +bool KDUpdater::UpdateInstaller::doPause() +{ + return false; +} + +/*! + \internal +*/ +bool KDUpdater::UpdateInstaller::doResume() +{ + return false; +} + +bool KDUpdater::UpdateInstaller::installUpdate(KDUpdater::Update* update, int minPc, int maxPc) +{ + QString updateName( update->name() ); + + // Sanity checks + if( !update->isDownloaded() ) + { + QString msg = tr("Could not download update '%1'").arg(update->name()); + reportError(msg); + return false; + } + + // Step 1: Prepare a directory into which the UpdateFile will be unpacked. + // If update file is C:/Users/PRASHA~1/AppData/Local/Temp/qt_temp.Hp1204 and + // the applicationn name "MyApplication" + // Then the directory would be %USERDIR%/AppData/Local/Temp/MyApplication_Update1 + static int count = 0; + QString dirName = QString::fromLatin1("%1_Update%2").arg(d->application->applicationName(), QString::number(count++)); + QString updateFile = update->downloadedFileName(); + QFileInfo fi(updateFile); + QDir dir( fi.absolutePath() ); + dir.mkdir( dirName ); + dir.cd( dirName ); + d->toRemoveDirs << dir.absolutePath(); + + // Step 2: Unpack the update file into the update directory + KDUpdater::UFUncompressor uncompressor; + uncompressor.setFileName( updateFile ); + uncompressor.setDestination( dir.absolutePath() ); + + if (!uncompressor.uncompress()) { + reportError(tr("Couldn't uncompress update: %1") + .arg(uncompressor.errorString())); + return false; + } + + // Step 3: Find out the directory in which UpdateInstructions.xml can be found + QDir updateDir = dir; + while( !updateDir.exists(QLatin1String( "UpdateInstructions.xml" )) ) + { + QString path = updateDir.absolutePath(); + QFileInfoList fiList = updateDir.entryInfoList(QDir::Dirs|QDir::NoDotAndDotDot); + if( !fiList.count() ) // || fiList.count() >= 2 ) + { + QString msg = tr("Could not find UpdateInstructions.xml for %1").arg(update->name()); + reportError(msg); + return false; + } + + updateDir.cd(fiList.first().fileName()); + } + + // Set the application's current working directory as updateDir + QDir::setCurrent(updateDir.absolutePath()); + + // Step 4: Now load the UpdateInstructions.xml file + QDomDocument doc; + QFile file(updateDir.absoluteFilePath(QLatin1String( "UpdateInstructions.xml" ))); + if( !file.open(QFile::ReadOnly) ) + { + QString msg = tr("Could not read UpdateInstructions.xml of %1").arg(update->name()); + reportError(msg); + return false; + } + if( !doc.setContent(&file) ) + { + QString msg = tr("Could not read UpdateInstructions.xml of %1").arg(update->name()); + reportError(msg); + return false; + } + + // Now parse and execute update operations + QDomNodeList operEList = doc.elementsByTagName(QLatin1String( "UpdateOperation" )); + QString msg = tr("Installing %1").arg(updateName); + + for(int i=0; i<operEList.count(); i++) + { + int pc = computePercent(i+1, operEList.count()); + pc = computeProgressPercentage(minPc, maxPc, pc); + reportProgress(pc, msg); + + // Fetch the important XML elements in UpdateOperation + QDomElement operE = operEList.at(i).toElement(); + QDomElement nameE = operE.firstChildElement(QLatin1String( "Name" )); + QDomElement errorE = operE.firstChildElement(QLatin1String( "OnError" )); + QDomElement argE = operE.firstChildElement(QLatin1String( "Arg" )); + + // Figure out information about the update operation to perform + QString operName = nameE.text(); + QString onError = errorE.attribute(QLatin1String( "Action" ), QLatin1String( "Abort" ) ); + QStringList args; + while( !argE.isNull() ) + { + args << argE.text(); + argE = argE.nextSiblingElement(QLatin1String( "Arg" )); + } + + //QString operSignature = QString::fromLatin1("%1(%2)").arg(operName, args.join( QLatin1String( ", ") ) ); + + // Fetch update operation + KDUpdater::UpdateOperation* const updateOperation = KDUpdater::UpdateOperationFactory::instance().create(operName); + if( !updateOperation ) + { + QString errMsg = tr("Update operation %1 not supported").arg(operName); + reportError(errMsg); + + if( onError == QLatin1String( "Continue" ) ) + continue; + + if( onError == QLatin1String( "Abort" ) ) + return false; + + if( onError == QLatin1String( "AskUser" ) ) + { + // TODO: + continue; + } + } + + // Now resolve special fields in arguments + d->resolveArguments(args); + + // Now set the arguments to the update operation and execute the update operation + updateOperation->setArguments(args); + updateOperation->setApplication( d->application ); + const bool success = updateOperation->performOperation(); + + updateOperation->clear(); + + if( !success ) + { + QString errMsg = tr("Cannot execute '%1'").arg(updateOperation->operationCommand()); + reportError(errMsg); + + if( onError == QLatin1String( "Continue" ) ) + continue; + + if( onError == QLatin1String( "Abort" ) ) + return false; + + if( onError == QLatin1String( "AskUser" ) ) + { + // TODO: + continue; + } + } + delete updateOperation; + } + + Q_FOREACH( UpdateOperation* updateOperation, update->operations() ) { + updateOperation->performOperation(); + } + + msg = tr("Finished installing update %1").arg(update->name()); + reportProgress(maxPc, msg); + return true; +} + +/*! + \internal +*/ +void KDUpdater::UpdateInstaller::Private::slotUpdateDownloadProgress(int percent) +{ + // 0-49 percent progress is dedicated for the download of updates + KDUpdater::Update* update = qobject_cast<KDUpdater::Update*>(q->sender()); + if( !update ) + return; + + int oldPc = update->property("_ProgressPc_").toInt(); + int diffPc = percent-oldPc; + if(diffPc <= 0) + return; + + currentProgressPc += diffPc; + update->setProperty("_ProgressPc_", percent); +} + +/*! + \internal +*/ +void KDUpdater::UpdateInstaller::Private::slotUpdateDownloadDone() +{ + ++updateDownloadDoneCount; +} + +void KDUpdater::UpdateInstaller::Private::resolveArguments(QStringList& args) +{ + for(int i=0; i<args.count(); i++) + { + QString arg = args[i]; + + arg = arg.replace(QLatin1String( "{APPDIR}" ), application->applicationDirectory()); + arg = arg.replace(QLatin1String( "{HOME}" ), QDir::homePath()); + arg = arg.replace(QLatin1String( "{APPNAME}" ), application->applicationName()); + arg = arg.replace(QLatin1String( "{APPVERSION}" ), application->applicationVersion()); + arg = arg.replace(QLatin1String( "{CURPATH}" ), QDir::currentPath()); + arg = arg.replace(QLatin1String( "{ROOT}" ), QDir::rootPath()); + arg = arg.replace(QLatin1String( "{TEMP}" ), QDir::tempPath()); + + args[i] = arg; + } +} + +void KDUpdater::UpdateInstaller::Private::removeDirectory(const QDir & dir) +{ + QFileInfoList fiList = dir.entryInfoList(QDir::Dirs|QDir::Files|QDir::NoDotAndDotDot); + if( !fiList.count() ) + return; + + for(int i=0; i<fiList.count(); i++) + { + QFileInfo fi = fiList[i]; + if( fi.isDir() ) + { + QDir childDir = fi.absoluteFilePath(); + removeDirectory( childDir ); + dir.rmdir( childDir.dirName() ); + } + else if( fi.isFile() ) + QFile::remove( fi.absoluteFilePath() ); + } +} + +#include "moc_kdupdaterupdateinstaller.cpp" |