feat(updater): download new & back up old

Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
This commit is contained in:
Rachel Powers 2023-06-04 20:58:28 -07:00
parent 3d3acb7a92
commit 50d5eb0621
No known key found for this signature in database
GPG Key ID: E10E321EB160949B
3 changed files with 166 additions and 29 deletions

View File

@ -1228,7 +1228,7 @@ install(TARGETS ${Launcher_Name}
FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR} COMPONENT Runtime FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR} COMPONENT Runtime
) )
if(WIN32 OR (DEFINED Launcher_BUILD_UPDATER AND Launcher_BUILD_UPDATER) ) if(NOT APPLE OR (DEFINED Launcher_BUILD_UPDATER AND Launcher_BUILD_UPDATER) )
# Updater # Updater
add_library(prism_updater_logic STATIC ${PRISMUPDATER_SOURCES} ${TASKS_SOURCES} ${PRISMUPDATER_UI}) add_library(prism_updater_logic STATIC ${PRISMUPDATER_SOURCES} ${TASKS_SOURCES} ${PRISMUPDATER_UI})
target_include_directories(prism_updater_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(prism_updater_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

View File

@ -22,6 +22,7 @@
#include "PrismUpdater.h" #include "PrismUpdater.h"
#include "BuildConfig.h" #include "BuildConfig.h"
#include "ui/dialogs/ProgressDialog.h"
#include <cstdlib> #include <cstdlib>
#include <iostream> #include <iostream>
@ -136,7 +137,7 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar
{ { "F", "force" }, tr("Force an update, even if one is not needed.") }, { { "F", "force" }, tr("Force an update, even if one is not needed.") },
{ { "l", "list" }, tr("List avalible releases.") }, { { "l", "list" }, tr("List avalible releases.") },
{ "debug", tr("Log debug to console.") }, { "debug", tr("Log debug to console.") },
{ { "L", "latest" }, tr("Update to the latest avalible version.") }, { { "S", "select-ui" }, tr("Select the version to install with a GUI.") },
{ { "D", "allow-downgrade" }, tr("Allow the updater to downgrade to previous verisons.") } }); { { "D", "allow-downgrade" }, tr("Allow the updater to downgrade to previous verisons.") } });
parser.addHelpOption(); parser.addHelpOption();
@ -145,10 +146,7 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar
logToConsole = parser.isSet("debug"); logToConsole = parser.isSet("debug");
auto prism_executable = QCoreApplication::applicationDirPath() + "/" + BuildConfig.LAUNCHER_APP_BINARY_NAME; auto prism_executable = QCoreApplication::applicationFilePath();
#if defined(Q_OS_WIN32)
prism_executable += ".exe";
#endif
if (BuildConfig.BUILD_PLATFORM.toLower() == "macos") if (BuildConfig.BUILD_PLATFORM.toLower() == "macos")
showFatalErrorMessage(tr("MacOS Not Supported"), tr("The updater does not support instaltions on MacOS")); showFatalErrorMessage(tr("MacOS Not Supported"), tr("The updater does not support instaltions on MacOS"));
@ -157,13 +155,15 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar
showFatalErrorMessage(tr("Unsupprted Instaltion"), tr("The updater can not find the main exacutable.")); showFatalErrorMessage(tr("Unsupprted Instaltion"), tr("The updater can not find the main exacutable."));
if (prism_executable.startsWith("/tmp/.mount_")) { if (prism_executable.startsWith("/tmp/.mount_")) {
m_appimage = true; m_isAppimage = true;
m_appimagePath = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE")); m_appimagePath = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE"));
if (m_appimagePath.isEmpty()) if (m_appimagePath.isEmpty())
showFatalErrorMessage(tr("Unsupprted Instaltion"), showFatalErrorMessage(tr("Unsupprted Instaltion"),
tr("Updater is running as misconfigured AppImage? ($APPIMAGE environment variable is missing)")); tr("Updater is running as misconfigured AppImage? ($APPIMAGE environment variable is missing)"));
} }
m_isFlatpak = DesktopServices::isFlatpak();
m_prismExecutable = prism_executable; m_prismExecutable = prism_executable;
auto prism_update_url = parser.value("update-url"); auto prism_update_url = parser.value("update-url");
@ -178,7 +178,7 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar
if (!user_version.isEmpty()) { if (!user_version.isEmpty()) {
m_userSelectedVersion = Version(user_version); m_userSelectedVersion = Version(user_version);
} }
m_updateLatest = parser.isSet("latest"); m_selectUI = parser.isSet("select-ui");
m_allowDowngrade = parser.isSet("allow-downgrade"); m_allowDowngrade = parser.isSet("allow-downgrade");
QString origcwdPath = QDir::currentPath(); QString origcwdPath = QDir::currentPath();
@ -217,7 +217,7 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar
if (QFile::exists(FS::PathCombine(m_rootPath, "portable.txt"))) { if (QFile::exists(FS::PathCombine(m_rootPath, "portable.txt"))) {
dataPath = m_rootPath; dataPath = m_rootPath;
adjustedBy = "Portable data path"; adjustedBy = "Portable data path";
m_portable = true; m_isPortable = true;
} }
#endif #endif
} }
@ -411,6 +411,24 @@ void PrismUpdaterApp::run()
return exit(0); return exit(0);
} }
if (m_isFlatpak) {
showFatalErrorMessage(tr("Updating flatpack not supported"), tr("Actions outside of checking if an update is avalible are not "
"supported when running the flatpak verison of Prism Launcher."));
return;
}
if (m_isAppimage) {
bool result = true;
if (need_update)
result = callAppimageUpdate();
return exit(result ? 0 : 1);
}
if (BuildConfig.BUILD_PLATFORM.toLower() == "linux" && !m_isPortable) {
showFatalErrorMessage(tr("Updating Not Supported"),
tr("Updating non-portable linux instalations is not supported. Please use your system package manager"));
return;
}
if (need_update || m_forceUpdate || !m_userSelectedVersion.isEmpty()) { if (need_update || m_forceUpdate || !m_userSelectedVersion.isEmpty()) {
GitHubRelease update_release = latest; GitHubRelease update_release = latest;
if (!m_userSelectedVersion.isEmpty()) { if (!m_userSelectedVersion.isEmpty()) {
@ -428,7 +446,7 @@ void PrismUpdaterApp::run()
QString("Can not find a github relase for user spesified verison %1").arg(m_userSelectedVersion.toString())); QString("Can not find a github relase for user spesified verison %1").arg(m_userSelectedVersion.toString()));
return; return;
} }
} else if (!m_updateLatest) { } else if (m_selectUI) {
update_release = selectRelease(); update_release = selectRelease();
if (!update_release.isValid()) { if (!update_release.isValid()) {
showFatalErrorMessage("No version selected.", "No version was selected."); showFatalErrorMessage("No version selected.", "No version was selected.");
@ -497,18 +515,19 @@ QList<GitHubReleaseAsset> PrismUpdaterApp::validReleaseArtifacts(const GitHubRel
{ {
QList<GitHubReleaseAsset> valid; QList<GitHubReleaseAsset> valid;
qDebug() << "Selecting best asset from" << release.tag_name << "for platfom" << BuildConfig.BUILD_PLATFORM << "portable:" << m_portable; qDebug() << "Selecting best asset from" << release.tag_name << "for platfom" << BuildConfig.BUILD_PLATFORM
<< "portable:" << m_isPortable;
if (BuildConfig.BUILD_PLATFORM.isEmpty()) if (BuildConfig.BUILD_PLATFORM.isEmpty())
qWarning() << "Build platform is not set!"; qWarning() << "Build platform is not set!";
for (auto asset : release.assets) { for (auto asset : release.assets) {
if (!m_appimage && asset.name.toLower().endsWith("appimage")) if (!m_isAppimage && asset.name.toLower().endsWith("appimage"))
continue; continue;
else if (m_appimage && !asset.name.toLower().endsWith("appimage")) else if (m_isAppimage && !asset.name.toLower().endsWith("appimage"))
continue; continue;
bool for_platform = !BuildConfig.BUILD_PLATFORM.isEmpty() && asset.name.toLower().contains(BuildConfig.BUILD_PLATFORM.toLower()); bool for_platform = !BuildConfig.BUILD_PLATFORM.isEmpty() && asset.name.toLower().contains(BuildConfig.BUILD_PLATFORM.toLower());
bool for_portable = asset.name.toLower().contains("portable"); bool for_portable = asset.name.toLower().contains("portable");
if (((m_portable && for_portable) || (!m_portable && !for_portable)) && for_platform) { if (((m_isPortable && for_portable) || (!m_isPortable && !for_portable)) && for_platform) {
valid.append(asset); valid.append(asset);
} }
} }
@ -536,35 +555,148 @@ void PrismUpdaterApp::performUpdate(const GitHubRelease& release)
GitHubReleaseAsset selected_asset; GitHubReleaseAsset selected_asset;
if (valid_assets.isEmpty()) { if (valid_assets.isEmpty()) {
showFatalErrorMessage(tr("No Valid Release Assets"), return showFatalErrorMessage(tr("No Valid Release Assets"),
tr("Github release %1 has no valid assets for this platform: %2") tr("Github release %1 has no valid assets for this platform: %2")
.arg(release.tag_name) .arg(release.tag_name)
.arg(tr("%1 portable: %2").arg(BuildConfig.BUILD_PLATFORM).arg(m_portable))); .arg(tr("%1 portable: %2").arg(BuildConfig.BUILD_PLATFORM).arg(m_isPortable)));
return;
} else if (valid_assets.length() > 1) { } else if (valid_assets.length() > 1) {
selected_asset = selectAsset(valid_assets); selected_asset = selectAsset(valid_assets);
} else { } else {
selected_asset = valid_assets.takeFirst(); selected_asset = valid_assets.takeFirst();
} }
if (! selected_asset.isValid()) { if (!selected_asset.isValid()) {
showFatalErrorMessage("No version selected.", "No version was selected."); return showFatalErrorMessage(tr("No version selected."), tr("No version was selected."));
return;
} }
qDebug() << "will intall" << selected_asset; qDebug() << "will install" << selected_asset;
auto file = downloadAsset(selected_asset);
if (!file.exists()) {
return showFatalErrorMessage(tr("Failed to Download"), tr("Failed to download the selected asset."));
}
performInstall(file);
} }
QFileInfo PrismUpdaterApp::downloadAsset(const GitHubReleaseAsset& asset) QFileInfo PrismUpdaterApp::downloadAsset(const GitHubReleaseAsset& asset)
{ {
// TODO! (researching what to do with appimages) auto temp_dir = QDir::tempPath();
auto file_url = QUrl(asset.browser_download_url);
auto out_file_path = FS::PathCombine(temp_dir, file_url.fileName());
auto download = Net::Download::makeFile(file_url, out_file_path);
auto progress_dialog = ProgressDialog();
if (progress_dialog.execWithTask(download.get()) == QDialog::Rejected)
showFatalErrorMessage(tr("Download Aborted"), tr("Download of %1 aborted by user").arg(file_url.toString()));
QFileInfo out_file(out_file_path);
return out_file;
}
bool PrismUpdaterApp::callAppimageUpdate()
{
QProcess proc = QProcess();
proc.setProgram("AppImageUpdate");
proc.setArguments({ QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE")) });
return proc.startDetached();
}
void PrismUpdaterApp::performInstall(QFileInfo file)
{
// TODO setup marker file
if (m_isPortable) {
unpackAndInstall(file);
} else {
QProcess proc = QProcess();
proc.setProgram(file.absoluteFilePath());
bool result = proc.startDetached();
exit(result ? 0 : 1);
}
}
void PrismUpdaterApp::unpackAndInstall(QFileInfo to_install_file)
{
backupAppDir();
// TODO: uppack (rename access failures)
}
void PrismUpdaterApp::backupAppDir()
{
auto manifest_path = FS::PathCombine(QCoreApplication::applicationDirPath(), "manifest.txt");
QFileInfo manifest(manifest_path);
QStringList file_list;
if (manifest.isFile()) {
// load manifest from file
try {
auto contents = QString::fromUtf8(FS::read(manifest.absoluteFilePath()));
file_list.append(contents.split('\n'));
} catch (FS::FileSystemException) {
}
}
if (file_list.isEmpty()) {
// best guess
if (BuildConfig.BUILD_PLATFORM.toLower() == "linux") {
file_list.append({ "PrismLauncher", "bin/*", "share/*", "lib/*" });
} else { // windows by process of elimination
file_list.append({
"jars/*",
"prismlauncher.exe",
"prismlauncher_filelink.exe",
"qtlogging.ini",
"imageformats/*",
"iconengines/*",
"platforms/*",
"styles/*",
"styles/*",
"tls/*",
});
}
file_list.append("portable.txt");
qDebug() << "manifest.txt empty or missing. makking best guess at files to back up.";
}
qDebug() << "Backing up" << file_list;
QDir app_dir = QCoreApplication::applicationDirPath();
auto backup_dir = FS::PathCombine(app_dir.absolutePath(), QStringLiteral("backup_") + m_prismVerison + ":" + m_prismGitCommit);
FS::ensureFolderPathExists(backup_dir);
for (auto glob : file_list) {
QDirIterator iter(app_dir.absolutePath(), QStringList({ glob }), QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot,
QDirIterator::Subdirectories);
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()) {
qDebug() << "Backing up and then removing" << to_bak_file;
FS::ensureFilePathExists(bak_path);
auto result = FS::copy(to_bak_file, bak_path)();
if (!result) {
qWarning() << "Failed to backup" << to_bak_file << "to" << bak_path;
} else {
if (!FS::deletePath(to_bak_file))
qWarning() << "Failed to remove" << to_bak_file;
}
}
}
}
} }
void PrismUpdaterApp::loadPrismVersionFromExe(const QString& exe_path) void PrismUpdaterApp::loadPrismVersionFromExe(const QString& exe_path)
{ {
QProcess proc = QProcess(); QProcess proc = QProcess();
proc.start(exe_path, { "-v" }); proc.start(exe_path, { "-v" });
proc.waitForFinished(); if (!proc.waitForStarted(5000)) // wait 5 seconds to start
return showFatalErrorMessage(tr("Failed to Check Version"), tr("Failed to launcher child launcher process to read verison."));
if (!proc.waitForFinished(5000))
return showFatalErrorMessage(tr("Failed to Check Version"), tr("Child launcher process failed."));
auto out = proc.readAll(); auto out = proc.readAll();
auto lines = out.split('\n'); auto lines = out.split('\n');
if (lines.length() < 2) if (lines.length() < 2)

View File

@ -77,8 +77,12 @@ class PrismUpdaterApp : public QApplication {
QList<GitHubReleaseAsset> validReleaseArtifacts(const GitHubRelease& release); QList<GitHubReleaseAsset> validReleaseArtifacts(const GitHubRelease& release);
GitHubReleaseAsset selectAsset(const QList<GitHubReleaseAsset>& assets); GitHubReleaseAsset selectAsset(const QList<GitHubReleaseAsset>& assets);
void performUpdate(const GitHubRelease& release); void performUpdate(const GitHubRelease& release);
void performInstall(QFileInfo file);
void unpackAndInstall(QFileInfo file);
void backupAppDir();
QFileInfo downloadAsset(const GitHubReleaseAsset& asset); QFileInfo downloadAsset(const GitHubReleaseAsset& asset);
bool callAppimageUpdate();
public slots: public slots:
void downloadError(QString reason); void downloadError(QString reason);
@ -86,11 +90,12 @@ class PrismUpdaterApp : public QApplication {
private: private:
const QString& root() { return m_rootPath; } const QString& root() { return m_rootPath; }
bool isPortable() { return m_portable; } bool isPortable() { return m_isPortable; }
QString m_rootPath; QString m_rootPath;
bool m_portable = false; bool m_isPortable = false;
bool m_appimage = false; bool m_isAppimage = false;
bool m_isFlatpak = false;
QString m_appimagePath; QString m_appimagePath;
QString m_prismExecutable; QString m_prismExecutable;
QUrl m_prismRepoUrl; QUrl m_prismRepoUrl;
@ -98,7 +103,7 @@ class PrismUpdaterApp : public QApplication {
bool m_checkOnly; bool m_checkOnly;
bool m_forceUpdate; bool m_forceUpdate;
bool m_printOnly; bool m_printOnly;
bool m_updateLatest; bool m_selectUI;
bool m_allowDowngrade; bool m_allowDowngrade;
QString m_prismBinaryName; QString m_prismBinaryName;