feat(updater): unpack portable archive

Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
This commit is contained in:
Rachel Powers 2023-06-18 22:34:23 -07:00
parent 50d5eb0621
commit 44bc60021d
No known key found for this signature in database
GPG Key ID: E10E321EB160949B
5 changed files with 186 additions and 58 deletions

View File

@ -598,11 +598,14 @@ set(PRISMUPDATER_SOURCES
Version.cpp Version.cpp
Markdown.h Markdown.h
# Zip
MMCZip.h
MMCZip.cpp
# Time # Time
MMCTime.h MMCTime.h
MMCTime.cpp MMCTime.cpp
net/ByteArraySink.h net/ByteArraySink.h
net/ChecksumValidator.h net/ChecksumValidator.h
net/Download.cpp net/Download.cpp

View File

@ -193,6 +193,40 @@ void write(const QString& filename, const QByteArray& data)
} }
} }
void appendSafe(const QString& filename, const QByteArray& data)
{
ensureExists(QFileInfo(filename).dir());
QByteArray buffer;
try {
buffer = read(filename);
} catch (FileSystemException) {
buffer = QByteArray();
}
buffer.append(data);
QSaveFile file(filename);
if (!file.open(QSaveFile::WriteOnly)) {
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
}
if (buffer.size() != file.write(buffer)) {
throw FileSystemException("Error writing data to " + filename + ": " + file.errorString());
}
if (!file.commit()) {
throw FileSystemException("Error while committing data to " + filename + ": " + file.errorString());
}
}
void append(const QString& filename, const QByteArray& data)
{
ensureExists(QFileInfo(filename).dir());
QFile file(filename);
if (!file.open(QFile::Append)) {
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
}
if (data.size() != file.write(data)) {
throw FileSystemException("Error writing data to " + filename + ": " + file.errorString());
}
}
QByteArray read(const QString& filename) QByteArray read(const QString& filename)
{ {
QFile file(filename); QFile file(filename);

View File

@ -60,6 +60,18 @@ class FileSystemException : public ::Exception {
*/ */
void write(const QString& filename, const QByteArray& data); void write(const QString& filename, const QByteArray& data);
/**
* append data to a file safely
*/
void appendSafe(const QString& filename, const QByteArray& data);
/**
* append data to a file
*/
void append(const QString& filename, const QByteArray& data);
/** /**
* read data from a file safely\ * read data from a file safely\
*/ */

View File

@ -80,6 +80,8 @@ namespace fs = ghc::filesystem;
#include "net/Download.h" #include "net/Download.h"
#include "net/RawHeaderProxy.h" #include "net/RawHeaderProxy.h"
#include <MMCZip.h>
/** output to the log file */ /** output to the log file */
void appDebugOutput(QtMsgType type, const QMessageLogContext& context, const QString& msg) void appDebugOutput(QtMsgType type, const QMessageLogContext& context, const QString& msg)
{ {
@ -103,14 +105,16 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar
#if defined Q_OS_WIN32 #if defined Q_OS_WIN32
// attach the parent console // attach the parent console
if (AttachConsole(ATTACH_PARENT_PROCESS)) { if (AttachConsole(ATTACH_PARENT_PROCESS)) {
FILE* _stream;
errno_t err;
// if attach succeeds, reopen and sync all the i/o // if attach succeeds, reopen and sync all the i/o
if (freopen("CON", "w", stdout)) { if (err = freopen_s(&_stream, "CON", "w", stdout); err == 0) {
std::cout.sync_with_stdio(); std::cout.sync_with_stdio();
} }
if (freopen("CON", "w", stderr)) { if (err = freopen_s(&_stream, "CON", "w", stderr); err == 0) {
std::cerr.sync_with_stdio(); std::cerr.sync_with_stdio();
} }
if (freopen("CON", "r", stdin)) { if (err = freopen_s(&_stream, "CON", "r", stdin); err == 0) {
std::cin.sync_with_stdio(); std::cin.sync_with_stdio();
} }
auto out = GetStdHandle(STD_OUTPUT_HANDLE); auto out = GetStdHandle(STD_OUTPUT_HANDLE);
@ -125,20 +129,20 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar
setApplicationName(BuildConfig.LAUNCHER_NAME + "Updater"); setApplicationName(BuildConfig.LAUNCHER_NAME + "Updater");
setApplicationVersion(BuildConfig.printableVersionString() + "\n" + BuildConfig.GIT_COMMIT); setApplicationVersion(BuildConfig.printableVersionString() + "\n" + BuildConfig.GIT_COMMIT);
// Commandline parsing // Command line parsing
QCommandLineParser parser; QCommandLineParser parser;
parser.setApplicationDescription(QObject::tr("An auto-updater for Prism Launcher")); parser.setApplicationDescription(QObject::tr("An auto-updater for Prism Launcher"));
parser.addOptions({ { { "d", "dir" }, tr("Use a custom path as application root (use '.' for current directory)."), tr("directory") }, parser.addOptions({ { { "d", "dir" }, tr("Use a custom path as application root (use '.' for current directory)."), tr("directory") },
{ { "I", "install-version" }, "Install a spesfic version.", tr("version name") }, { { "I", "install-version" }, "Install a specific version.", tr("version name") },
{ { "U", "update-url" }, tr("Update from the spesified repo."), tr("github repo url") }, { { "U", "update-url" }, tr("Update from the specified repo."), tr("github repo url") },
{ { "c", "check-only" }, { { "c", "check-only" },
tr("Only check if an update is needed. Exit status 100 if true, 0 if false (or non 0 if there was an error).") }, tr("Only check if an update is needed. Exit status 100 if true, 0 if false (or non 0 if there was an error).") },
{ { "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 available releases.") },
{ "debug", tr("Log debug to console.") }, { "debug", tr("Log debug to console.") },
{ { "S", "select-ui" }, tr("Select the version to install with a GUI.") }, { { "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 versions.") } });
parser.addHelpOption(); parser.addHelpOption();
parser.addVersionOption(); parser.addVersionOption();
@ -149,16 +153,16 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar
auto prism_executable = QCoreApplication::applicationFilePath(); auto prism_executable = QCoreApplication::applicationFilePath();
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 installations on MacOS"));
if (!QFileInfo(prism_executable).isFile()) if (!QFileInfo(prism_executable).isFile())
showFatalErrorMessage(tr("Unsupprted Instaltion"), tr("The updater can not find the main exacutable.")); showFatalErrorMessage(tr("Unsupported Installation"), tr("The updater can not find the main executable."));
if (prism_executable.startsWith("/tmp/.mount_")) { if (prism_executable.startsWith("/tmp/.mount_")) {
m_isAppimage = 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("Unsupported Installation"),
tr("Updater is running as misconfigured AppImage? ($APPIMAGE environment variable is missing)")); tr("Updater is running as misconfigured AppImage? ($APPIMAGE environment variable is missing)"));
} }
@ -181,7 +185,7 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar
m_selectUI = parser.isSet("select-ui"); 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();
QString binPath = applicationDirPath(); QString binPath = applicationDirPath();
{ // find data director { // find data director
@ -200,22 +204,21 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar
} }
QString adjustedBy; QString adjustedBy;
QString dataPath;
// change folder // change folder
QString dirParam = parser.value("dir"); QString dirParam = parser.value("dir");
if (!dirParam.isEmpty()) { if (!dirParam.isEmpty()) {
// the dir param. it makes multimc data path point to whatever the user specified // the dir param. it makes prism launcher data path point to whatever the user specified
// on command line // on command line
adjustedBy = "Command line"; adjustedBy = "Command line";
dataPath = dirParam; m_dataPath = dirParam;
} else { } else {
QDir foo(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "..")); QDir foo(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), ".."));
dataPath = foo.absolutePath(); m_dataPath = foo.absolutePath();
adjustedBy = "Persistent data path"; adjustedBy = "Persistent data path";
#ifndef Q_OS_MACOS #ifndef Q_OS_MACOS
if (QFile::exists(FS::PathCombine(m_rootPath, "portable.txt"))) { if (QFile::exists(FS::PathCombine(m_rootPath, "portable.txt"))) {
dataPath = m_rootPath; m_dataPath = m_rootPath;
adjustedBy = "Portable data path"; adjustedBy = "Portable data path";
m_isPortable = true; m_isPortable = true;
} }
@ -230,8 +233,6 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar
QFile::remove(oldName); QFile::remove(oldName);
}; };
moveFile(logBase.arg(3), logBase.arg(4));
moveFile(logBase.arg(2), logBase.arg(3));
moveFile(logBase.arg(1), logBase.arg(2)); moveFile(logBase.arg(1), logBase.arg(2));
moveFile(logBase.arg(0), logBase.arg(1)); moveFile(logBase.arg(0), logBase.arg(1));
@ -244,7 +245,7 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar
"(%1)\n" "(%1)\n"
"\n" "\n"
"The launcher cannot continue until you fix this problem.") "The launcher cannot continue until you fix this problem.")
.arg(dataPath)); .arg(m_dataPath));
return; return;
} }
qInstallMessageHandler(appDebugOutput); qInstallMessageHandler(appDebugOutput);
@ -266,7 +267,7 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar
bool foundLoggingRules = false; bool foundLoggingRules = false;
auto logRulesFile = QStringLiteral("qtlogging.ini"); auto logRulesFile = QStringLiteral("qtlogging.ini");
auto logRulesPath = FS::PathCombine(dataPath, logRulesFile); auto logRulesPath = FS::PathCombine(m_dataPath, logRulesFile);
qDebug() << "Testing" << logRulesPath << "..."; qDebug() << "Testing" << logRulesPath << "...";
foundLoggingRules = QFile::exists(logRulesPath); foundLoggingRules = QFile::exists(logRulesPath);
@ -314,7 +315,7 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar
qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT; qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT;
qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC; qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC;
if (adjustedBy.size()) { if (adjustedBy.size()) {
qDebug() << "Work dir before adjustment : " << origcwdPath; qDebug() << "Work dir before adjustment : " << origCwdPath;
qDebug() << "Work dir after adjustment : " << QDir::currentPath(); qDebug() << "Work dir after adjustment : " << QDir::currentPath();
qDebug() << "Adjusted by : " << adjustedBy; qDebug() << "Adjusted by : " << adjustedBy;
} else { } else {
@ -394,7 +395,7 @@ void PrismUpdaterApp::run()
loadPrismVersionFromExe(m_prismExecutable); loadPrismVersionFromExe(m_prismExecutable);
m_status = Succeeded; m_status = Succeeded;
qDebug() << "Executable reports as:" << m_prismBinaryName << "version:" << m_prismVerison; qDebug() << "Executable reports as:" << m_prismBinaryName << "version:" << m_prismVersion;
qDebug() << "Version major:" << m_prismVersionMajor; qDebug() << "Version major:" << m_prismVersionMajor;
qDebug() << "Verison minor:" << m_prismVersionMinor; qDebug() << "Verison minor:" << m_prismVersionMinor;
qDebug() << "Verison channel:" << m_prsimVersionChannel; qDebug() << "Verison channel:" << m_prsimVersionChannel;
@ -412,8 +413,8 @@ void PrismUpdaterApp::run()
} }
if (m_isFlatpak) { if (m_isFlatpak) {
showFatalErrorMessage(tr("Updating flatpack not supported"), tr("Actions outside of checking if an update is avalible are not " showFatalErrorMessage(tr("Updating flatpack not supported"), tr("Actions outside of checking if an update is available are not "
"supported when running the flatpak verison of Prism Launcher.")); "supported when running the flatpak version of Prism Launcher."));
return; return;
} }
if (m_isAppimage) { if (m_isAppimage) {
@ -425,7 +426,7 @@ void PrismUpdaterApp::run()
if (BuildConfig.BUILD_PLATFORM.toLower() == "linux" && !m_isPortable) { if (BuildConfig.BUILD_PLATFORM.toLower() == "linux" && !m_isPortable) {
showFatalErrorMessage(tr("Updating Not Supported"), showFatalErrorMessage(tr("Updating Not Supported"),
tr("Updating non-portable linux instalations is not supported. Please use your system package manager")); tr("Updating non-portable linux installations is not supported. Please use your system package manager"));
return; return;
} }
@ -443,7 +444,7 @@ void PrismUpdaterApp::run()
if (!found) { if (!found) {
showFatalErrorMessage( showFatalErrorMessage(
"No release for version!", "No release for version!",
QString("Can not find a github relase for user spesified verison %1").arg(m_userSelectedVersion.toString())); QString("Can not find a github release for specified version %1").arg(m_userSelectedVersion.toString()));
return; return;
} }
} else if (m_selectUI) { } else if (m_selectUI) {
@ -481,7 +482,7 @@ QList<GitHubRelease> PrismUpdaterApp::newerReleases()
{ {
QList<GitHubRelease> newer; QList<GitHubRelease> newer;
for (auto rls : nonDraftReleases()) { for (auto rls : nonDraftReleases()) {
if (rls.version > m_prismVerison) if (rls.version > m_prismVersion)
newer.append(rls); newer.append(rls);
} }
return newer; return newer;
@ -500,7 +501,7 @@ GitHubRelease PrismUpdaterApp::selectRelease()
if (releases.isEmpty()) if (releases.isEmpty())
return {}; return {};
SelectReleaseDialog dlg(Version(m_prismVerison), releases); SelectReleaseDialog dlg(Version(m_prismVersion), releases);
auto result = dlg.exec(); auto result = dlg.exec();
if (result == QDialog::Rejected) { if (result == QDialog::Rejected) {
@ -515,7 +516,7 @@ 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 qDebug() << "Selecting best asset from" << release.tag_name << "for platform" << BuildConfig.BUILD_PLATFORM
<< "portable:" << m_isPortable; << "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!";
@ -549,9 +550,10 @@ GitHubReleaseAsset PrismUpdaterApp::selectAsset(const QList<GitHubReleaseAsset>&
void PrismUpdaterApp::performUpdate(const GitHubRelease& release) void PrismUpdaterApp::performUpdate(const GitHubRelease& release)
{ {
m_install_release = release;
qDebug() << "Updating to" << release.tag_name; qDebug() << "Updating to" << release.tag_name;
auto valid_assets = validReleaseArtifacts(release); auto valid_assets = validReleaseArtifacts(release);
qDebug() << "vallid release assets:" << valid_assets; qDebug() << "valid release assets:" << valid_assets;
GitHubReleaseAsset selected_asset; GitHubReleaseAsset selected_asset;
if (valid_assets.isEmpty()) { if (valid_assets.isEmpty()) {
@ -604,12 +606,32 @@ bool PrismUpdaterApp::callAppimageUpdate()
return proc.startDetached(); return proc.startDetached();
} }
void PrismUpdaterApp::clearUpdateLog()
{
auto update_log_path = FS::PathCombine(m_dataPath, "prism_launcher_update.log");
QFile::remove(update_log_path);
}
void PrismUpdaterApp::logUpdate(const QString& msg)
{
qDebug() << msg;
auto update_log_path = FS::PathCombine(m_dataPath, "prism_launcher_update.log");
FS::append(update_log_path, QStringLiteral("%1\n").arg(msg).toUtf8());
}
void PrismUpdaterApp::performInstall(QFileInfo file) void PrismUpdaterApp::performInstall(QFileInfo file)
{ {
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());
clearUpdateLog();
logUpdate(tr("Updating from %1 to %2").arg(m_prismVersion).arg(m_install_release.tag_name));
// TODO setup marker file // TODO setup marker file
if (m_isPortable) { if (m_isPortable) {
logUpdate(tr("Updating portable install at %1").arg(applicationDirPath()));
unpackAndInstall(file); unpackAndInstall(file);
} else { } else {
logUpdate(tr("Running installer file at %1").arg(file.absoluteFilePath()));
QProcess proc = QProcess(); QProcess proc = QProcess();
proc.setProgram(file.absoluteFilePath()); proc.setProgram(file.absoluteFilePath());
bool result = proc.startDetached(); bool result = proc.startDetached();
@ -617,21 +639,24 @@ void PrismUpdaterApp::performInstall(QFileInfo file)
} }
} }
void PrismUpdaterApp::unpackAndInstall(QFileInfo to_install_file) void PrismUpdaterApp::unpackAndInstall(QFileInfo archive)
{ {
logUpdate(tr("Backing up install"));
backupAppDir(); backupAppDir();
// TODO: uppack (rename access failures) auto loc = unpackArchive(archive);
// TODO: unpack (rename access failures)
} }
void PrismUpdaterApp::backupAppDir() void PrismUpdaterApp::backupAppDir()
{ {
auto manifest_path = FS::PathCombine(QCoreApplication::applicationDirPath(), "manifest.txt"); auto manifest_path = FS::PathCombine(applicationDirPath(), "manifest.txt");
QFileInfo manifest(manifest_path); QFileInfo manifest(manifest_path);
QStringList file_list; QStringList file_list;
if (manifest.isFile()) { if (manifest.isFile()) {
// load manifest from file // load manifest from file
logUpdate(tr("Reading manifest from %1").arg(manifest.absoluteFilePath()));
try { try {
auto contents = QString::fromUtf8(FS::read(manifest.absoluteFilePath())); auto contents = QString::fromUtf8(FS::read(manifest.absoluteFilePath()));
file_list.append(contents.split('\n')); file_list.append(contents.split('\n'));
@ -658,12 +683,12 @@ void PrismUpdaterApp::backupAppDir()
}); });
} }
file_list.append("portable.txt"); file_list.append("portable.txt");
qDebug() << "manifest.txt empty or missing. makking best guess at files to back up."; logUpdate("manifest.txt empty or missing. making best guess at files to back up.");
} }
qDebug() << "Backing up" << file_list; logUpdate(tr("Backing up:\n %1").arg(file_list.join(",\n ")));
QDir app_dir = QCoreApplication::applicationDirPath(); QDir app_dir = QCoreApplication::applicationDirPath();
auto backup_dir = FS::PathCombine(app_dir.absolutePath(), QStringLiteral("backup_") + m_prismVerison + ":" + m_prismGitCommit); auto backup_dir = FS::PathCombine(app_dir.absolutePath(), QStringLiteral("backup_") + m_prismVersion + ":" + m_prismGitCommit);
FS::ensureFolderPathExists(backup_dir); FS::ensureFolderPathExists(backup_dir);
for (auto glob : file_list) { for (auto glob : file_list) {
@ -675,26 +700,72 @@ void PrismUpdaterApp::backupAppDir()
auto bak_path = FS::PathCombine(backup_dir, rel_path); auto bak_path = FS::PathCombine(backup_dir, rel_path);
if (QFileInfo(to_bak_file).isFile()) { if (QFileInfo(to_bak_file).isFile()) {
qDebug() << "Backing up and then removing" << to_bak_file; logUpdate(tr("Backing up and then removing %1").arg(to_bak_file));
FS::ensureFilePathExists(bak_path); FS::ensureFilePathExists(bak_path);
auto result = FS::copy(to_bak_file, bak_path)(); auto result = FS::copy(to_bak_file, bak_path)();
if (!result) { if (!result) {
qWarning() << "Failed to backup" << to_bak_file << "to" << bak_path; logUpdate(tr("Failed to backup %1 to %2").arg(to_bak_file).arg(bak_path));
} else { } else {
if (!FS::deletePath(to_bak_file)) if (!FS::deletePath(to_bak_file))
qWarning() << "Failed to remove" << to_bak_file; logUpdate(tr("Failed to remove %1").arg(to_bak_file));
} }
} }
} }
} }
} }
std::optional<QDir> PrismUpdaterApp::unpackArchive(QFileInfo archive)
{
auto temp_extract_path = FS::PathCombine(m_dataPath, "prism_launcher_update_release");
FS::ensureFolderPathExists(temp_extract_path);
auto tmp_extract_dir = QDir(temp_extract_path);
if (archive.fileName().endsWith(".zip")) {
auto result = MMCZip::extractDir(archive.absoluteFilePath(), tmp_extract_dir.absolutePath());
if (result) {
logUpdate(
tr("Extracted the following to \"%1\":\n %2").arg(tmp_extract_dir.absolutePath()).arg(result->join("\n ")));
} else {
logUpdate(tr("Failed to extract %1 to %2").arg(archive.absoluteFilePath()).arg(tmp_extract_dir.absolutePath()));
showFatalErrorMessage("Failed to extract archive",
tr("Failed to extract %1 to %2").arg(archive.absoluteFilePath()).arg(tmp_extract_dir.absolutePath()));
return {};
}
} else if (archive.fileName().endsWith(".tar.gz")) {
QString cmd = "tar";
QStringList args = { "-xvf", archive.absoluteFilePath(), "-C", tmp_extract_dir.absolutePath() };
logUpdate(tr("Running: `%1 %2`").arg(cmd).arg(args.join(" ")));
QProcess proc = QProcess();
proc.start(cmd, args);
if (!proc.waitForStarted(5000)) { // wait 5 seconds to start
showFatalErrorMessage(tr("Failed extract archive"),
tr("Failed to launcher child process \"%1 %2\".").arg(cmd).arg(args.join(" ")));
return {};
}
auto result = proc.waitForFinished(5000);
auto out = proc.readAll();
logUpdate(out);
if (!result) {
showFatalErrorMessage(tr("Failed to extract archive"), tr("Child process \"%1 %2\" failed.").arg(cmd).arg(args.join(" ")));
return {};
}
} else {
logUpdate(tr("Unknown archive format for %1").arg(archive.absoluteFilePath()));
showFatalErrorMessage("Can not extract", QStringLiteral("Unknown archive format %1").arg(archive.absoluteFilePath()));
return {};
}
return tmp_extract_dir;
}
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" });
if (!proc.waitForStarted(5000)) // wait 5 seconds to start 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.")); return showFatalErrorMessage(tr("Failed to Check Version"), tr("Failed to launcher child launcher process to read version."));
if (!proc.waitForFinished(5000)) if (!proc.waitForFinished(5000))
return showFatalErrorMessage(tr("Failed to Check Version"), tr("Child launcher process failed.")); return showFatalErrorMessage(tr("Failed to Check Version"), tr("Child launcher process failed."));
auto out = proc.readAll(); auto out = proc.readAll();
@ -707,7 +778,7 @@ void PrismUpdaterApp::loadPrismVersionFromExe(const QString& exe_path)
return; return;
m_prismBinaryName = first_parts.takeFirst(); m_prismBinaryName = first_parts.takeFirst();
auto version = first_parts.takeFirst(); auto version = first_parts.takeFirst();
m_prismVerison = version; m_prismVersion = version;
if (version.contains('-')) { if (version.contains('-')) {
auto index = version.indexOf('-'); auto index = version.indexOf('-');
m_prsimVersionChannel = version.mid(index + 1); m_prsimVersionChannel = version.mid(index + 1);
@ -742,20 +813,20 @@ void PrismUpdaterApp::downloadReleasePage(const QString& api_url, int page)
{ {
int per_page = 30; int per_page = 30;
auto page_url = QString("%1?per_page=%2&page=%3").arg(api_url).arg(QString::number(per_page)).arg(QString::number(page)); auto page_url = QString("%1?per_page=%2&page=%3").arg(api_url).arg(QString::number(per_page)).arg(QString::number(page));
auto responce = std::make_shared<QByteArray>(); auto response = std::make_shared<QByteArray>();
auto download = Net::Download::makeByteArray(page_url, responce.get()); auto download = Net::Download::makeByteArray(page_url, response.get());
download->setNetwork(m_network); download->setNetwork(m_network);
m_current_url = page_url; m_current_url = page_url;
auto githup_api_headers = new Net::RawHeaderProxy(); auto github_api_headers = new Net::RawHeaderProxy();
githup_api_headers->addHeaders({ github_api_headers->addHeaders({
{ "Accept", "application/vnd.github+json" }, { "Accept", "application/vnd.github+json" },
{ "X-GitHub-Api-Version", "2022-11-28" }, { "X-GitHub-Api-Version", "2022-11-28" },
}); });
download->addHeaderProxy(githup_api_headers); download->addHeaderProxy(github_api_headers);
connect(download.get(), &Net::Download::succeeded, this, [this, responce, per_page, api_url, page]() { connect(download.get(), &Net::Download::succeeded, this, [this, response, per_page, api_url, page]() {
int num_found = parseReleasePage(responce.get()); int num_found = parseReleasePage(response.get());
if (!(num_found < per_page)) { // there may be more, fetch next page if (!(num_found < per_page)) { // there may be more, fetch next page
downloadReleasePage(api_url, page + 1); downloadReleasePage(api_url, page + 1);
} else { } else {
@ -766,7 +837,7 @@ void PrismUpdaterApp::downloadReleasePage(const QString& api_url, int page)
m_current_task.reset(download); m_current_task.reset(download);
connect(download.get(), &Net::Download::finished, this, [this]() { connect(download.get(), &Net::Download::finished, this, [this]() {
qDebug() << "Download" << m_current_task->getUid().toString() << "finsihed"; qDebug() << "Download" << m_current_task->getUid().toString() << "finished";
m_current_task.reset(); m_current_task.reset();
m_current_url = ""; m_current_url = "";
}); });
@ -776,13 +847,13 @@ void PrismUpdaterApp::downloadReleasePage(const QString& api_url, int page)
QMetaObject::invokeMethod(download.get(), &Task::start, Qt::QueuedConnection); QMetaObject::invokeMethod(download.get(), &Task::start, Qt::QueuedConnection);
} }
int PrismUpdaterApp::parseReleasePage(const QByteArray* responce) int PrismUpdaterApp::parseReleasePage(const QByteArray* response)
{ {
if (responce->isEmpty()) // empty page if (response->isEmpty()) // empty page
return 0; return 0;
int num_releases = 0; int num_releases = 0;
try { try {
auto doc = Json::requireDocument(*responce); auto doc = Json::requireDocument(*response);
auto release_list = Json::requireArray(doc); auto release_list = Json::requireArray(doc);
for (auto release_json : release_list) { for (auto release_json : release_list) {
auto release_obj = Json::requireObject(release_json); auto release_obj = Json::requireObject(release_json);
@ -817,7 +888,7 @@ int PrismUpdaterApp::parseReleasePage(const QByteArray* responce)
} }
} catch (Json::JsonException& e) { } catch (Json::JsonException& e) {
auto err_msg = auto err_msg =
QString("Failed to parse releases from github: %1\n%2").arg(e.what()).arg(QString::fromStdString(responce->toStdString())); QString("Failed to parse releases from github: %1\n%2").arg(e.what()).arg(QString::fromStdString(response->toStdString()));
fail(err_msg); fail(err_msg);
} }
return num_releases; return num_releases;

View File

@ -63,7 +63,7 @@ class PrismUpdaterApp : public QApplication {
void loadPrismVersionFromExe(const QString& exe_path); void loadPrismVersionFromExe(const QString& exe_path);
void downloadReleasePage(const QString& api_url, int page); void downloadReleasePage(const QString& api_url, int page);
int parseReleasePage(const QByteArray* responce); int parseReleasePage(const QByteArray* response);
bool needUpdate(const GitHubRelease& release); bool needUpdate(const GitHubRelease& release);
@ -80,6 +80,7 @@ class PrismUpdaterApp : public QApplication {
void performInstall(QFileInfo file); void performInstall(QFileInfo file);
void unpackAndInstall(QFileInfo file); void unpackAndInstall(QFileInfo file);
void backupAppDir(); void backupAppDir();
std::optional<QDir> unpackArchive(QFileInfo file);
QFileInfo downloadAsset(const GitHubReleaseAsset& asset); QFileInfo downloadAsset(const GitHubReleaseAsset& asset);
bool callAppimageUpdate(); bool callAppimageUpdate();
@ -92,7 +93,12 @@ class PrismUpdaterApp : public QApplication {
bool isPortable() { return m_isPortable; } bool isPortable() { return m_isPortable; }
void clearUpdateLog();
void logUpdate(const QString& msg);
QString m_rootPath; QString m_rootPath;
QString m_dataPath;
bool m_isPortable = false; bool m_isPortable = false;
bool m_isAppimage = false; bool m_isAppimage = false;
bool m_isFlatpak = false; bool m_isFlatpak = false;
@ -107,12 +113,14 @@ class PrismUpdaterApp : public QApplication {
bool m_allowDowngrade; bool m_allowDowngrade;
QString m_prismBinaryName; QString m_prismBinaryName;
QString m_prismVerison; QString m_prismVersion;
int m_prismVersionMajor; int m_prismVersionMajor;
int m_prismVersionMinor; int m_prismVersionMinor;
QString m_prsimVersionChannel; QString m_prsimVersionChannel;
QString m_prismGitCommit; QString m_prismGitCommit;
GitHubRelease m_install_release;
Status m_status = Status::Starting; Status m_status = Status::Starting;
shared_qobject_ptr<QNetworkAccessManager> m_network; shared_qobject_ptr<QNetworkAccessManager> m_network;
QString m_current_url; QString m_current_url;