summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPetr Mrázek <peterix@gmail.com>2015-06-08 02:43:16 +0200
committerPetr Mrázek <peterix@gmail.com>2015-06-09 00:03:42 +0200
commit82e05661d207621f917d79ebd513abc57a36c084 (patch)
treeb13eb12af54ff1a6214d9b0c7b34ce6a9e5aa975
parent166813cb918ebd029325e12377989bfdc2021074 (diff)
downloadMultiMC-82e05661d207621f917d79ebd513abc57a36c084.tar
MultiMC-82e05661d207621f917d79ebd513abc57a36c084.tar.gz
MultiMC-82e05661d207621f917d79ebd513abc57a36c084.tar.lz
MultiMC-82e05661d207621f917d79ebd513abc57a36c084.tar.xz
MultiMC-82e05661d207621f917d79ebd513abc57a36c084.zip
GH-1060 implement very basic updater (only linux and maybe osx right now)
-rw-r--r--application/MainWindow.cpp2
-rw-r--r--application/MultiMC.cpp162
-rw-r--r--application/MultiMC.h3
-rw-r--r--logic/updater/DownloadTask.cpp15
-rw-r--r--logic/updater/DownloadTask.h5
-rw-r--r--logic/updater/GoUpdate.cpp74
-rw-r--r--logic/updater/GoUpdate.h11
-rw-r--r--tests/tst_DownloadTask.cpp14
8 files changed, 170 insertions, 116 deletions
diff --git a/application/MainWindow.cpp b/application/MainWindow.cpp
index 933c4dbc..8c827806 100644
--- a/application/MainWindow.cpp
+++ b/application/MainWindow.cpp
@@ -992,7 +992,7 @@ void MainWindow::downloadUpdates(GoUpdate::Status status)
// If the task succeeds, install the updates.
if (updateDlg.exec(&updateTask))
{
- MMC->installUpdates(updateTask.updateFilesDir());
+ MMC->installUpdates(updateTask.updateFilesDir(), updateTask.operations());
}
else
{
diff --git a/application/MultiMC.cpp b/application/MultiMC.cpp
index 67b50b40..a21455f0 100644
--- a/application/MultiMC.cpp
+++ b/application/MultiMC.cpp
@@ -583,7 +583,69 @@ std::shared_ptr<JavaVersionList> MultiMC::javalist()
return m_javalist;
}
-void MultiMC::installUpdates(const QString updateFilesDir)
+// 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)
{
qDebug() << "Installing updates.";
#ifdef WINDOWS
@@ -596,14 +658,96 @@ void MultiMC::installUpdates(const QString updateFilesDir)
#error Unsupported operating system.
#endif
- QStringList args;
- args << "--install-dir" << root();
- args << "--package-dir" << updateFilesDir;
- args << "--script" << PathCombine(updateFilesDir, "file_list.xml");
- args << "--wait" << QString::number(applicationPid());
- args << "--finish-cmd" << finishCmd;
- args << "--finish-dir" << dataPath;
- qDebug() << "Running updater with args" << args.join(" ");
+ QString backupPath = PathCombine(root(), "update-backup");
+ QString trashPath = PathCombine(root(), "update-trash");
+ if(!ensureFolderPathExists(backupPath))
+ {
+ qWarning() << "couldn't create folder" << backupPath;
+ return;
+ }
+ if(!ensureFolderPathExists(trashPath))
+ {
+ qWarning() << "couldn't create folder" << trashPath;
+ return;
+ }
+ struct BackupEntry
+ {
+ QString orig;
+ QString backup;
+ };
+ QList <BackupEntry> backups;
+ QList <BackupEntry> trashcan;
+ for(auto op: operations)
+ {
+ switch(op.type)
+ {
+ case GoUpdate::Operation::OP_COPY:
+ {
+ QFileInfo replaced (PathCombine(root(), op.dest));
+ if(replaced.exists())
+ {
+ QString backupFilePath = PathCombine(backupPath, replaced.completeBaseName());
+ QFile::rename(replaced.absoluteFilePath(), backupFilePath);
+ BackupEntry be;
+ be.orig = replaced.absoluteFilePath();
+ be.backup = backupFilePath;
+ backups.append(be);
+ }
+ QFile::copy(op.file, replaced.absoluteFilePath());
+ QFile::setPermissions(replaced.absoluteFilePath(), unixModeToPermissions(op.mode));
+ }
+ break;
+ case GoUpdate::Operation::OP_DELETE:
+ {
+ QString trashFilePath = PathCombine(backupPath, op.file);
+ QString origFilePath = PathCombine(root(), op.file);
+ if(QFile::exists(origFilePath))
+ {
+ QFile::rename(origFilePath, trashFilePath);
+ BackupEntry be;
+ be.orig = origFilePath;
+ be.backup = trashFilePath;
+ trashcan.append(be);
+ }
+ }
+ break;
+ }
+ }
+
+ // try to start the new binary
+ qint64 pid = -1;
+ auto args = qApp->arguments();
+ args.removeFirst();
+ QProcess::startDetached(finishCmd, args, QDir::currentPath(), &pid);
+ // failed to start... ?
+ if(pid == -1)
+ {
+ goto FAILED;
+ }
+ // now clean up the backed up stuff.
+ for(auto backup:backups)
+ {
+ QFile::remove(backup.backup);
+ }
+ for(auto backup:trashcan)
+ {
+ QFile::remove(backup.backup);
+ }
+ qApp->quit();
+ return;
+
+FAILED:
+ // if the above failed, roll back changes
+ for(auto backup:backups)
+ {
+ QFile::remove(backup.orig);
+ QFile::rename(backup.backup, backup.orig);
+ }
+ for(auto backup:trashcan)
+ {
+ QFile::rename(backup.backup, backup.orig);
+ }
+ // and do nothing
}
void MultiMC::setIconTheme(const QString& name)
diff --git a/application/MultiMC.h b/application/MultiMC.h
index f815b8e4..9edf0596 100644
--- a/application/MultiMC.h
+++ b/application/MultiMC.h
@@ -6,6 +6,7 @@
#include <QFlag>
#include <QIcon>
#include <QDateTime>
+#include <updater/GoUpdate.h>
class QFile;
class MinecraftVersionList;
@@ -105,7 +106,7 @@ public:
}
// APPLICATION ONLY
- void installUpdates(const QString updateFilesDir);
+ void installUpdates(const QString updateFilesDir, GoUpdate::OperationList operations);
/*!
* Opens a json file using either a system default editor, or, if note empty, the editor
diff --git a/logic/updater/DownloadTask.cpp b/logic/updater/DownloadTask.cpp
index 352f1f0d..4a42f583 100644
--- a/logic/updater/DownloadTask.cpp
+++ b/logic/updater/DownloadTask.cpp
@@ -89,7 +89,6 @@ void DownloadTask::processDownloadedVersionInfo()
{
VersionFileList m_currentVersionFileList;
VersionFileList m_newVersionFileList;
- OperationList operationList;
setStatus(tr("Reading file list for new version..."));
qDebug() << "Reading file list for new version...";
@@ -125,19 +124,12 @@ void DownloadTask::processDownloadedVersionInfo()
NetJobPtr netJob (new NetJob("Update Files"));
// fill netJob and operationList
- if (!processFileLists(m_currentVersionFileList, m_newVersionFileList, m_status.rootPath, m_updateFilesDir.path(), netJob, operationList))
+ if (!processFileLists(m_currentVersionFileList, m_newVersionFileList, m_status.rootPath, m_updateFilesDir.path(), netJob, m_operations))
{
emitFailed(tr("Failed to process update lists..."));
return;
}
- // write the instruction file for the file swapper
- if(!writeInstallScript(operationList, PathCombine(m_updateFilesDir.path(), "file_list.xml")))
- {
- emitFailed(tr("Failed to write update script file."));
- return;
- }
-
// Now start the download.
QObject::connect(netJob.get(), &NetJob::succeeded, this, &DownloadTask::fileDownloadFinished);
QObject::connect(netJob.get(), &NetJob::progress, this, &DownloadTask::fileDownloadProgressChanged);
@@ -170,4 +162,9 @@ QString DownloadTask::updateFilesDir()
return m_updateFilesDir.path();
}
+OperationList DownloadTask::operations()
+{
+ return m_operations;
+}
+
} \ No newline at end of file
diff --git a/logic/updater/DownloadTask.h b/logic/updater/DownloadTask.h
index 197aa3e6..3bc504fc 100644
--- a/logic/updater/DownloadTask.h
+++ b/logic/updater/DownloadTask.h
@@ -35,6 +35,9 @@ public:
/// Get the directory that will contain the update files.
QString updateFilesDir();
+ /// Get the list of operations that should be done
+ OperationList operations();
+
/// set updater download behavior
void setUseLocalUpdater(bool useLocal);
@@ -61,6 +64,8 @@ protected:
Status m_status;
+ OperationList m_operations;
+
/*!
* Temporary directory to store update files in.
* This will be set to not auto delete. Task will fail if this fails to be created.
diff --git a/logic/updater/GoUpdate.cpp b/logic/updater/GoUpdate.cpp
index d85f00d6..a43c5e7c 100644
--- a/logic/updater/GoUpdate.cpp
+++ b/logic/updater/GoUpdate.cpp
@@ -213,78 +213,4 @@ bool fixPathForOSX(QString &path)
return false;
}
}
-
-bool writeInstallScript(OperationList &opsList, QString scriptFile)
-{
- // Build the base structure of the XML document.
- QDomDocument doc;
-
- QDomElement root = doc.createElement("update");
- root.setAttribute("version", "3");
- doc.appendChild(root);
-
- QDomElement installFiles = doc.createElement("install");
- root.appendChild(installFiles);
-
- QDomElement removeFiles = doc.createElement("uninstall");
- root.appendChild(removeFiles);
-
- // Write the operation list to the XML document.
- for (Operation op : opsList)
- {
- QDomElement file = doc.createElement("file");
-
- switch (op.type)
- {
- case Operation::OP_COPY:
- {
- // Install the file.
- QDomElement name = doc.createElement("source");
- QDomElement path = doc.createElement("dest");
- QDomElement mode = doc.createElement("mode");
- name.appendChild(doc.createTextNode(op.file));
- path.appendChild(doc.createTextNode(op.dest));
- // We need to add a 0 at the beginning here, because Qt doesn't convert to octal
- // correctly.
- mode.appendChild(doc.createTextNode("0" + QString::number(op.mode, 8)));
- file.appendChild(name);
- file.appendChild(path);
- file.appendChild(mode);
- installFiles.appendChild(file);
- qDebug() << "Will install file " << op.file << " to " << op.dest;
- }
- break;
-
- case Operation::OP_DELETE:
- {
- // Delete the file.
- file.appendChild(doc.createTextNode(op.file));
- removeFiles.appendChild(file);
- qDebug() << "Will remove file" << op.file;
- }
- break;
-
- default:
- qWarning() << "Can't write update operation of type" << op.type
- << "to file. Not implemented.";
- continue;
- }
- }
-
- // Write the XML document to the file.
- QFile outFile(scriptFile);
-
- if (outFile.open(QIODevice::WriteOnly))
- {
- outFile.write(doc.toByteArray());
- }
- else
- {
- return false;
- }
-
- return true;
-}
-
-
} \ No newline at end of file
diff --git a/logic/updater/GoUpdate.h b/logic/updater/GoUpdate.h
index 941c4e3a..21976f8f 100644
--- a/logic/updater/GoUpdate.h
+++ b/logic/updater/GoUpdate.h
@@ -88,23 +88,18 @@ struct Operation
OP_DELETE,
} type;
- //! The file to operate on. If this is a DELETE or CHMOD operation, this is the file that will be modified.
+ //! The file to operate on.
QString file;
- //! The destination file. If this is a DELETE or CHMOD operation, this field will be ignored.
+ //! The destination file.
QString dest;
- //! The mode to change the source file to. Ignored if this isn't a CHMOD operation.
+ //! The mode to change the source file to.
int mode;
};
typedef QList<Operation> OperationList;
/**
- * Takes the @OperationList list and writes an install script for the updater to the update files directory.
- */
-bool writeInstallScript(OperationList& opsList, QString scriptFile);
-
-/**
* Loads the file list from the given version info JSON object into the given list.
*/
bool parseVersionInfo(const QByteArray &data, VersionFileList& list, QString &error);
diff --git a/tests/tst_DownloadTask.cpp b/tests/tst_DownloadTask.cpp
index 289aa195..eb58762b 100644
--- a/tests/tst_DownloadTask.cpp
+++ b/tests/tst_DownloadTask.cpp
@@ -69,20 +69,6 @@ slots:
{
}
- void test_writeInstallScript()
- {
- OperationList ops;
-
- ops << Operation::CopyOp("sourceOne", "destOne", 0777)
- << Operation::CopyOp("MultiMC.exe", "M/u/l/t/i/M/C/e/x/e")
- << Operation::DeleteOp("toDelete.abc");
- auto testFile = "tests/data/tst_DownloadTask-test_writeInstallScript.xml";
- const QString script = QDir::temp().absoluteFilePath("MultiMCUpdateScript.xml");
- QVERIFY(writeInstallScript(ops, script));
- QCOMPARE(TestsInternal::readFileUtf8(script).replace(QRegExp("[\r\n]+"), "\n"),
- MULTIMC_GET_TEST_FILE_UTF8(testFile).replace(QRegExp("[\r\n]+"), "\n"));
- }
-
void test_parseVersionInfo_data()
{
QTest::addColumn<QByteArray>("data");