feat(updater): final step for portable install
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
This commit is contained in:
@ -39,7 +39,7 @@
|
||||
#include <QProcess>
|
||||
#include <memory>
|
||||
|
||||
#include <qglobal.h>
|
||||
#include <QProgressDialog>
|
||||
#include <sys.h>
|
||||
|
||||
#if defined Q_OS_WIN32
|
||||
@ -106,7 +106,7 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext& context, const QSt
|
||||
#if defined Q_OS_WIN32
|
||||
|
||||
// taken from https://stackoverflow.com/a/25927081
|
||||
// getting a proper output to console with redirection support on windows is apearently hell
|
||||
// getting a proper output to console with redirection support on windows is apparently hell
|
||||
void BindCrtHandlesToStdHandles(bool bindStdIn, bool bindStdOut, bool bindStdErr)
|
||||
{
|
||||
// Re-initialize the C runtime "FILE" handles with clean handles bound to "nul". We do this because it has been
|
||||
@ -266,8 +266,11 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar
|
||||
m_prismExecutable = prism_executable;
|
||||
|
||||
auto prism_update_url = parser.value("update-url");
|
||||
if (prism_update_url.isEmpty())
|
||||
prism_update_url = BuildConfig.UPDATER_GITHUB_REPO;
|
||||
if (prism_update_url.isEmpty())
|
||||
prism_update_url = "https://github.com/PrismLauncher/PrismLauncher";
|
||||
|
||||
m_prismRepoUrl = QUrl::fromUserInput(prism_update_url);
|
||||
|
||||
m_checkOnly = parser.isSet("check-only");
|
||||
@ -334,6 +337,8 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar
|
||||
#endif
|
||||
}
|
||||
|
||||
m_updateLogPath = FS::PathCombine(m_dataPath, "prism_launcher_update.log");
|
||||
|
||||
{ // setup logging
|
||||
static const QString logBase = BuildConfig.LAUNCHER_NAME + "Updater" + (m_checkOnly ? "-CheckOnly" : "") + "-%0.log";
|
||||
auto moveFile = [](const QString& oldName, const QString& newName) {
|
||||
@ -424,14 +429,16 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar
|
||||
qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT;
|
||||
qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC;
|
||||
if (adjustedBy.size()) {
|
||||
qDebug() << "Work dir before adjustment : " << origCwdPath;
|
||||
qDebug() << "Work dir after adjustment : " << QDir::currentPath();
|
||||
qDebug() << "Data dir before adjustment : " << origCwdPath;
|
||||
qDebug() << "Data dir after adjustment : " << m_dataPath;
|
||||
qDebug() << "Adjusted by : " << adjustedBy;
|
||||
} else {
|
||||
qDebug() << "Work dir : " << QDir::currentPath();
|
||||
qDebug() << "Data dir : " << QDir::currentPath();
|
||||
}
|
||||
qDebug() << "Work dir : " << QDir::currentPath();
|
||||
qDebug() << "Binary path : " << binPath;
|
||||
qDebug() << "Application root path : " << m_rootPath;
|
||||
qDebug() << "Portable install : " << m_isPortable;
|
||||
qDebug() << "<> Paths set.";
|
||||
}
|
||||
|
||||
@ -442,14 +449,28 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar
|
||||
m_network->setProxy(proxy);
|
||||
}
|
||||
|
||||
QMetaObject::invokeMethod(this, &PrismUpdaterApp::loadReleaseList, Qt::QueuedConnection);
|
||||
auto marker_file_path = QDir(applicationDirPath()).absoluteFilePath(".prism_launcher_updater_unpack.marker");
|
||||
auto marker_file = QFileInfo(marker_file_path);
|
||||
if (marker_file.exists()) {
|
||||
auto target_dir = QString(FS::read(marker_file_path)).trimmed();
|
||||
if (target_dir.isEmpty()) {
|
||||
qWarning() << "Empty updater marker file contains no install target. making best guess of parent dir";
|
||||
target_dir = QDir(applicationDirPath()).absoluteFilePath("..");
|
||||
}
|
||||
|
||||
QMetaObject::invokeMethod(
|
||||
this, [this, target_dir]() { moveAndFinishUpdate(target_dir); }, Qt::QueuedConnection);
|
||||
|
||||
} else {
|
||||
QMetaObject::invokeMethod(this, &PrismUpdaterApp::loadReleaseList, Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
PrismUpdaterApp::~PrismUpdaterApp()
|
||||
{
|
||||
qDebug() << "updater shutting down";
|
||||
// Shut down logger by setting the logger function to nothing
|
||||
qInstallMessageHandler(nullptr);
|
||||
qDebug() << "updater shutting down";
|
||||
|
||||
#if defined Q_OS_WIN32
|
||||
// Detach from Windows console
|
||||
@ -576,6 +597,81 @@ void PrismUpdaterApp::run()
|
||||
exit(0);
|
||||
}
|
||||
|
||||
void PrismUpdaterApp::moveAndFinishUpdate(QDir target)
|
||||
{
|
||||
logUpdate("Finishing update process");
|
||||
auto manifest_path = FS::PathCombine(applicationDirPath(), "manifest.txt");
|
||||
QFileInfo manifest(manifest_path);
|
||||
|
||||
auto app_dir = QDir(applicationDirPath());
|
||||
|
||||
QStringList file_list;
|
||||
if (manifest.isFile()) {
|
||||
// load manifest from file
|
||||
logUpdate(tr("Reading manifest from %1").arg(manifest.absoluteFilePath()));
|
||||
try {
|
||||
auto contents = QString::fromUtf8(FS::read(manifest.absoluteFilePath()));
|
||||
auto files = contents.split('\n');
|
||||
for (auto file : files) {
|
||||
file_list.append(file.trimmed());
|
||||
}
|
||||
} catch (FS::FileSystemException) {
|
||||
}
|
||||
}
|
||||
|
||||
if (file_list.isEmpty()) {
|
||||
logUpdate(tr("Manifest empty, making best guess of the directory contents of %1").arg(applicationDirPath()));
|
||||
auto entries = target.entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
|
||||
for (auto entry : entries) {
|
||||
file_list.append(entry.fileName());
|
||||
}
|
||||
}
|
||||
logUpdate(tr("Installing the following to %1 :\n %2").arg(target.absolutePath()).arg(file_list.join(",\n ")));
|
||||
|
||||
bool error = false;
|
||||
|
||||
QProgressDialog progress(tr("Backing up install at %1").arg(applicationDirPath()), "", 0, file_list.length());
|
||||
progress.setCancelButton(nullptr);
|
||||
progress.show();
|
||||
QCoreApplication::processEvents();
|
||||
|
||||
int i = 0;
|
||||
for (auto glob : file_list) {
|
||||
QDirIterator iter(applicationDirPath(), QStringList({ glob }), QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
progress.setValue(i);
|
||||
QCoreApplication::processEvents();
|
||||
while (iter.hasNext()) {
|
||||
auto to_install_file = iter.next();
|
||||
auto rel_path = app_dir.relativeFilePath(to_install_file);
|
||||
auto install_path = FS::PathCombine(target.absolutePath(), rel_path);
|
||||
logUpdate(tr("Installing %1 from %2").arg(install_path).arg(to_install_file));
|
||||
FS::ensureFilePathExists(install_path);
|
||||
auto result = FS::copy(to_install_file, install_path)();
|
||||
if (!result) {
|
||||
error = true;
|
||||
logUpdate(tr("Failed copy %1 to %2").arg(to_install_file).arg(install_path));
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
progress.setValue(i);
|
||||
QCoreApplication::processEvents();
|
||||
|
||||
if (error) {
|
||||
logUpdate(tr("There were errors installing the update."));
|
||||
auto fail_marker = FS::PathCombine(m_dataPath, ".prism_launcher_update.fail");
|
||||
FS::move(m_updateLogPath, fail_marker);
|
||||
} else {
|
||||
logUpdate(tr("Update succeed."));
|
||||
auto success_marker = FS::PathCombine(m_dataPath, ".prism_launcher_update.success");
|
||||
FS::move(m_updateLogPath, success_marker);
|
||||
}
|
||||
auto update_lock_path = FS::PathCombine(m_dataPath, ".prism_launcher_update.lock");
|
||||
FS::deletePath(update_lock_path);
|
||||
|
||||
exit(error ? 1 : 0);
|
||||
}
|
||||
|
||||
void PrismUpdaterApp::printReleases()
|
||||
{
|
||||
for (auto release : m_releases) {
|
||||
@ -739,24 +835,107 @@ bool PrismUpdaterApp::callAppImageUpdate()
|
||||
|
||||
void PrismUpdaterApp::clearUpdateLog()
|
||||
{
|
||||
auto update_log_path = FS::PathCombine(m_dataPath, "prism_launcher_update.log");
|
||||
QFile::remove(update_log_path);
|
||||
QFile::remove(m_updateLogPath);
|
||||
}
|
||||
|
||||
void PrismUpdaterApp::logUpdate(const QString& msg)
|
||||
{
|
||||
qDebug() << qUtf8Printable(msg);
|
||||
auto update_log_path = FS::PathCombine(m_dataPath, "prism_launcher_update.log");
|
||||
FS::append(update_log_path, QStringLiteral("%1\n").arg(msg).toUtf8());
|
||||
FS::append(m_updateLogPath, QStringLiteral("%1\n").arg(msg).toUtf8());
|
||||
}
|
||||
|
||||
std::tuple<QDateTime, QString, QString, QString, QString> read_lock_File(const QString& path)
|
||||
{
|
||||
auto contents = QString(FS::read(path));
|
||||
auto lines = contents.split('\n');
|
||||
|
||||
QDateTime timestamp;
|
||||
QString from, to, target, data_path;
|
||||
for (auto line : lines) {
|
||||
auto index = line.indexOf("=");
|
||||
if (index < 0)
|
||||
continue;
|
||||
auto left = line.left(index);
|
||||
auto right = line.mid(index + 1);
|
||||
if (left.toLower() == "timestamp") {
|
||||
timestamp = QDateTime::fromString(right, Qt::ISODate);
|
||||
} else if (left.toLower() == "from") {
|
||||
from = right;
|
||||
} else if (left.toLower() == "to") {
|
||||
to = right;
|
||||
} else if (left.toLower() == "target") {
|
||||
target = right;
|
||||
} else if (left.toLower() == "data_path") {
|
||||
data_path = right;
|
||||
}
|
||||
}
|
||||
return std::make_tuple(timestamp, from, to, target, data_path);
|
||||
}
|
||||
|
||||
bool write_lock_file(const QString& path, QDateTime timestamp, QString from, QString to, QString target, QString data_path)
|
||||
{
|
||||
try {
|
||||
FS::write(path, QStringLiteral("TIMESTAMP=%1\nFROM=%2\nTO=%3\nTARGET=%4\nDATA_PATH=%5\n")
|
||||
.arg(timestamp.toString(Qt::ISODate))
|
||||
.arg(from)
|
||||
.arg(to)
|
||||
.arg(target)
|
||||
.arg(data_path)
|
||||
.toUtf8());
|
||||
} catch (FS::FileSystemException err) {
|
||||
qWarning() << "Error writing lockfile:" << err.what() << "\n" << err.cause();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PrismUpdaterApp::performInstall(QFileInfo file)
|
||||
{
|
||||
qDebug() << "starting install";
|
||||
auto update_lock_path = FS::PathCombine(m_dataPath, ".prism_launcher_update.lock");
|
||||
FS::write(update_lock_path, QStringLiteral("FROM=%1\nTO=%2\n").arg(m_prismVersion).arg(m_install_release.tag_name).toUtf8());
|
||||
QFileInfo update_lock(update_lock_path);
|
||||
if (update_lock.exists()) {
|
||||
auto [timestamp, from, to, target, data_path] = read_lock_File(update_lock_path);
|
||||
auto msg = tr("Update already in progress\n");
|
||||
auto infoMsg =
|
||||
tr("This installation has a update lock file present at: %1\n"
|
||||
"\n"
|
||||
"Timestamp: %2\n"
|
||||
"Updating from version %3 to %4\n"
|
||||
"Target install path: %5\n"
|
||||
"Data Path: %6"
|
||||
"\n"
|
||||
"This likely means that a previous update attempt failed. Please ensure your installation is in working order before "
|
||||
"proceeding.\n"
|
||||
"Check the Prism Launcher updater log at \n"
|
||||
"%7\n"
|
||||
"for details on the last update attempt.\n"
|
||||
"\n"
|
||||
"To overwrite this lock and proceed with this update anyway, select \"Ignore\" below.")
|
||||
.arg(update_lock_path)
|
||||
.arg(timestamp.toString(Qt::ISODate), from, to, target, data_path)
|
||||
.arg(m_updateLogPath);
|
||||
QMessageBox msgBox;
|
||||
msgBox.setText(msg);
|
||||
msgBox.setInformativeText(infoMsg);
|
||||
msgBox.setStandardButtons(QMessageBox::Ignore | QMessageBox::Cancel);
|
||||
msgBox.setDefaultButton(QMessageBox::Cancel);
|
||||
switch (msgBox.exec()) {
|
||||
case QMessageBox::Ignore:
|
||||
break;
|
||||
case QMessageBox::Cancel:
|
||||
[[fallthrough]];
|
||||
default:
|
||||
return showFatalErrorMessage(tr("Update Aborted"), tr("The update attempt was aborted"));
|
||||
}
|
||||
}
|
||||
write_lock_file(update_lock_path, QDateTime::currentDateTime(), m_prismVersion, m_install_release.tag_name, applicationDirPath(),
|
||||
m_dataPath);
|
||||
clearUpdateLog();
|
||||
|
||||
auto changelog_path = FS::PathCombine(m_dataPath, ".prism_launcher_update.changelog");
|
||||
FS::write(changelog_path, m_install_release.body.toUtf8());
|
||||
|
||||
logUpdate(tr("Updating from %1 to %2").arg(m_prismVersion).arg(m_install_release.tag_name));
|
||||
if (m_isPortable || file.suffix().toLower() == "zip") {
|
||||
logUpdate(tr("Updating portable install at %1").arg(applicationDirPath()));
|
||||
@ -779,14 +958,13 @@ void PrismUpdaterApp::unpackAndInstall(QFileInfo archive)
|
||||
if (auto loc = unpackArchive(archive)) {
|
||||
auto marker_file_path = loc.value().absoluteFilePath(".prism_launcher_updater_unpack.marker");
|
||||
FS::write(marker_file_path, applicationDirPath().toUtf8());
|
||||
auto new_updater_path = loc.value().absoluteFilePath("prismlauncher-updater");
|
||||
auto new_updater_path = loc.value().absoluteFilePath("prismlauncher_updater");
|
||||
#if defined Q_OS_WIN32
|
||||
new_updater_path.append(".exe");
|
||||
#endif
|
||||
logUpdate(tr("Starting new updater at '%1'").arg(new_updater_path));
|
||||
QProcess proc = QProcess();
|
||||
proc.startDetached(new_updater_path, {}, loc.value().absolutePath());
|
||||
if (!proc.waitForStarted(5000)) {
|
||||
if (!proc.startDetached(new_updater_path, { "-d", m_dataPath }, loc.value().absolutePath())) {
|
||||
logUpdate(tr("Failed to launch '%1' %2").arg(new_updater_path).arg(proc.errorString()));
|
||||
return exit(10);
|
||||
}
|
||||
@ -807,7 +985,10 @@ void PrismUpdaterApp::backupAppDir()
|
||||
logUpdate(tr("Reading manifest from %1").arg(manifest.absoluteFilePath()));
|
||||
try {
|
||||
auto contents = QString::fromUtf8(FS::read(manifest.absoluteFilePath()));
|
||||
file_list.append(contents.split('\n'));
|
||||
auto files = contents.split('\n');
|
||||
for (auto file : files) {
|
||||
file_list.append(file.trimmed());
|
||||
}
|
||||
} catch (FS::FileSystemException) {
|
||||
}
|
||||
}
|
||||
@ -815,19 +996,19 @@ void PrismUpdaterApp::backupAppDir()
|
||||
if (file_list.isEmpty()) {
|
||||
// best guess
|
||||
if (BuildConfig.BUILD_PLATFORM.toLower() == "linux") {
|
||||
file_list.append({ "PrismLauncher", "bin/*", "share/*", "lib/*" });
|
||||
file_list.append({ "PrismLauncher", "bin", "share", "lib" });
|
||||
} else { // windows by process of elimination
|
||||
file_list.append({
|
||||
"jars/*",
|
||||
"jars",
|
||||
"prismlauncher.exe",
|
||||
"prismlauncher_filelink.exe",
|
||||
"prismlauncher_updater.exe",
|
||||
"qtlogging.ini",
|
||||
"imageformats/*",
|
||||
"iconengines/*",
|
||||
"platforms/*",
|
||||
"styles/*",
|
||||
"styles/*",
|
||||
"tls/*",
|
||||
"imageformats",
|
||||
"iconengines",
|
||||
"platforms",
|
||||
"styles",
|
||||
"tls",
|
||||
"qt.conf",
|
||||
"Qt*.dll",
|
||||
});
|
||||
@ -839,32 +1020,41 @@ void PrismUpdaterApp::backupAppDir()
|
||||
|
||||
QDir app_dir = QCoreApplication::applicationDirPath();
|
||||
auto backup_dir = FS::PathCombine(
|
||||
app_dir.absolutePath(), QStringLiteral("backup_") +
|
||||
QString(m_prismVersion).replace(QRegularExpression("[" + QRegularExpression::escape("\\/:*?\"<>|") + "]"), QString("_")) +
|
||||
"-" + m_prismGitCommit);
|
||||
app_dir.absolutePath(),
|
||||
QStringLiteral("backup_") +
|
||||
QString(m_prismVersion).replace(QRegularExpression("[" + QRegularExpression::escape("\\/:*?\"<>|") + "]"), QString("_")) + "-" +
|
||||
m_prismGitCommit);
|
||||
FS::ensureFolderPathExists(backup_dir);
|
||||
auto backup_marker_path = FS::PathCombine(m_dataPath, ".prism_launcher_update_backup_path.txt");
|
||||
FS::write(backup_marker_path, backup_dir.toUtf8());
|
||||
|
||||
QProgressDialog progress(tr("Backing up install at %1").arg(applicationDirPath()), "", 0, file_list.length());
|
||||
progress.setCancelButton(nullptr);
|
||||
progress.show();
|
||||
QCoreApplication::processEvents();
|
||||
int i = 0;
|
||||
for (auto glob : file_list) {
|
||||
QDirIterator iter(app_dir.absolutePath(), QStringList({ glob }), QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot,
|
||||
QDirIterator::Subdirectories);
|
||||
QDirIterator iter(app_dir.absolutePath(), QStringList({ glob }), QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
progress.setValue(i);
|
||||
QCoreApplication::processEvents();
|
||||
while (iter.hasNext()) {
|
||||
auto to_bak_file = iter.next();
|
||||
auto rel_path = app_dir.relativeFilePath(to_bak_file);
|
||||
auto bak_path = FS::PathCombine(backup_dir, rel_path);
|
||||
|
||||
if (QFileInfo(to_bak_file).isFile()) {
|
||||
logUpdate(tr("Backing up and then removing %1").arg(to_bak_file));
|
||||
FS::ensureFilePathExists(bak_path);
|
||||
auto result = FS::copy(to_bak_file, bak_path)();
|
||||
if (!result) {
|
||||
logUpdate(tr("Failed to backup %1 to %2").arg(to_bak_file).arg(bak_path));
|
||||
} else {
|
||||
if (!FS::deletePath(to_bak_file))
|
||||
logUpdate(tr("Failed to remove %1").arg(to_bak_file));
|
||||
}
|
||||
logUpdate(tr("Backing up and then removing %1").arg(to_bak_file));
|
||||
FS::ensureFilePathExists(bak_path);
|
||||
auto result = FS::copy(to_bak_file, bak_path)();
|
||||
if (!result) {
|
||||
logUpdate(tr("Failed to backup %1 to %2").arg(to_bak_file).arg(bak_path));
|
||||
} else {
|
||||
if (!FS::deletePath(to_bak_file))
|
||||
logUpdate(tr("Failed to remove %1").arg(to_bak_file));
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
progress.setValue(i);
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
|
||||
std::optional<QDir> PrismUpdaterApp::unpackArchive(QFileInfo archive)
|
||||
|
@ -85,7 +85,7 @@ class PrismUpdaterApp : public QApplication {
|
||||
QFileInfo downloadAsset(const GitHubReleaseAsset& asset);
|
||||
bool callAppImageUpdate();
|
||||
|
||||
void moveAndPostProcess(QDir target);
|
||||
void moveAndFinishUpdate(QDir target);
|
||||
|
||||
public slots:
|
||||
void downloadError(QString reason);
|
||||
@ -98,7 +98,6 @@ class PrismUpdaterApp : public QApplication {
|
||||
void clearUpdateLog();
|
||||
void logUpdate(const QString& msg);
|
||||
|
||||
|
||||
QString m_rootPath;
|
||||
QString m_dataPath;
|
||||
bool m_isPortable = false;
|
||||
@ -114,6 +113,8 @@ class PrismUpdaterApp : public QApplication {
|
||||
bool m_selectUI;
|
||||
bool m_allowDowngrade;
|
||||
|
||||
QString m_updateLogPath;
|
||||
|
||||
QString m_prismBinaryName;
|
||||
QString m_prismVersion;
|
||||
int m_prismVersionMajor = -1;
|
||||
|
@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>478</width>
|
||||
<height>517</height>
|
||||
<width>468</width>
|
||||
<height>385</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
Reference in New Issue
Block a user