Mess with the updater again.

This commit is contained in:
Petr Mrázek 2014-01-05 00:06:55 +01:00
parent e558584af0
commit b49fa9d2a9
12 changed files with 119 additions and 161 deletions

View File

@ -503,6 +503,8 @@ IF(WIN32)
) )
ENDIF(WIN32) ENDIF(WIN32)
OPTION(MultiMC_UPDATER_DRY_RUN "Enable updater dry-run mode." OFF)
OPTION(MultiMC_CODE_COVERAGE "Compiles for code coverage" OFF) OPTION(MultiMC_CODE_COVERAGE "Compiles for code coverage" OFF)
IF(MultiMC_CODE_COVERAGE) IF(MultiMC_CODE_COVERAGE)
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 --coverage") SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 --coverage")

View File

@ -35,17 +35,6 @@
#include "logger/QsLog.h" #include "logger/QsLog.h"
#include <logger/QsLogDest.h> #include <logger/QsLogDest.h>
#include "config.h"
#ifdef WINDOWS
#define UPDATER_BIN "updater.exe"
#elif LINUX
#define UPDATER_BIN "updater"
#elif OSX
#define UPDATER_BIN "updater"
#else
#error Unsupported operating system.
#endif
using namespace Util::Commandline; using namespace Util::Commandline;
MultiMC::MultiMC(int &argc, char **argv, const QString &data_dir_override) MultiMC::MultiMC(int &argc, char **argv, const QString &data_dir_override)
@ -136,6 +125,11 @@ MultiMC::MultiMC(int &argc, char **argv, const QString &data_dir_override)
adjustedBy += "Command line " + dirParam; adjustedBy += "Command line " + dirParam;
dataPath = dirParam; dataPath = dirParam;
} }
else
{
dataPath = applicationDirPath();
adjustedBy += "Fallback to binary path " + dataPath;
}
if(!ensureFolderPathExists(dataPath) || !QDir::setCurrent(dataPath)) if(!ensureFolderPathExists(dataPath) || !QDir::setCurrent(dataPath))
{ {
// BAD STUFF. WHAT DO? // BAD STUFF. WHAT DO?
@ -150,8 +144,7 @@ MultiMC::MultiMC(int &argc, char **argv, const QString &data_dir_override)
QDir foo(PathCombine(binPath, "..")); QDir foo(PathCombine(binPath, ".."));
rootPath = foo.absolutePath(); rootPath = foo.absolutePath();
#elif defined(Q_OS_WIN32) #elif defined(Q_OS_WIN32)
QDir foo(PathCombine(binPath, "..")); rootPath = binPath;
rootPath = foo.absolutePath();
#elif defined(Q_OS_MAC) #elif defined(Q_OS_MAC)
QDir foo(PathCombine(binPath, "../..")); QDir foo(PathCombine(binPath, "../.."));
rootPath = foo.absolutePath(); rootPath = foo.absolutePath();
@ -341,7 +334,7 @@ void MultiMC::initLogger()
QsLogging::Logger &logger = QsLogging::Logger::instance(); QsLogging::Logger &logger = QsLogging::Logger::instance();
logger.setLoggingLevel(QsLogging::TraceLevel); logger.setLoggingLevel(QsLogging::TraceLevel);
m_fileDestination = QsLogging::DestinationFactory::MakeFileDestination(logBase.arg(0)); m_fileDestination = QsLogging::DestinationFactory::MakeFileDestination(logBase.arg(0));
m_debugDestination = QsLogging::DestinationFactory::MakeDebugOutputDestination(); m_debugDestination = QsLogging::DestinationFactory::MakeQDebugDestination();
logger.addDestination(m_fileDestination.get()); logger.addDestination(m_fileDestination.get());
logger.addDestination(m_debugDestination.get()); logger.addDestination(m_debugDestination.get());
// log all the things // log all the things
@ -468,7 +461,7 @@ void MultiMC::initHttpMetaCache()
m_metacache->addBase("libraries", QDir("libraries").absolutePath()); m_metacache->addBase("libraries", QDir("libraries").absolutePath());
m_metacache->addBase("minecraftforge", QDir("mods/minecraftforge").absolutePath()); m_metacache->addBase("minecraftforge", QDir("mods/minecraftforge").absolutePath());
m_metacache->addBase("skins", QDir("accounts/skins").absolutePath()); m_metacache->addBase("skins", QDir("accounts/skins").absolutePath());
m_metacache->addBase("root", QDir(".").absolutePath()); m_metacache->addBase("root", QDir(root()).absolutePath());
m_metacache->Load(); m_metacache->Load();
} }
@ -520,36 +513,29 @@ std::shared_ptr<JavaVersionList> MultiMC::javalist()
void MultiMC::installUpdates(const QString &updateFilesDir, bool restartOnFinish) void MultiMC::installUpdates(const QString &updateFilesDir, bool restartOnFinish)
{ {
QLOG_INFO() << "Installing updates."; QLOG_INFO() << "Installing updates.";
#if LINUX #ifdef WINDOWS
// On Linux, the MultiMC executable file is actually in the bin folder inside the QString finishCmd = MMC->applicationFilePath();
// installation directory. QString updaterBinary = PathCombine(bin(), "updater.exe");
// This means that MultiMC's *actual* install path is the parent folder. #elif LINUX
// We need to tell the updater to run with this directory as the install path, rather than QString finishCmd = PathCombine(root(), "MultiMC");
// the bin folder where the executable is. QString updaterBinary = PathCombine(bin(), "updater");
// On other operating systems, we'll just use the path to the executable. #elif OSX
QString appDir = QFileInfo(MMC->applicationDirPath()).dir().path(); QString finishCmd = MMC->applicationFilePath();
QString updaterBinary = PathCombine(bin(), "updater");
// On Linux, we also need to set the finish command to the launch script, rather than the #else
// binary. #error Unsupported operating system.
QString finishCmd = PathCombine(appDir, "MultiMC"); #endif
#else
QString appDir = MMC->applicationDirPath();
QString finishCmd = MMC->applicationFilePath();
#endif
// Build the command we'll use to run the updater.
// Note, the above comment about the app dir path on Linux is irrelevant here because the
// updater binary is always in the
// same folder as the main binary.
QString updaterBinary = PathCombine(MMC->applicationDirPath(), UPDATER_BIN);
QStringList args; QStringList args;
// ./updater --install-dir $INSTALL_DIR --package-dir $UPDATEFILES_DIR --script // ./updater --install-dir $INSTALL_DIR --package-dir $UPDATEFILES_DIR --script
// $UPDATEFILES_DIR/file_list.xml --wait $PID --mode main // $UPDATEFILES_DIR/file_list.xml --wait $PID --mode main
args << "--install-dir" << appDir; args << "--install-dir" << root();
args << "--package-dir" << updateFilesDir; args << "--package-dir" << updateFilesDir;
args << "--script" << PathCombine(updateFilesDir, "file_list.xml"); args << "--script" << PathCombine(updateFilesDir, "file_list.xml");
args << "--wait" << QString::number(MMC->applicationPid()); args << "--wait" << QString::number(MMC->applicationPid());
#ifdef MultiMC_UPDATER_DRY_RUN
args << "--dry-run";
#endif
if (restartOnFinish) if (restartOnFinish)
args << "--finish-cmd" << finishCmd; args << "--finish-cmd" << finishCmd;

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "config.h"
#include <QApplication> #include <QApplication>
#include "MultiMCVersion.h" #include "MultiMCVersion.h"
#include <memory> #include <memory>

View File

@ -1,3 +1,5 @@
#pragma once
// Minor and major version, used to communicate changes to users. // Minor and major version, used to communicate changes to users.
#define VERSION_MAJOR @MultiMC_VERSION_MAJOR@ #define VERSION_MAJOR @MultiMC_VERSION_MAJOR@
#define VERSION_MINOR @MultiMC_VERSION_MINOR@ #define VERSION_MINOR @MultiMC_VERSION_MINOR@
@ -16,6 +18,9 @@
// Used for matching notifications // Used for matching notifications
#define FULL_VERSION_STR "@MultiMC_VERSION_MAJOR@.@MultiMC_VERSION_MINOR@.@MultiMC_VERSION_BUILD@" #define FULL_VERSION_STR "@MultiMC_VERSION_MAJOR@.@MultiMC_VERSION_MINOR@.@MultiMC_VERSION_BUILD@"
// enabled for updater dry run
#cmakedefine MultiMC_UPDATER_DRY_RUN
// The commit hash of this build // The commit hash of this build
#define GIT_COMMIT "@MultiMC_GIT_COMMIT@" #define GIT_COMMIT "@MultiMC_GIT_COMMIT@"

View File

@ -402,12 +402,17 @@ DownloadUpdateTask::processFileLists(NetJob *job,
if (isUpdater) if (isUpdater)
{ {
#ifdef MultiMC_UPDATER_DRY_RUN
QLOG_DEBUG() << "Skipping updater download and using local version.";
#else
auto cache_entry = MMC->metacache()->resolveEntry("root", entry.path); auto cache_entry = MMC->metacache()->resolveEntry("root", entry.path);
QLOG_DEBUG() << "Updater will be in " << cache_entry->getFullPath(); QLOG_DEBUG() << "Updater will be in " << cache_entry->getFullPath();
// force check. // force check.
cache_entry->stale = true; cache_entry->stale = true;
auto download = CacheDownload::make(QUrl(source.url), cache_entry); auto download = CacheDownload::make(QUrl(source.url), cache_entry);
job->addNetAction(download); job->addNetAction(download);
#endif
} }
else else
{ {
@ -514,7 +519,6 @@ bool DownloadUpdateTask::fixPathForOSX(QString &path)
{ {
// remove the prefix and add a new, more appropriate one. // remove the prefix and add a new, more appropriate one.
path.remove(0, 12); path.remove(0, 12);
path = QString("../../") + path;
return true; return true;
} }
else else

View File

@ -206,11 +206,9 @@ protected:
* The updater runs in MultiMC.app/Contents/MacOs by default * The updater runs in MultiMC.app/Contents/MacOs by default
* The destination paths are such as this: MultiMC.app/blah/blah * The destination paths are such as this: MultiMC.app/blah/blah
* *
* Therefore we chop off the 'MultiMC.app' prefix and prepend ../.. * Therefore we chop off the 'MultiMC.app' prefix
* *
* Returns false if the path couldn't be fixed (is invalid) * Returns false if the path couldn't be fixed (is invalid)
*
* Has no effect on systems that aren't OSX
*/ */
static bool fixPathForOSX(QString &path); static bool fixPathForOSX(QString &path);

View File

@ -17,7 +17,6 @@
#include "MultiMC.h" #include "MultiMC.h"
#include "config.h"
#include "logger/QsLog.h" #include "logger/QsLog.h"
#include <QJsonObject> #include <QJsonObject>

View File

@ -6,16 +6,6 @@
#include "ProcessUtils.h" #include "ProcessUtils.h"
#include "UpdateObserver.h" #include "UpdateObserver.h"
UpdateInstaller::UpdateInstaller()
: m_mode(Setup)
, m_waitPid(0)
, m_script(0)
, m_observer(0)
, m_forceElevated(false)
, m_autoClose(false)
{
}
void UpdateInstaller::setWaitPid(PLATFORM_PID pid) void UpdateInstaller::setWaitPid(PLATFORM_PID pid)
{ {
m_waitPid = pid; m_waitPid = pid;
@ -69,6 +59,10 @@ std::list<std::string> UpdateInstaller::updaterArgs() const
{ {
args.push_back("--auto-close"); args.push_back("--auto-close");
} }
if (m_dryRun)
{
args.push_back("--dry-run");
}
return args; return args;
} }
@ -255,25 +249,32 @@ void UpdateInstaller::cleanup()
void UpdateInstaller::revert() void UpdateInstaller::revert()
{ {
LOG(Info,"Reverting installation!");
std::map<std::string,std::string>::const_iterator iter = m_backups.begin(); std::map<std::string,std::string>::const_iterator iter = m_backups.begin();
for (;iter != m_backups.end();iter++) for (;iter != m_backups.end();iter++)
{ {
const std::string& installedFile = iter->first; const std::string& installedFile = iter->first;
const std::string& backupFile = iter->second; const std::string& backupFile = iter->second;
LOG(Info,"Restoring " + installedFile);
if (FileUtils::fileExists(installedFile.c_str())) if(!m_dryRun)
{ {
FileUtils::removeFile(installedFile.c_str()); if (FileUtils::fileExists(installedFile.c_str()))
{
FileUtils::removeFile(installedFile.c_str());
}
FileUtils::moveFile(backupFile.c_str(),installedFile.c_str());
} }
FileUtils::moveFile(backupFile.c_str(),installedFile.c_str());
} }
} }
void UpdateInstaller::installFile(const UpdateScriptFile& file) void UpdateInstaller::installFile(const UpdateScriptFile& file)
{ {
std::string sourceFile = file.source;
std::string destPath = file.dest; std::string destPath = file.dest;
std::string target = file.linkTarget; std::string target = file.linkTarget;
LOG(Info,"Installing file " + sourceFile + " to " + destPath);
// backup the existing file if any // backup the existing file if any
backupFile(destPath); backupFile(destPath);
@ -281,22 +282,29 @@ void UpdateInstaller::installFile(const UpdateScriptFile& file)
std::string destDir = FileUtils::dirname(destPath.c_str()); std::string destDir = FileUtils::dirname(destPath.c_str());
if (!FileUtils::fileExists(destDir.c_str())) if (!FileUtils::fileExists(destDir.c_str()))
{ {
FileUtils::mkpath(destDir.c_str()); LOG(Info,"Destination path missing. Creating " + destDir);
if(!m_dryRun)
{
FileUtils::mkpath(destDir.c_str());
}
} }
std::string sourceFile = file.source;
if (!FileUtils::fileExists(sourceFile.c_str())) if (!FileUtils::fileExists(sourceFile.c_str()))
{ {
throw "Source file does not exist: " + sourceFile; throw "Source file does not exist: " + sourceFile;
} }
FileUtils::copyFile(sourceFile.c_str(),destPath.c_str()); if(!m_dryRun)
{
FileUtils::copyFile(sourceFile.c_str(),destPath.c_str());
// set the permissions on the newly extracted file // set the permissions on the newly extracted file
FileUtils::chmod(destPath.c_str(),file.permissions); FileUtils::chmod(destPath.c_str(),file.permissions);
}
} }
void UpdateInstaller::installFiles() void UpdateInstaller::installFiles()
{ {
LOG(Info,"Installing files.");
std::vector<UpdateScriptFile>::const_iterator iter = m_script->filesToInstall().begin(); std::vector<UpdateScriptFile>::const_iterator iter = m_script->filesToInstall().begin();
int filesInstalled = 0; int filesInstalled = 0;
for (;iter != m_script->filesToInstall().end();iter++) for (;iter != m_script->filesToInstall().end();iter++)
@ -314,13 +322,18 @@ void UpdateInstaller::installFiles()
void UpdateInstaller::uninstallFiles() void UpdateInstaller::uninstallFiles()
{ {
LOG(Info,"Uninstalling files.");
std::vector<std::string>::const_iterator iter = m_script->filesToUninstall().begin(); std::vector<std::string>::const_iterator iter = m_script->filesToUninstall().begin();
for (;iter != m_script->filesToUninstall().end();iter++) for (;iter != m_script->filesToUninstall().end();iter++)
{ {
std::string path = m_installDir + '/' + iter->c_str(); std::string path = m_installDir + '/' + iter->c_str();
if (FileUtils::fileExists(path.c_str())) if (FileUtils::fileExists(path.c_str()))
{ {
FileUtils::removeFile(path.c_str()); LOG(Info,"Uninstalling " + path);
if(!m_dryRun)
{
FileUtils::removeFile(path.c_str());
}
} }
else else
{ {
@ -336,30 +349,41 @@ void UpdateInstaller::backupFile(const std::string& path)
// no existing file to backup // no existing file to backup
return; return;
} }
std::string backupPath = path + ".bak"; std::string backupPath = path + ".bak";
FileUtils::removeFile(backupPath.c_str()); LOG(Info,"Backing up file: " + path + " as " + backupPath);
FileUtils::moveFile(path.c_str(), backupPath.c_str()); if(!m_dryRun)
{
FileUtils::removeFile(backupPath.c_str());
FileUtils::moveFile(path.c_str(), backupPath.c_str());
}
m_backups[path] = backupPath; m_backups[path] = backupPath;
} }
void UpdateInstaller::removeBackups() void UpdateInstaller::removeBackups()
{ {
LOG(Info,"Removing backups.");
std::map<std::string,std::string>::const_iterator iter = m_backups.begin(); std::map<std::string,std::string>::const_iterator iter = m_backups.begin();
for (;iter != m_backups.end();iter++) for (;iter != m_backups.end();iter++)
{ {
const std::string& backupFile = iter->second; const std::string& backupFile = iter->second;
FileUtils::removeFile(backupFile.c_str()); LOG(Info,"Removing " + backupFile);
if(!m_dryRun)
{
FileUtils::removeFile(backupFile.c_str());
}
} }
} }
bool UpdateInstaller::checkAccess() bool UpdateInstaller::checkAccess()
{ {
std::string testFile = m_installDir + "/update-installer-test-file"; std::string testFile = m_installDir + "/update-installer-test-file";
LOG(Info,"Checking for access: " + testFile);
try try
{ {
FileUtils::removeFile(testFile.c_str()); if(!m_dryRun)
{
FileUtils::removeFile(testFile.c_str());
}
} }
catch (const FileUtils::IOException& error) catch (const FileUtils::IOException& error)
{ {
@ -368,8 +392,11 @@ bool UpdateInstaller::checkAccess()
try try
{ {
FileUtils::touch(testFile.c_str()); if(!m_dryRun)
FileUtils::removeFile(testFile.c_str()); {
FileUtils::touch(testFile.c_str());
FileUtils::removeFile(testFile.c_str());
}
return true; return true;
} }
catch (const FileUtils::IOException& error) catch (const FileUtils::IOException& error)
@ -394,7 +421,10 @@ void UpdateInstaller::restartMainApp()
if (!command.empty()) if (!command.empty())
{ {
LOG(Info,"Starting main application " + command); LOG(Info,"Starting main application " + command);
ProcessUtils::runAsync(command,args); if(!m_dryRun)
{
ProcessUtils::runAsync(command,args);
}
} }
else else
{ {
@ -415,7 +445,11 @@ void UpdateInstaller::postInstallUpdate()
// touch the application's bundle directory so that // touch the application's bundle directory so that
// OS X' Launch Services notices any changes in the application's // OS X' Launch Services notices any changes in the application's
// Info.plist file. // Info.plist file.
FileUtils::touch(m_installDir.c_str()); LOG(Info,"Touching " + m_installDir.c_str() + " to notify OSX of metadata changes.");
if(!m_dryRun)
{
FileUtils::touch(m_installDir.c_str());
}
#endif #endif
} }
@ -424,3 +458,7 @@ void UpdateInstaller::setAutoClose(bool autoClose)
m_autoClose = autoClose; m_autoClose = autoClose;
} }
void UpdateInstaller::setDryRun(bool dryRun)
{
m_dryRun = dryRun;
}

View File

@ -24,7 +24,6 @@ class UpdateInstaller
Main Main
}; };
UpdateInstaller();
void setInstallDir(const std::string& path); void setInstallDir(const std::string& path);
void setPackageDir(const std::string& path); void setPackageDir(const std::string& path);
void setBackupDir(const std::string& path); void setBackupDir(const std::string& path);
@ -33,6 +32,7 @@ class UpdateInstaller
void setWaitPid(PLATFORM_PID pid); void setWaitPid(PLATFORM_PID pid);
void setForceElevated(bool elevated); void setForceElevated(bool elevated);
void setAutoClose(bool autoClose); void setAutoClose(bool autoClose);
void setDryRun(bool dryRun);
void setFinishCmd(const std::string& cmd); void setFinishCmd(const std::string& cmd);
void setObserver(UpdateObserver* observer); void setObserver(UpdateObserver* observer);
@ -57,16 +57,16 @@ class UpdateInstaller
std::list<std::string> updaterArgs() const; std::list<std::string> updaterArgs() const;
std::string friendlyErrorForError(const FileUtils::IOException& ex) const; std::string friendlyErrorForError(const FileUtils::IOException& ex) const;
Mode m_mode; Mode m_mode = Setup;
std::string m_installDir; std::string m_installDir;
std::string m_packageDir; std::string m_packageDir;
std::string m_backupDir; std::string m_backupDir;
std::string m_finishCmd; std::string m_finishCmd;
PLATFORM_PID m_waitPid; PLATFORM_PID m_waitPid = 0;
UpdateScript* m_script; UpdateScript* m_script = nullptr;
UpdateObserver* m_observer; UpdateObserver* m_observer = nullptr;
std::map<std::string,std::string> m_backups; std::map<std::string,std::string> m_backups;
bool m_forceElevated; bool m_forceElevated = false;
bool m_autoClose; bool m_autoClose = false;
bool m_dryRun = false;
}; };

View File

@ -34,71 +34,6 @@ UpdateInstaller::Mode stringToMode(const std::string& modeStr)
} }
} }
void UpdaterOptions::parseOldFormatArg(const std::string& arg, std::string* key, std::string* value)
{
size_t pos = arg.find('=');
if (pos != std::string::npos)
{
*key = arg.substr(0,pos);
*value = arg.substr(pos+1);
}
}
// this is a compatibility function to allow the updater binary
// to be involved by legacy versions of Mendeley Desktop
// which used a different syntax for the updater's command-line
// arguments
void UpdaterOptions::parseOldFormatArgs(int argc, char** argv)
{
for (int i=0; i < argc; i++)
{
std::string key;
std::string value;
parseOldFormatArg(argv[i],&key,&value);
if (key == "CurrentDir")
{
// CurrentDir is the directory containing the main application
// binary. On Mac and Linux this differs from the root of
// the installation directory
#ifdef PLATFORM_LINUX
// the main binary is in lib/mendeleydesktop/libexec,
// go up 3 levels
installDir = FileUtils::canonicalPath((value + "/../../../").c_str());
#elif defined(PLATFORM_MAC)
// the main binary is in Contents/MacOS,
// go up 2 levels
installDir = FileUtils::canonicalPath((value + "/../../").c_str());
#elif defined(PLATFORM_WINDOWS)
// the main binary is in the root of the install directory
installDir = value;
#endif
}
else if (key == "TempDir")
{
packageDir = value;
}
else if (key == "UpdateScriptFileName")
{
scriptPath = value;
}
else if (key == "AppFileName")
{
// TODO - Store app file name
}
else if (key == "PID")
{
waitPid = static_cast<PLATFORM_PID>(atoll(value.c_str()));
}
else if (key == "--main")
{
mode = UpdateInstaller::Main;
}
}
}
void UpdaterOptions::parse(int argc, char** argv) void UpdaterOptions::parse(int argc, char** argv)
{ {
AnyOption parser; AnyOption parser;
@ -110,6 +45,7 @@ void UpdaterOptions::parse(int argc, char** argv)
parser.setOption("mode"); parser.setOption("mode");
parser.setFlag("version"); parser.setFlag("version");
parser.setFlag("force-elevated"); parser.setFlag("force-elevated");
parser.setFlag("dry-run");
parser.setFlag("auto-close"); parser.setFlag("auto-close");
parser.processCommandArgs(argc,argv); parser.processCommandArgs(argc,argv);
@ -141,15 +77,6 @@ void UpdaterOptions::parse(int argc, char** argv)
showVersion = parser.getFlag("version"); showVersion = parser.getFlag("version");
forceElevated = parser.getFlag("force-elevated"); forceElevated = parser.getFlag("force-elevated");
dryRun = parser.getFlag("dry-run");
autoClose = parser.getFlag("auto-close"); autoClose = parser.getFlag("auto-close");
if (installDir.empty())
{
// if no --install-dir argument is present, try parsing
// the command-line arguments in the old format (which uses
// a list of 'Key=Value' args)
parseOldFormatArgs(argc,argv);
}
} }

View File

@ -18,11 +18,8 @@ class UpdaterOptions
PLATFORM_PID waitPid; PLATFORM_PID waitPid;
std::string logFile; std::string logFile;
bool showVersion; bool showVersion;
bool dryRun;
bool forceElevated; bool forceElevated;
bool autoClose; bool autoClose;
private:
void parseOldFormatArgs(int argc, char** argv);
static void parseOldFormatArg(const std::string& arg, std::string* key, std::string* value);
}; };

View File

@ -148,6 +148,7 @@ int main(int argc, char** argv)
installer.setForceElevated(options.forceElevated); installer.setForceElevated(options.forceElevated);
installer.setAutoClose(options.autoClose); installer.setAutoClose(options.autoClose);
installer.setFinishCmd(options.finishCmd); installer.setFinishCmd(options.finishCmd);
installer.setDryRun(options.dryRun);
if (options.mode == UpdateInstaller::Main) if (options.mode == UpdateInstaller::Main)
{ {