summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPetr Mrázek <peterix@gmail.com>2016-11-18 16:04:08 +0100
committerPetr Mrázek <peterix@gmail.com>2016-11-19 22:11:45 +0100
commit69be23c5f629884465dec98efcb9d5a2678b4df5 (patch)
tree9c9135bb995006fac7a6de3b349fe24b325dbe99
parente974950d48e4e8b9500acbeadf9c1abdab1dec72 (diff)
downloadMultiMC-69be23c5f629884465dec98efcb9d5a2678b4df5.tar
MultiMC-69be23c5f629884465dec98efcb9d5a2678b4df5.tar.gz
MultiMC-69be23c5f629884465dec98efcb9d5a2678b4df5.tar.lz
MultiMC-69be23c5f629884465dec98efcb9d5a2678b4df5.tar.xz
MultiMC-69be23c5f629884465dec98efcb9d5a2678b4df5.zip
GH-1726 better failure detection for updates
Instead of just checking if the new version started, make sure it is able to write its IPC key to a file and then use the key to connect to the process.
-rw-r--r--api/logic/updater/DownloadTask_test.cpp4
-rw-r--r--api/logic/updater/GoUpdate.h21
-rw-r--r--application/CMakeLists.txt2
-rw-r--r--application/MainWindow.cpp4
-rw-r--r--application/MultiMC.cpp340
-rw-r--r--application/MultiMC.h4
-rw-r--r--application/UpdateController.cpp431
-rw-r--r--application/UpdateController.h44
-rw-r--r--application/main.cpp22
-rw-r--r--libraries/LocalPeer/include/LocalPeer.h5
-rw-r--r--libraries/LocalPeer/src/LocalPeer.cpp5
11 files changed, 553 insertions, 329 deletions
diff --git a/api/logic/updater/DownloadTask_test.cpp b/api/logic/updater/DownloadTask_test.cpp
index 5b88b366..e75c3ffa 100644
--- a/api/logic/updater/DownloadTask_test.cpp
+++ b/api/logic/updater/DownloadTask_test.cpp
@@ -52,8 +52,8 @@ QDebug operator<<(QDebug dbg, const Operation::Type &t)
QDebug operator<<(QDebug dbg, const Operation &u)
{
- dbg.nospace() << "Operation(type=" << u.type << " file=" << u.file
- << " dest=" << u.dest << " mode=" << u.mode << ")";
+ dbg.nospace() << "Operation(type=" << u.type << " file=" << u.source
+ << " dest=" << u.destination << " mode=" << u.destinationMode << ")";
return dbg.maybeSpace();
}
diff --git a/api/logic/updater/GoUpdate.h b/api/logic/updater/GoUpdate.h
index b8a534de..95f26b8c 100644
--- a/api/logic/updater/GoUpdate.h
+++ b/api/logic/updater/GoUpdate.h
@@ -68,19 +68,22 @@ typedef QList<VersionFileEntry> VersionFileList;
*/
struct MULTIMC_LOGIC_EXPORT Operation
{
- static Operation CopyOp(QString fsource, QString fdest, int fmode=0644)
+ static Operation CopyOp(QString from, QString to, int fmode=0644)
{
- return Operation{OP_REPLACE, fsource, fdest, fmode};
+ return Operation{OP_REPLACE, from, to, fmode};
}
static Operation DeleteOp(QString file)
{
- return Operation{OP_DELETE, file, "", 0644};
+ return Operation{OP_DELETE, QString(), file, 0644};
}
// FIXME: for some types, some of the other fields are irrelevant!
bool operator==(const Operation &u2) const
{
- return type == u2.type && file == u2.file && dest == u2.dest && mode == u2.mode;
+ return type == u2.type &&
+ source == u2.source &&
+ destination == u2.destination &&
+ destinationMode == u2.destinationMode;
}
//! Specifies the type of operation that this is.
@@ -90,14 +93,14 @@ struct MULTIMC_LOGIC_EXPORT Operation
OP_DELETE,
} type;
- //! The file to operate on.
- QString file;
+ //! The source file, if any
+ QString source;
//! The destination file.
- QString dest;
+ QString destination;
- //! The mode to change the source file to.
- int mode;
+ //! The mode to change the destination file to.
+ int destinationMode;
};
typedef QList<Operation> OperationList;
diff --git a/application/CMakeLists.txt b/application/CMakeLists.txt
index 64879e27..18351cce 100644
--- a/application/CMakeLists.txt
+++ b/application/CMakeLists.txt
@@ -85,6 +85,8 @@ SET(MULTIMC_SOURCES
MultiMC.cpp
BuildConfig.h
${PROJECT_BINARY_DIR}/BuildConfig.cpp
+ UpdateController.cpp
+ UpdateController.h
# GUI - general utilities
GuiUtil.h
diff --git a/application/MainWindow.cpp b/application/MainWindow.cpp
index e5ea81f6..4e649521 100644
--- a/application/MainWindow.cpp
+++ b/application/MainWindow.cpp
@@ -90,6 +90,7 @@
#include "dialogs/ExportInstanceDialog.h"
#include <FolderInstanceProvider.h>
#include <InstanceImportTask.h>
+#include "UpdateController.h"
class MainWindow::Ui
{
@@ -952,7 +953,8 @@ void MainWindow::downloadUpdates(GoUpdate::Status status)
// If the task succeeds, install the updates.
if (updateDlg.execWithTask(&updateTask))
{
- MMC->installUpdates(updateTask.updateFilesDir(), updateTask.operations());
+ UpdateController update(this, MMC->root(), updateTask.updateFilesDir(), updateTask.operations());
+ update.installUpdates();
}
else
{
diff --git a/application/MultiMC.cpp b/application/MultiMC.cpp
index c102e198..64c380ec 100644
--- a/application/MultiMC.cpp
+++ b/application/MultiMC.cpp
@@ -73,6 +73,8 @@
#include <stdio.h>
#endif
+static const QLatin1String liveCheckFile("live.check");
+
using namespace Commandline;
MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
@@ -132,6 +134,9 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
parser.addOption("launch");
parser.addShortOpt("launch", 'l');
parser.addDocumentation("launch", "launch the specified instance (by instance ID)");
+ // --alive
+ parser.addSwitch("alive");
+ parser.addDocumentation("alive", "write a small '" + liveCheckFile + "' file after MultiMC starts");
// parse the arguments
try
@@ -165,6 +170,7 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
}
}
m_instanceIdToLaunch = args["launch"].toString();
+ m_liveCheck = args["alive"].toBool();
QString origcwdPath = QDir::currentPath();
QString binPath = applicationDirPath();
@@ -242,6 +248,27 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
qDebug() << "ID of instance to launch : " << m_instanceIdToLaunch;
}
+ do // once
+ {
+ if(m_liveCheck)
+ {
+ QFile check(liveCheckFile);
+ if(!check.open(QIODevice::WriteOnly | QIODevice::Truncate))
+ {
+ qWarning() << "Could not open" << liveCheckFile << "for writing!";
+ break;
+ }
+ auto payload = appID.toString().toUtf8();
+ if(check.write(payload) != payload.size())
+ {
+ qWarning() << "Could not write into" << liveCheckFile;
+ check.remove();
+ break;
+ }
+ check.close();
+ }
+ } while(false);
+
// load settings
initGlobalSettings();
@@ -697,319 +724,6 @@ std::shared_ptr<JavaInstallList> MultiMC::javalist()
return m_javalist;
}
-// from <sys/stat.h>
-#ifndef S_IRUSR
-#define __S_IREAD 0400 /* Read by owner. */
-#define __S_IWRITE 0200 /* Write by owner. */
-#define __S_IEXEC 0100 /* Execute by owner. */
-#define S_IRUSR __S_IREAD /* Read by owner. */
-#define S_IWUSR __S_IWRITE /* Write by owner. */
-#define S_IXUSR __S_IEXEC /* Execute by owner. */
-
-#define S_IRGRP (S_IRUSR >> 3) /* Read by group. */
-#define S_IWGRP (S_IWUSR >> 3) /* Write by group. */
-#define S_IXGRP (S_IXUSR >> 3) /* Execute by group. */
-
-#define S_IROTH (S_IRGRP >> 3) /* Read by others. */
-#define S_IWOTH (S_IWGRP >> 3) /* Write by others. */
-#define S_IXOTH (S_IXGRP >> 3) /* Execute by others. */
-#endif
-static QFile::Permissions unixModeToPermissions(const int mode)
-{
- QFile::Permissions perms;
-
- if (mode & S_IRUSR)
- {
- perms |= QFile::ReadUser;
- }
- if (mode & S_IWUSR)
- {
- perms |= QFile::WriteUser;
- }
- if (mode & S_IXUSR)
- {
- perms |= QFile::ExeUser;
- }
-
- if (mode & S_IRGRP)
- {
- perms |= QFile::ReadGroup;
- }
- if (mode & S_IWGRP)
- {
- perms |= QFile::WriteGroup;
- }
- if (mode & S_IXGRP)
- {
- perms |= QFile::ExeGroup;
- }
-
- if (mode & S_IROTH)
- {
- perms |= QFile::ReadOther;
- }
- if (mode & S_IWOTH)
- {
- perms |= QFile::WriteOther;
- }
- if (mode & S_IXOTH)
- {
- perms |= QFile::ExeOther;
- }
- return perms;
-}
-
-void MultiMC::installUpdates(const QString updateFilesDir, GoUpdate::OperationList operations)
-{
- qint64 pid = -1;
- QStringList args;
- bool started = false;
-
- qDebug() << "Installing updates.";
-#ifdef Q_OS_WIN
- QString finishCmd = applicationFilePath();
-#elif defined Q_OS_LINUX
- QString finishCmd = FS::PathCombine(root(), "MultiMC");
-#elif defined Q_OS_MAC
- QString finishCmd = applicationFilePath();
-#else
-#error Unsupported operating system.
-#endif
-
- QString backupPath = FS::PathCombine(root(), "update", "backup");
- QDir origin(root());
-
- // clean up the backup folder. it should be empty before we start
- if(!FS::deletePath(backupPath))
- {
- qWarning() << "couldn't remove previous backup folder" << backupPath;
- }
- // and it should exist.
- if(!FS::ensureFolderPathExists(backupPath))
- {
- qWarning() << "couldn't create folder" << backupPath;
- return;
- }
-
- struct BackupEntry
- {
- QString orig;
- QString backup;
- };
- enum Failure
- {
- Replace,
- Delete,
- Start,
- Nothing
- } failedOperationType = Nothing;
- QString failedFile;
-
- QList <BackupEntry> backups;
- QList <BackupEntry> trashcan;
-
- bool useXPHack = false;
- QString exePath;
- QString exeOrigin;
- QString exeBackup;
-
- // perform the update operations
- for(auto op: operations)
- {
- switch(op.type)
- {
- // replace = move original out to backup, if it exists, move the new file in its place
- case GoUpdate::Operation::OP_REPLACE:
- {
-#ifdef Q_OS_WIN32
- // hack for people renaming the .exe because ... reasons :)
- if(op.dest == "MultiMC.exe")
- {
- op.dest = QFileInfo(applicationFilePath()).fileName();
- }
-#endif
- QFileInfo replaced (FS::PathCombine(root(), op.dest));
-#ifdef Q_OS_WIN32
- if(QSysInfo::windowsVersion() < QSysInfo::WV_VISTA)
- {
- if(replaced.fileName() == "MultiMC.exe")
- {
- QDir rootDir(root());
- exeOrigin = rootDir.relativeFilePath(op.file);
- exePath = rootDir.relativeFilePath(op.dest);
- exeBackup = rootDir.relativeFilePath(FS::PathCombine(backupPath, replaced.fileName()));
- useXPHack = true;
- continue;
- }
- }
-#endif
- if(replaced.exists())
- {
- QString backupName = op.dest;
- backupName.replace('/', '_');
- QString backupFilePath = FS::PathCombine(backupPath, backupName);
- if(!QFile::rename(replaced.absoluteFilePath(), backupFilePath))
- {
- qWarning() << "Couldn't move:" << replaced.absoluteFilePath() << "to" << backupFilePath;
- failedOperationType = Replace;
- failedFile = op.dest;
- goto FAILED;
- }
- BackupEntry be;
- be.orig = replaced.absoluteFilePath();
- be.backup = backupFilePath;
- backups.append(be);
- }
- // make sure the folder we are putting this into exists
- if(!FS::ensureFilePathExists(replaced.absoluteFilePath()))
- {
- qWarning() << "REPLACE: Couldn't create folder:" << replaced.absoluteFilePath();
- failedOperationType = Replace;
- failedFile = op.dest;
- goto FAILED;
- }
- // now move the new file in
- if(!QFile::rename(op.file, replaced.absoluteFilePath()))
- {
- qWarning() << "REPLACE: Couldn't move:" << op.file << "to" << replaced.absoluteFilePath();
- failedOperationType = Replace;
- failedFile = op.dest;
- goto FAILED;
- }
- QFile::setPermissions(replaced.absoluteFilePath(), unixModeToPermissions(op.mode));
- }
- break;
- // delete = move original to backup
- case GoUpdate::Operation::OP_DELETE:
- {
- QString origFilePath = FS::PathCombine(root(), op.file);
- if(QFile::exists(origFilePath))
- {
- QString backupName = op.file;
- backupName.replace('/', '_');
- QString trashFilePath = FS::PathCombine(backupPath, backupName);
-
- if(!QFile::rename(origFilePath, trashFilePath))
- {
- qWarning() << "DELETE: Couldn't move:" << op.file << "to" << trashFilePath;
- failedFile = op.file;
- failedOperationType = Delete;
- goto FAILED;
- }
- BackupEntry be;
- be.orig = origFilePath;
- be.backup = trashFilePath;
- trashcan.append(be);
- }
- }
- break;
- }
- }
-
- // try to start the new binary
- args = qApp->arguments();
- args.removeFirst();
-
- // on old Windows, do insane things... no error checking here, this is just to have something.
- if(useXPHack)
- {
- QString script;
- auto nativePath = QDir::toNativeSeparators(exePath);
- auto nativeOriginPath = QDir::toNativeSeparators(exeOrigin);
- auto nativeBackupPath = QDir::toNativeSeparators(exeBackup);
-
- // so we write this vbscript thing...
- QTextStream out(&script);
- out << "WScript.Sleep 1000\n";
- out << "Set fso=CreateObject(\"Scripting.FileSystemObject\")\n";
- out << "Set shell=CreateObject(\"WScript.Shell\")\n";
- out << "fso.MoveFile \"" << nativePath << "\", \"" << nativeBackupPath << "\"\n";
- out << "fso.MoveFile \"" << nativeOriginPath << "\", \"" << nativePath << "\"\n";
- out << "shell.Run \"" << nativePath << "\"\n";
-
- QString scriptPath = FS::PathCombine(root(), "update", "update.vbs");
-
- // we save it
- QFile scriptFile(scriptPath);
- scriptFile.open(QIODevice::WriteOnly);
- scriptFile.write(script.toLocal8Bit().replace("\n", "\r\n"));
- scriptFile.close();
-
- // we run it
- started = QProcess::startDetached("wscript", {scriptPath}, root());
-
- // and we quit. conscious thought.
- qApp->quit();
- return;
- }
- started = QProcess::startDetached(finishCmd, args, QDir::currentPath(), &pid);
- // failed to start... ?
- if(!started || pid == -1)
- {
- qWarning() << "Couldn't start new process properly!";
- failedOperationType = Start;
- goto FAILED;
- }
- origin.rmdir(updateFilesDir);
- qApp->quit();
- return;
-
-FAILED:
- qWarning() << "Update failed!";
- bool revertOK = true;
- // if the above failed, roll back changes
- for(auto backup:backups)
- {
- qWarning() << "restoring" << backup.orig << "from" << backup.backup;
- if(!QFile::remove(backup.orig))
- {
- revertOK = false;
- qWarning() << "removing new" << backup.orig << "failed!";
- continue;
- }
-
- if(!QFile::rename(backup.backup, backup.orig))
- {
- revertOK = false;
- qWarning() << "restoring" << backup.orig << "failed!";
- }
- }
- for(auto backup:trashcan)
- {
- qWarning() << "restoring" << backup.orig << "from" << backup.backup;
- if(!QFile::rename(backup.backup, backup.orig))
- {
- revertOK = false;
- qWarning() << "restoring" << backup.orig << "failed!";
- }
- }
- QString msg;
- if(!revertOK)
- {
- msg = tr("The update failed and then the update revert failed too.\n"
- "You will have to repair MultiMC manually.\n"
- "Please let us know why and how this happened.").arg(failedFile);
- }
- else switch (failedOperationType)
- {
- case Replace:
- msg = tr("Couldn't replace file %1. Changes were reverted.\n"
- "See the MultiMC log file for details.").arg(failedFile);
- break;
- case Delete:
- msg = tr("Couldn't remove file %1. Changes were reverted.\n"
- "See the MultiMC log file for details.").arg(failedFile);
- break;
- case Start:
- msg = tr("The new version didn't start and the update was rolled back.");
- break;
- case Nothing:
- default:
- return;
- }
- QMessageBox::critical(nullptr, tr("Update failed!"), msg);
-}
-
std::vector<ITheme *> MultiMC::getValidApplicationThemes()
{
std::vector<ITheme *> ret;
diff --git a/application/MultiMC.h b/application/MultiMC.h
index c416b8c0..23cafdb1 100644
--- a/application/MultiMC.h
+++ b/application/MultiMC.h
@@ -132,9 +132,6 @@ public:
return m_rootPath;
}
- // install updates now.
- void installUpdates(const QString updateFilesDir, GoUpdate::OperationList operations);
-
/*!
* Opens a json file using either a system default editor, or, if not empty, the editor
* specified in the settings
@@ -223,5 +220,6 @@ private:
LocalPeer * m_peerInstance = nullptr;
public:
QString m_instanceIdToLaunch;
+ bool m_liveCheck = false;
std::unique_ptr<QFile> logFile;
};
diff --git a/application/UpdateController.cpp b/application/UpdateController.cpp
new file mode 100644
index 00000000..e3d8f2fb
--- /dev/null
+++ b/application/UpdateController.cpp
@@ -0,0 +1,431 @@
+#include <QFile>
+#include <QMessageBox>
+#include <FileSystem.h>
+#include <updater/GoUpdate.h>
+#include "UpdateController.h"
+#include <QApplication>
+#include <thread>
+#include <chrono>
+#include <LocalPeer.h>
+
+// from <sys/stat.h>
+#ifndef S_IRUSR
+#define __S_IREAD 0400 /* Read by owner. */
+#define __S_IWRITE 0200 /* Write by owner. */
+#define __S_IEXEC 0100 /* Execute by owner. */
+#define S_IRUSR __S_IREAD /* Read by owner. */
+#define S_IWUSR __S_IWRITE /* Write by owner. */
+#define S_IXUSR __S_IEXEC /* Execute by owner. */
+
+#define S_IRGRP (S_IRUSR >> 3) /* Read by group. */
+#define S_IWGRP (S_IWUSR >> 3) /* Write by group. */
+#define S_IXGRP (S_IXUSR >> 3) /* Execute by group. */
+
+#define S_IROTH (S_IRGRP >> 3) /* Read by others. */
+#define S_IWOTH (S_IWGRP >> 3) /* Write by others. */
+#define S_IXOTH (S_IXGRP >> 3) /* Execute by others. */
+#endif
+static QFile::Permissions unixModeToPermissions(const int mode)
+{
+ QFile::Permissions perms;
+
+ if (mode & S_IRUSR)
+ {
+ perms |= QFile::ReadUser;
+ }
+ if (mode & S_IWUSR)
+ {
+ perms |= QFile::WriteUser;
+ }
+ if (mode & S_IXUSR)
+ {
+ perms |= QFile::ExeUser;
+ }
+
+ if (mode & S_IRGRP)
+ {
+ perms |= QFile::ReadGroup;
+ }
+ if (mode & S_IWGRP)
+ {
+ perms |= QFile::WriteGroup;
+ }
+ if (mode & S_IXGRP)
+ {
+ perms |= QFile::ExeGroup;
+ }
+
+ if (mode & S_IROTH)
+ {
+ perms |= QFile::ReadOther;
+ }
+ if (mode & S_IWOTH)
+ {
+ perms |= QFile::WriteOther;
+ }
+ if (mode & S_IXOTH)
+ {
+ perms |= QFile::ExeOther;
+ }
+ return perms;
+}
+
+static const QLatin1String liveCheckFile("live.check");
+
+UpdateController::UpdateController(QWidget * parent, const QString& root, const QString updateFilesDir, GoUpdate::OperationList operations)
+{
+ m_parent = parent;
+ m_root = root;
+ m_updateFilesDir = updateFilesDir;
+ m_operations = operations;
+}
+
+
+void UpdateController::installUpdates()
+{
+ qint64 pid = -1;
+ QStringList args;
+ bool started = false;
+
+ qDebug() << "Installing updates.";
+#ifdef Q_OS_WIN
+ QString finishCmd = QApplication::applicationFilePath();
+#elif defined Q_OS_LINUX
+ QString finishCmd = FS::PathCombine(m_root, "MultiMC");
+#elif defined Q_OS_MAC
+ QString finishCmd = QApplication::applicationFilePath();
+#else
+#error Unsupported operating system.
+#endif
+
+ QString backupPath = FS::PathCombine(m_root, "update", "backup");
+ QDir origin(m_root);
+
+ // clean up the backup folder. it should be empty before we start
+ if(!FS::deletePath(backupPath))
+ {
+ qWarning() << "couldn't remove previous backup folder" << backupPath;
+ }
+ // and it should exist.
+ if(!FS::ensureFolderPathExists(backupPath))
+ {
+ qWarning() << "couldn't create folder" << backupPath;
+ return;
+ }
+
+ bool useXPHack = false;
+ QString exePath;
+ QString exeOrigin;
+ QString exeBackup;
+
+ // perform the update operations
+ for(auto op: m_operations)
+ {
+ switch(op.type)
+ {
+ // replace = move original out to backup, if it exists, move the new file in its place
+ case GoUpdate::Operation::OP_REPLACE:
+ {
+#ifdef Q_OS_WIN32
+ // hack for people renaming the .exe because ... reasons :)
+ if(op.destination == "MultiMC.exe")
+ {
+ op.destination = QFileInfo(QApplication::applicationFilePath()).fileName();
+ }
+#endif
+ QFileInfo destination (FS::PathCombine(m_root, op.destination));
+#ifdef Q_OS_WIN32
+ if(QSysInfo::windowsVersion() < QSysInfo::WV_VISTA)
+ {
+ if(destination.fileName() == "MultiMC.exe")
+ {
+ QDir rootDir(m_root);
+ exeOrigin = rootDir.relativeFilePath(op.source);
+ exePath = rootDir.relativeFilePath(op.destination);
+ exeBackup = rootDir.relativeFilePath(FS::PathCombine(backupPath, destination.fileName()));
+ useXPHack = true;
+ continue;
+ }
+ }
+#endif
+ if(destination.exists())
+ {
+ QString backupName = op.destination;
+ backupName.replace('/', '_');
+ QString backupFilePath = FS::PathCombine(backupPath, backupName);
+ if(!QFile::rename(destination.absoluteFilePath(), backupFilePath))
+ {
+ qWarning() << "Couldn't move:" << destination.absoluteFilePath() << "to" << backupFilePath;
+ m_failedOperationType = Replace;
+ m_failedFile = op.destination;
+ fail();
+ return;
+ }
+ BackupEntry be;
+ be.original = destination.absoluteFilePath();
+ be.backup = backupFilePath;
+ be.update = op.source;
+ m_replace_backups.append(be);
+ }
+ // make sure the folder we are putting this into exists
+ if(!FS::ensureFilePathExists(destination.absoluteFilePath()))
+ {
+ qWarning() << "REPLACE: Couldn't create folder:" << destination.absoluteFilePath();
+ m_failedOperationType = Replace;
+ m_failedFile = op.destination;
+ fail();
+ return;
+ }
+ // now move the new file in
+ if(!QFile::rename(op.source, destination.absoluteFilePath()))
+ {
+ qWarning() << "REPLACE: Couldn't move:" << op.source << "to" << destination.absoluteFilePath();
+ m_failedOperationType = Replace;
+ m_failedFile = op.destination;
+ fail();
+ return;
+ }
+ QFile::setPermissions(destination.absoluteFilePath(), unixModeToPermissions(op.destinationMode));
+ }
+ break;
+ // delete = move original to backup
+ case GoUpdate::Operation::OP_DELETE:
+ {
+ QString destFilePath = FS::PathCombine(m_root, op.destination);
+ if(QFile::exists(destFilePath))
+ {
+ QString backupName = op.destination;
+ backupName.replace('/', '_');
+ QString trashFilePath = FS::PathCombine(backupPath, backupName);
+
+ if(!QFile::rename(destFilePath, trashFilePath))
+ {
+ qWarning() << "DELETE: Couldn't move:" << op.destination << "to" << trashFilePath;
+ m_failedFile = op.destination;
+ m_failedOperationType = Delete;
+ fail();
+ return;
+ }
+ BackupEntry be;
+ be.original = destFilePath;
+ be.backup = trashFilePath;
+ m_delete_backups.append(be);
+ }
+ }
+ break;
+ }
+ }
+
+ // try to start the new binary
+ args = qApp->arguments();
+ args.removeFirst();
+
+ // on old Windows, do insane things... no error checking here, this is just to have something.
+ if(useXPHack)
+ {
+ QString script;
+ auto nativePath = QDir::toNativeSeparators(exePath);
+ auto nativeOriginPath = QDir::toNativeSeparators(exeOrigin);
+ auto nativeBackupPath = QDir::toNativeSeparators(exeBackup);
+
+ // so we write this vbscript thing...
+ QTextStream out(&script);
+ out << "WScript.Sleep 1000\n";
+ out << "Set fso=CreateObject(\"Scripting.FileSystemObject\")\n";
+ out << "Set shell=CreateObject(\"WScript.Shell\")\n";
+ out << "fso.MoveFile \"" << nativePath << "\", \"" << nativeBackupPath << "\"\n";
+ out << "fso.MoveFile \"" << nativeOriginPath << "\", \"" << nativePath << "\"\n";
+ out << "shell.Run \"" << nativePath << "\"\n";
+
+ QString scriptPath = FS::PathCombine(m_root, "update", "update.vbs");
+
+ // we save it
+ QFile scriptFile(scriptPath);
+ scriptFile.open(QIODevice::WriteOnly);
+ scriptFile.write(script.toLocal8Bit().replace("\n", "\r\n"));
+ scriptFile.close();
+
+ // we run it
+ started = QProcess::startDetached("wscript", {scriptPath}, m_root);
+
+ // and we quit. conscious thought.
+ qApp->quit();
+ return;
+ }
+ bool doLiveCheck = true;
+ bool startFailed = false;
+
+ // remove live check file, if any
+ if(QFile::exists(liveCheckFile))
+ {
+ if(!QFile::remove(liveCheckFile))
+ {
+ qWarning() << "Couldn't remove the" << liveCheckFile << "file! We will proceed without :(";
+ doLiveCheck = false;
+ }
+ }
+
+ if(doLiveCheck)
+ {
+ if(!args.contains("--alive"))
+ {
+ args.append("--alive");
+ }
+ }
+
+ // start the updated application
+ started = QProcess::startDetached(finishCmd, args, QDir::currentPath(), &pid);
+ // much dumber check - just find out if the call
+ if(!started || pid == -1)
+ {
+ qWarning() << "Couldn't start new process properly!";
+ startFailed = true;
+ }
+ if(!startFailed && doLiveCheck)
+ {
+ int attempts = 0;
+ while(attempts < 10)
+ {
+ attempts++;
+ QString key;
+ std::this_thread::sleep_for(std::chrono::milliseconds(250));
+ if(!QFile::exists(liveCheckFile))
+ {
+ qWarning() << "Couldn't find the" << liveCheckFile << "file!";
+ startFailed = true;
+ continue;
+ }
+ try
+ {
+ key = QString::fromUtf8(FS::read(liveCheckFile));
+ auto id = ApplicationId::fromRawString(key);
+ LocalPeer peer(nullptr, id);
+ if(peer.isClient())
+ {
+ startFailed = false;
+ qDebug() << "Found process started with key " << key;
+ break;
+ }
+ else
+ {
+ startFailed = true;
+ qDebug() << "Process started with key " << key << "apparently died or is not reponding...";
+ break;
+ }
+ }
+ catch(Exception e)
+ {
+ qWarning() << "Couldn't read the" << liveCheckFile << "file!";
+ startFailed = true;
+ continue;
+ }
+ }
+ }
+ if(startFailed)
+ {
+ m_failedOperationType = Start;
+ fail();
+ return;
+ }
+ else
+ {
+ origin.rmdir(m_updateFilesDir);
+ qApp->quit();
+ return;
+ }
+}
+
+void UpdateController::fail()
+{
+ qWarning() << "Update failed!";
+
+ QString msg;
+ bool doRollback = false;
+ QString failTitle = QObject::tr("Update failed!");
+ QString rollFailTitle = QObject::tr("Rollback failed!");
+ switch (m_failedOperationType)
+ {
+ case Replace:
+ {
+ msg = QObject::tr("Couldn't replace file %1. Changes will be reverted.\n"
+ "See the MultiMC log file for details.").arg(m_failedFile);
+ doRollback = true;
+ QMessageBox::critical(m_parent, failTitle, msg);
+ break;
+ }
+ case Delete:
+ {
+ msg = QObject::tr("Couldn't remove file %1. Changes will be reverted.\n"
+ "See the MultiMC log file for details.").arg(m_failedFile);
+ doRollback = true;
+ QMessageBox::critical(m_parent, failTitle, msg);
+ break;
+ }
+ case Start:
+ {
+ msg = QObject::tr("The new version didn't start or is too old and doesn't respond to startup checks.\n"
+ "\n"
+ "Roll back to previous version?");
+ auto result = QMessageBox::critical(
+ m_parent,
+ failTitle,
+ msg,
+ QMessageBox::Yes | QMessageBox::No,
+ QMessageBox::Yes
+ );
+ doRollback = (result == QMessageBox::Yes);
+ break;
+ }
+ case Nothing:
+ default:
+ return;
+ }
+ if(doRollback)
+ {
+ auto rollbackOK = rollback();
+ if(!rollbackOK)
+ {
+ msg = QObject::tr("The rollback failed too.\n"
+ "You will have to repair MultiMC manually.\n"
+ "Please let us know why and how this happened.").arg(m_failedFile);
+ QMessageBox::critical(m_parent, rollFailTitle, msg);
+ qApp->quit();
+ }
+ }
+ else
+ {
+ qApp->quit();
+ }
+}
+
+bool UpdateController::rollback()
+{
+ bool revertOK = true;
+ // if the above failed, roll back changes
+ for(auto backup:m_replace_backups)
+ {
+ qWarning() << "restoring" << backup.original << "from" << backup.backup;
+ if(!QFile::rename(backup.original, backup.update))
+ {
+ revertOK = false;
+ qWarning() << "moving new" << backup.original << "back to" << backup.update << "failed!";
+ continue;
+ }
+
+ if(!QFile::rename(backup.backup, backup.original))
+ {
+ revertOK = false;
+ qWarning() << "restoring" << backup.original << "failed!";
+ }
+ }
+ for(auto backup:m_delete_backups)
+ {
+ qWarning() << "restoring" << backup.original << "from" << backup.backup;
+ if(!QFile::rename(backup.backup, backup.original))
+ {
+ revertOK = false;
+ qWarning() << "restoring" << backup.original << "failed!";
+ }
+ }
+ return revertOK;
+}
diff --git a/application/UpdateController.h b/application/UpdateController.h
new file mode 100644
index 00000000..2b258701
--- /dev/null
+++ b/application/UpdateController.h
@@ -0,0 +1,44 @@
+#pragma once
+
+#include <QString>
+#include <QList>
+#include <updater/GoUpdate.h>
+
+class QWidget;
+
+class UpdateController
+{
+public:
+ UpdateController(QWidget * parent, const QString &root, const QString updateFilesDir, GoUpdate::OperationList operations);
+ void installUpdates();
+
+private:
+ void fail();
+ bool rollback();
+
+private:
+ QString m_root;
+ QString m_updateFilesDir;
+ GoUpdate::OperationList m_operations;
+ QWidget * m_parent;
+
+ struct BackupEntry
+ {
+ // path where we got the new file from
+ QString update;
+ // path of what is being actually updated
+ QString original;
+ // path where the backup of the updated file was placed
+ QString backup;
+ };
+ QList <BackupEntry> m_replace_backups;
+ QList <BackupEntry> m_delete_backups;
+ enum Failure
+ {
+ Replace,
+ Delete,
+ Start,
+ Nothing
+ } m_failedOperationType = Nothing;
+ QString m_failedFile;
+};
diff --git a/application/main.cpp b/application/main.cpp
index 61771f6e..0fd9f089 100644
--- a/application/main.cpp
+++ b/application/main.cpp
@@ -4,8 +4,30 @@
#include <InstanceList.h>
#include <QDebug>
+// #define BREAK_INFINITE_LOOP
+// #define BREAK_EXCEPTION
+// #define BREAK_RETURN
+
+#ifdef BREAK_INFINITE_LOOP
+#include <thread>
+#include <chrono>
+#endif
+
int main(int argc, char *argv[])
{
+#ifdef BREAK_INFINITE_LOOP
+ while(true)
+ {
+ std::this_thread::sleep_for(std::chrono::milliseconds(250));
+ }
+#endif
+#ifdef BREAK_EXCEPTION
+ throw 42;
+#endif
+#ifdef BREAK_RETURN
+ return 42;
+#endif
+
// initialize Qt
MultiMC app(argc, argv);
diff --git a/libraries/LocalPeer/include/LocalPeer.h b/libraries/LocalPeer/include/LocalPeer.h
index 940bfcbd..7558f18e 100644
--- a/libraries/LocalPeer/include/LocalPeer.h
+++ b/libraries/LocalPeer/include/LocalPeer.h
@@ -54,8 +54,11 @@ public: /* methods */
static ApplicationId fromTraditionalApp();
// ID based on a path with all the application data (no two instances with the same data path should run)
static ApplicationId fromPathAndVersion(const QString & dataPath, const QString & version);
- // fully custom ID
+ // custom ID
static ApplicationId fromCustomId(const QString & id);
+ // custom ID, based on a raw string previously acquired from 'toString'
+ static ApplicationId fromRawString(const QString & id);
+
QString toString()
{
diff --git a/libraries/LocalPeer/src/LocalPeer.cpp b/libraries/LocalPeer/src/LocalPeer.cpp
index db0c73e5..f71c62d8 100644
--- a/libraries/LocalPeer/src/LocalPeer.cpp
+++ b/libraries/LocalPeer/src/LocalPeer.cpp
@@ -108,6 +108,11 @@ ApplicationId ApplicationId::fromCustomId(const QString& id)
return ApplicationId(QLatin1String("qtsingleapp-") + id);
}
+ApplicationId ApplicationId::fromRawString(const QString& id)
+{
+ return ApplicationId(id);
+}
+
LocalPeer::LocalPeer(QObject * parent, const ApplicationId &appId)
: QObject(parent), id(appId)
{