Merge pull request #458 from Scrumplex/remove-updater

This commit is contained in:
Sefa Eyeoglu 2023-01-25 10:32:49 +01:00 committed by GitHub
commit 0546345182
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 58 additions and 2228 deletions

View File

@ -101,7 +101,7 @@
#include "java/JavaUtils.h"
#include "updater/UpdateChecker.h"
#include "updater/ExternalUpdater.h"
#include "tools/JProfiler.h"
#include "tools/JVisualVM.h"
@ -125,6 +125,10 @@
#include "MangoHud.h"
#endif
#ifdef Q_OS_MAC
#include "updater/MacSparkleUpdater.h"
#endif
#if defined Q_OS_WIN32
#ifndef WIN32_LEAN_AND_MEAN
@ -155,45 +159,6 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QSt
fflush(stderr);
}
QString getIdealPlatform(QString currentPlatform) {
auto info = Sys::getKernelInfo();
switch(info.kernelType) {
case Sys::KernelType::Darwin: {
if(info.kernelMajor >= 17) {
// macOS 10.13 or newer
return "osx64-5.15.2";
}
else {
// macOS 10.12 or older
return "osx64";
}
}
case Sys::KernelType::Windows: {
// FIXME: 5.15.2 is not stable on Windows, due to a large number of completely unpredictable and hard to reproduce issues
break;
/*
if(info.kernelMajor == 6 && info.kernelMinor >= 1) {
// Windows 7
return "win32-5.15.2";
}
else if (info.kernelMajor > 6) {
// Above Windows 7
return "win32-5.15.2";
}
else {
// Below Windows 7
return "win32";
}
*/
}
case Sys::KernelType::Undetermined:
case Sys::KernelType::Linux: {
break;
}
}
return currentPlatform;
}
}
Application::Application(int &argc, char **argv) : QApplication(argc, argv)
@ -503,10 +468,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
{
// Provide a fallback for migration from PolyMC
m_settings.reset(new INISettingsObject({ BuildConfig.LAUNCHER_CONFIGFILE, "polymc.cfg", "multimc.cfg" }, this));
// Updates
// Multiple channels are separated by spaces
m_settings->registerSetting("UpdateChannel", BuildConfig.VERSION_CHANNEL);
m_settings->registerSetting("AutoUpdate", true);
// Theming
m_settings->registerSetting("IconTheme", QString("pe_colored"));
@ -740,10 +701,10 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
// initialize the updater
if(BuildConfig.UPDATER_ENABLED)
{
auto platform = getIdealPlatform(BuildConfig.BUILD_PLATFORM);
auto channelUrl = BuildConfig.UPDATER_BASE + platform + "/channels.json";
qDebug() << "Initializing updater with platform: " << platform << " -- " << channelUrl;
m_updateChecker.reset(new UpdateChecker(m_network, channelUrl, BuildConfig.VERSION_CHANNEL));
qDebug() << "Initializing updater";
#ifdef Q_OS_MAC
m_updater.reset(new MacSparkleUpdater());
#endif
qDebug() << "<> Updater started.";
}
@ -1698,3 +1659,14 @@ bool Application::handleDataMigration(const QString& currentData,
}
return true;
}
void Application::triggerUpdateCheck()
{
if (m_updater) {
qDebug() << "Checking for updates.";
m_updater->setBetaAllowed(false); // There are no other channels than stable
m_updater->checkForUpdates();
} else {
qDebug() << "Updater not available.";
}
}

View File

@ -43,7 +43,6 @@
#include <QIcon>
#include <QDateTime>
#include <QUrl>
#include <updater/GoUpdate.h>
#include <BaseInstance.h>
@ -63,7 +62,7 @@ class AccountList;
class IconList;
class QNetworkAccessManager;
class JavaInstallList;
class UpdateChecker;
class ExternalUpdater;
class BaseProfilerFactory;
class BaseDetachedToolFactory;
class TranslationsModel;
@ -126,10 +125,12 @@ public:
void setApplicationTheme(const QString& name);
shared_qobject_ptr<UpdateChecker> updateChecker() {
return m_updateChecker;
shared_qobject_ptr<ExternalUpdater> updater() {
return m_updater;
}
void triggerUpdateCheck();
std::shared_ptr<TranslationsModel> translations();
std::shared_ptr<JavaInstallList> javalist();
@ -251,7 +252,7 @@ private:
shared_qobject_ptr<QNetworkAccessManager> m_network;
shared_qobject_ptr<UpdateChecker> m_updateChecker;
shared_qobject_ptr<ExternalUpdater> m_updater;
shared_qobject_ptr<AccountList> m_accounts;
shared_qobject_ptr<HttpMetaCache> m_metacache;
@ -310,4 +311,3 @@ public:
QString m_instanceIdToShowWindowOf;
std::unique_ptr<QFile> logFile;
};

View File

@ -161,12 +161,6 @@ set(LAUNCH_SOURCES
# Old update system
set(UPDATE_SOURCES
updater/GoUpdate.h
updater/GoUpdate.cpp
updater/UpdateChecker.h
updater/UpdateChecker.cpp
updater/DownloadTask.h
updater/DownloadTask.cpp
updater/ExternalUpdater.h
)
@ -621,8 +615,6 @@ SET(LAUNCHER_SOURCES
Application.cpp
DataMigrationTask.h
DataMigrationTask.cpp
UpdateController.cpp
UpdateController.h
ApplicationMessage.h
ApplicationMessage.cpp
@ -868,8 +860,6 @@ SET(LAUNCHER_SOURCES
ui/dialogs/ProgressDialog.h
ui/dialogs/ReviewMessageBox.cpp
ui/dialogs/ReviewMessageBox.h
ui/dialogs/UpdateDialog.cpp
ui/dialogs/UpdateDialog.h
ui/dialogs/VersionSelectDialog.cpp
ui/dialogs/VersionSelectDialog.h
ui/dialogs/SkinUploadDialog.cpp
@ -986,7 +976,6 @@ qt_wrap_ui(LAUNCHER_UI
ui/dialogs/ProfileSetupDialog.ui
ui/dialogs/ProgressDialog.ui
ui/dialogs/NewInstanceDialog.ui
ui/dialogs/UpdateDialog.ui
ui/dialogs/NewComponentDialog.ui
ui/dialogs/NewsDialog.ui
ui/dialogs/ProfileSelectDialog.ui

View File

@ -1,443 +0,0 @@
#include <QFile>
#include <QMessageBox>
#include <FileSystem.h>
#include <updater/GoUpdate.h>
#include "UpdateController.h"
#include <QApplication>
#include <thread>
#include <chrono>
#include <LocalPeer.h>
#include "BuildConfig.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) || defined(Q_OS_FREEBSD) || defined (Q_OS_OPENBSD)
QString finishCmd = FS::PathCombine(m_root, BuildConfig.LAUNCHER_NAME);
#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
QString windowsExeName = BuildConfig.LAUNCHER_NAME + ".exe";
// hack for people renaming the .exe because ... reasons :)
if(op.destination == windowsExeName)
{
op.destination = QFileInfo(QApplication::applicationFilePath()).fileName();
}
#endif
QFileInfo destination (FS::PathCombine(m_root, op.destination));
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");
}
}
// FIXME: reparse args and construct a safe variant from scratch. This is a workaround for GH-1874:
QStringList realargs;
int skip = 0;
for(auto & arg: args)
{
if(skip)
{
skip--;
continue;
}
if(arg == "-l")
{
skip = 1;
continue;
}
realargs.append(arg);
}
// start the updated application
started = QProcess::startDetached(finishCmd, realargs, 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 (const 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 %2 log file for details."
).arg(m_failedFile, BuildConfig.LAUNCHER_DISPLAYNAME);
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 %2 log file for details."
).arg(m_failedFile, BuildConfig.LAUNCHER_DISPLAYNAME);
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 %1 manually.\n"
"Please let us know why and how this happened.").arg(BuildConfig.LAUNCHER_DISPLAYNAME);
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;
}

View File

@ -1,44 +0,0 @@
#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;
};

View File

@ -36,6 +36,7 @@
#include "LauncherPartLaunch.h"
#include <QStandardPaths>
#include <QRegularExpression>
#include "launch/LaunchTask.h"
#include "minecraft/MinecraftInstance.h"

View File

@ -8,7 +8,7 @@
#include "Application.h"
#include "BuildConfig.h"
#include "Json.h"
#include "net/NetJob.h"
#include "net/Upload.h"
Task::Ptr FlameAPI::matchFingerprints(const QList<uint>& fingerprints, QByteArray* response)

View File

@ -6,6 +6,7 @@
#include "Application.h"
#include "Json.h"
#include "net/NetJob.h"
#include "net/Upload.h"
Task::Ptr ModrinthAPI::currentVersion(QString hash, QString hash_format, QByteArray* response)

View File

@ -36,6 +36,7 @@
#include "MetaCacheSink.h"
#include <QFile>
#include <QFileInfo>
#include <QRegularExpression>
#include "Application.h"
namespace Net {

View File

@ -41,9 +41,11 @@
#include <QDebug>
#include <QJsonObject>
#include <QHttpPart>
#include <QJsonArray>
#include <QJsonDocument>
#include <QFile>
#include <QUrlQuery>
std::array<PasteUpload::PasteTypeInfo, 4> PasteUpload::PasteTypes = {
{{"0x0.st", "https://0x0.st", ""},

View File

@ -40,6 +40,7 @@
#include <QClipboard>
#include <QApplication>
#include <QFileDialog>
#include <QStandardPaths>
#include "ui/dialogs/ProgressDialog.h"
#include "ui/dialogs/CustomMessageBox.h"

View File

@ -86,8 +86,7 @@
#include <net/Download.h>
#include <news/NewsChecker.h>
#include <tools/BaseProfiler.h>
#include <updater/DownloadTask.h>
#include <updater/UpdateChecker.h>
#include <updater/ExternalUpdater.h>
#include <DesktopServices.h>
#include "InstanceWindow.h"
#include "InstancePageProvider.h"
@ -102,11 +101,9 @@
#include "ui/dialogs/NewsDialog.h"
#include "ui/dialogs/ProgressDialog.h"
#include "ui/dialogs/AboutDialog.h"
#include "ui/dialogs/VersionSelectDialog.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/IconPickerDialog.h"
#include "ui/dialogs/CopyInstanceDialog.h"
#include "ui/dialogs/UpdateDialog.h"
#include "ui/dialogs/EditAccountDialog.h"
#include "ui/dialogs/ExportInstanceDialog.h"
#include "ui/dialogs/ImportResourceDialog.h"
@ -117,7 +114,6 @@
#include "minecraft/mod/ModFolderModel.h"
#include "minecraft/WorldList.h"
#include "UpdateController.h"
#include "KonamiCode.h"
#include "InstanceImportTask.h"
@ -1061,9 +1057,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
updateNewsLabel();
}
if(BuildConfig.UPDATER_ENABLED)
{
if (BuildConfig.UPDATER_ENABLED) {
bool updatesAllowed = APPLICATION->updatesAreAllowed();
updatesAllowedChanged(updatesAllowed);
@ -1071,21 +1065,10 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
connect(ui->actionCheckUpdate.operator->(), &QAction::triggered, this, &MainWindow::checkForUpdates);
// set up the updater object.
auto updater = APPLICATION->updateChecker();
connect(updater.get(), &UpdateChecker::updateAvailable, this, &MainWindow::updateAvailable);
connect(updater.get(), &UpdateChecker::noUpdateFound, this, &MainWindow::updateNotAvailable);
// if automatic update checks are allowed, start one.
if (APPLICATION->settings()->get("AutoUpdate").toBool() && updatesAllowed)
{
updater->checkForUpdate(APPLICATION->settings()->get("UpdateChannel").toString(), false);
}
auto updater = APPLICATION->updater();
if (APPLICATION->updateChecker()->getExternalUpdater())
{
connect(APPLICATION->updateChecker()->getExternalUpdater(),
&ExternalUpdater::canCheckForUpdatesChanged,
this,
&MainWindow::updatesAllowedChanged);
if (updater) {
connect(updater.get(), &ExternalUpdater::canCheckForUpdatesChanged, this, &MainWindow::updatesAllowedChanged);
}
}
@ -1563,32 +1546,6 @@ void MainWindow::updateNewsLabel()
}
}
void MainWindow::updateAvailable(GoUpdate::Status status)
{
if(!APPLICATION->updatesAreAllowed())
{
updateNotAvailable();
return;
}
UpdateDialog dlg(true, this);
UpdateAction action = (UpdateAction)dlg.exec();
switch (action)
{
case UPDATE_LATER:
qDebug() << "Update will be installed later.";
break;
case UPDATE_NOW:
downloadUpdates(status);
break;
}
}
void MainWindow::updateNotAvailable()
{
UpdateDialog dlg(false, this);
dlg.exec();
}
QList<int> stringToIntList(const QString &string)
{
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
@ -1613,40 +1570,6 @@ QString intListToString(const QList<int> &list)
return slist.join(',');
}
void MainWindow::downloadUpdates(GoUpdate::Status status)
{
if(!APPLICATION->updatesAreAllowed())
{
return;
}
qDebug() << "Downloading updates.";
ProgressDialog updateDlg(this);
status.rootPath = APPLICATION->root();
auto dlPath = FS::PathCombine(APPLICATION->root(), "update", "XXXXXX");
if (!FS::ensureFilePathExists(dlPath))
{
CustomMessageBox::selectable(this, tr("Error"), tr("Couldn't create folder for update downloads:\n%1").arg(dlPath), QMessageBox::Warning)->show();
}
GoUpdate::DownloadTask updateTask(APPLICATION->network(), status, dlPath, &updateDlg);
// If the task succeeds, install the updates.
if (updateDlg.execWithTask(&updateTask))
{
/**
* NOTE: This disables launching instances until the update either succeeds (and this process exits)
* or the update fails (and the control leaves this scope).
*/
APPLICATION->updateIsRunning(true);
UpdateController update(this, APPLICATION->root(), updateTask.updateFilesDir(), updateTask.operations());
update.installUpdates();
APPLICATION->updateIsRunning(false);
}
else
{
CustomMessageBox::selectable(this, tr("Error"), updateTask.failReason(), QMessageBox::Warning)->show();
}
}
void MainWindow::onCatToggled(bool state)
{
setCatBackground(state);
@ -1983,8 +1906,7 @@ void MainWindow::checkForUpdates()
{
if(BuildConfig.UPDATER_ENABLED)
{
auto updater = APPLICATION->updateChecker();
updater->checkForUpdate(APPLICATION->settings()->get("UpdateChannel").toString(), true);
APPLICATION->triggerUpdateCheck();
}
else
{

View File

@ -48,7 +48,6 @@
#include "BaseInstance.h"
#include "minecraft/auth/MinecraftAccount.h"
#include "net/NetJob.h"
#include "updater/GoUpdate.h"
class LaunchController;
class NewsChecker;
@ -192,10 +191,6 @@ private slots:
void startTask(Task *task);
void updateAvailable(GoUpdate::Status status);
void updateNotAvailable();
void defaultAccountChanged();
void changeActiveAccount();
@ -204,10 +199,6 @@ private slots:
void updateNewsLabel();
/*!
* Runs the DownloadTask and installs updates.
*/
void downloadUpdates(GoUpdate::Status status);
void konamiTriggered();
@ -256,4 +247,3 @@ private:
// managed by the application object
Task *m_versionLoadTask = nullptr;
};

View File

@ -32,10 +32,14 @@
#include <QDebug>
#include <QDesktopServices>
#include <QDialogButtonBox>
#include <QDir>
#include <QDirIterator>
#include <QDragEnterEvent>
#include <QFileDialog>
#include <QFileInfo>
#include <QMimeData>
#include <QPushButton>
#include <QMimeData>
#include <QStandardPaths>
BlockedModsDialog::BlockedModsDialog(QWidget* parent, const QString& title, const QString& text, QList<BlockedMod>& mods)
@ -88,7 +92,7 @@ void BlockedModsDialog::dropEvent(QDropEvent* e)
if (url.scheme().isEmpty()) { // ensure isLocalFile() works correctly
url.setScheme("file");
}
if (!url.isLocalFile()) { // can't drop external files here.
continue;
}

View File

@ -44,6 +44,7 @@
#include <QSortFilterProxyModel>
#include <QDebug>
#include <QSaveFile>
#include <QStack>
#include "StringUtils.h"
#include "SeparatorPrefixTree.h"
#include "Application.h"

View File

@ -1,216 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "UpdateDialog.h"
#include "ui_UpdateDialog.h"
#include <QDebug>
#include "Application.h"
#include <settings/SettingsObject.h>
#include <Json.h>
#include "BuildConfig.h"
#include "Markdown.h"
UpdateDialog::UpdateDialog(bool hasUpdate, QWidget *parent) : QDialog(parent), ui(new Ui::UpdateDialog)
{
ui->setupUi(this);
auto channel = APPLICATION->settings()->get("UpdateChannel").toString();
if(hasUpdate)
{
ui->label->setText(tr("A new %1 update is available!").arg(channel));
}
else
{
ui->label->setText(tr("No %1 updates found. You are running the latest version.").arg(channel));
ui->btnUpdateNow->setHidden(true);
ui->btnUpdateLater->setText(tr("Close"));
}
ui->changelogBrowser->setHtml(tr("<center><h1>Loading changelog...</h1></center>"));
loadChangelog();
restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("UpdateDialogGeometry").toByteArray()));
}
UpdateDialog::~UpdateDialog()
{
}
void UpdateDialog::loadChangelog()
{
auto channel = APPLICATION->settings()->get("UpdateChannel").toString();
dljob = new NetJob("Changelog", APPLICATION->network());
QString url;
if(channel == "stable")
{
url = QString("https://raw.githubusercontent.com/PrismLauncher/PrismLauncher/%1/changelog.md").arg(channel);
m_changelogType = CHANGELOG_MARKDOWN;
}
else
{
url = QString("https://api.github.com/repos/PrismLauncher/PrismLauncher/compare/%1...%2").arg(BuildConfig.GIT_COMMIT, channel);
m_changelogType = CHANGELOG_COMMITS;
}
dljob->addNetAction(Net::Download::makeByteArray(QUrl(url), &changelogData));
connect(dljob.get(), &NetJob::succeeded, this, &UpdateDialog::changelogLoaded);
connect(dljob.get(), &NetJob::failed, this, &UpdateDialog::changelogFailed);
dljob->start();
}
QString reprocessMarkdown(QByteArray markdown)
{
QString output = markdownToHTML(markdown);
// HACK: easier than customizing hoedown
output.replace(QRegularExpression("GH-([0-9]+)"), "<a href=\"https://github.com/PrismLauncher/PrismLauncher/issues/\\1\">GH-\\1</a>");
qDebug() << output;
return output;
}
QString reprocessCommits(QByteArray json)
{
auto channel = APPLICATION->settings()->get("UpdateChannel").toString();
try
{
QString result;
auto document = Json::requireDocument(json);
auto rootobject = Json::requireObject(document);
auto status = Json::requireString(rootobject, "status");
auto diff_url = Json::requireString(rootobject, "html_url");
auto print_commits = [&]()
{
result += "<table cellspacing=0 cellpadding=2 style='border-width: 1px; border-style: solid'>";
auto commitarray = Json::requireArray(rootobject, "commits");
for(int i = commitarray.size() - 1; i >= 0; i--)
{
const auto & commitval = commitarray[i];
auto commitobj = Json::requireObject(commitval);
auto parents_info = Json::ensureArray(commitobj, "parents");
// NOTE: this ignores merge commits, because they have more than one parent
if(parents_info.size() > 1)
{
continue;
}
auto commit_url = Json::requireString(commitobj, "html_url");
auto commit_info = Json::requireObject(commitobj, "commit");
auto commit_message = Json::requireString(commit_info, "message");
auto lines = commit_message.split('\n');
QRegularExpression regexp("(?<prefix>(GH-(?<issuenr>[0-9]+))|(NOISSUE)|(SCRATCH))? *(?<rest>.*) *");
auto match = regexp.match(lines.takeFirst(), 0, QRegularExpression::NormalMatch);
auto issuenr = match.captured("issuenr");
auto prefix = match.captured("prefix");
auto rest = match.captured("rest");
result += "<tr><td>";
if(issuenr.length())
{
result += QString("<a href=\"https://github.com/PrismLauncher/PrismLauncher/issues/%1\">GH-%2</a>").arg(issuenr, issuenr);
}
else if(prefix.length())
{
result += QString("<a href=\"%1\">%2</a>").arg(commit_url, prefix);
}
else
{
result += QString("<a href=\"%1\">NOISSUE</a>").arg(commit_url);
}
result += "</td>";
lines.prepend(rest);
result += "<td><p>" + lines.join("<br />") + "</p></td></tr>";
}
result += "</table>";
};
if(status == "identical")
{
return QObject::tr("<p>There are no code changes between your current version and latest %1.</p>").arg(channel);
}
else if(status == "ahead")
{
result += QObject::tr("<p>Following commits were added since last update:</p>");
print_commits();
}
else if(status == "diverged")
{
auto commit_ahead = Json::requireInteger(rootobject, "ahead_by");
auto commit_behind = Json::requireInteger(rootobject, "behind_by");
result += QObject::tr("<p>The update removes %1 commits and adds the following %2:</p>").arg(commit_behind).arg(commit_ahead);
print_commits();
}
result += QObject::tr("<p>You can <a href=\"%1\">look at the changes on github</a>.</p>").arg(diff_url);
return result;
}
catch (const JSONValidationError &e)
{
qWarning() << "Got an unparseable commit log from github:" << e.what();
qDebug() << json;
}
return QString();
}
void UpdateDialog::changelogLoaded()
{
QString result;
switch(m_changelogType)
{
case CHANGELOG_COMMITS:
result = reprocessCommits(changelogData);
break;
case CHANGELOG_MARKDOWN:
result = reprocessMarkdown(changelogData);
break;
}
changelogData.clear();
ui->changelogBrowser->setHtml(result);
}
void UpdateDialog::changelogFailed(QString reason)
{
ui->changelogBrowser->setHtml(tr("<p align=\"center\" <span style=\"font-size:22pt;\">Failed to fetch changelog... Error: %1</span></p>").arg(reason));
}
void UpdateDialog::on_btnUpdateLater_clicked()
{
reject();
}
void UpdateDialog::on_btnUpdateNow_clicked()
{
done(UPDATE_NOW);
}
void UpdateDialog::closeEvent(QCloseEvent* evt)
{
APPLICATION->settings()->set("UpdateDialogGeometry", saveGeometry().toBase64());
QDialog::closeEvent(evt);
}

View File

@ -1,67 +0,0 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QDialog>
#include "net/NetJob.h"
namespace Ui
{
class UpdateDialog;
}
enum UpdateAction
{
UPDATE_LATER = QDialog::Rejected,
UPDATE_NOW = QDialog::Accepted,
};
enum ChangelogType
{
CHANGELOG_MARKDOWN,
CHANGELOG_COMMITS
};
class UpdateDialog : public QDialog
{
Q_OBJECT
public:
explicit UpdateDialog(bool hasUpdate = true, QWidget *parent = 0);
~UpdateDialog();
public slots:
void on_btnUpdateNow_clicked();
void on_btnUpdateLater_clicked();
/// Starts loading the changelog
void loadChangelog();
/// Slot for when the chengelog loads successfully.
void changelogLoaded();
/// Slot for when the chengelog fails to load...
void changelogFailed(QString reason);
protected:
void closeEvent(QCloseEvent * ) override;
private:
Ui::UpdateDialog *ui;
QByteArray changelogData;
NetJob::Ptr dljob;
ChangelogType m_changelogType = CHANGELOG_MARKDOWN;
};

View File

@ -1,91 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>UpdateDialog</class>
<widget class="QDialog" name="UpdateDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>657</width>
<height>673</height>
</rect>
</property>
<property name="windowTitle">
<string>Launcher Update</string>
</property>
<property name="windowIcon">
<iconset>
<normaloff>:/icons/toolbar/checkupdate</normaloff>:/icons/toolbar/checkupdate</iconset>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<pointsize>14</pointsize>
</font>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>changelogBrowser</cstring>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTextBrowser" name="changelogBrowser">
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QPushButton" name="btnUpdateNow">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Update now</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="btnUpdateLater">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Don't update yet</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<tabstops>
<tabstop>changelogBrowser</tabstop>
<tabstop>btnUpdateNow</tabstop>
<tabstop>btnUpdateLater</tabstop>
</tabstops>
<resources>
<include location="../../resources/multimc/multimc.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -44,14 +44,13 @@
#include <QTextCharFormat>
#include <QMenuBar>
#include "updater/UpdateChecker.h"
#include "settings/SettingsObject.h"
#include <FileSystem.h>
#include "Application.h"
#include "BuildConfig.h"
#include "DesktopServices.h"
#include "ui/themes/ITheme.h"
#include "updater/ExternalUpdater.h"
#include <QApplication>
#include <QProcess>
@ -80,30 +79,8 @@ LauncherPage::LauncherPage(QWidget *parent) : QWidget(parent), ui(new Ui::Launch
m_languageModel = APPLICATION->translations();
loadSettings();
if(BuildConfig.UPDATER_ENABLED)
{
QObject::connect(APPLICATION->updateChecker().get(), &UpdateChecker::channelListLoaded, this, &LauncherPage::refreshUpdateChannelList);
ui->updateSettingsBox->setHidden(!APPLICATION->updater());
if (APPLICATION->updateChecker()->hasChannels())
{
refreshUpdateChannelList();
}
else
{
APPLICATION->updateChecker()->updateChanList(false);
}
if (APPLICATION->updateChecker()->getExternalUpdater())
{
ui->updateChannelComboBox->setVisible(false);
ui->updateChannelDescLabel->setVisible(false);
ui->updateChannelLabel->setVisible(false);
}
}
else
{
ui->updateSettingsBox->setHidden(true);
}
connect(ui->fontSizeBox, SIGNAL(valueChanged(int)), SLOT(refreshFontPreview()));
connect(ui->consoleFont, SIGNAL(currentFontChanged(QFont)), SLOT(refreshFontPreview()));
@ -200,94 +177,15 @@ void LauncherPage::on_metadataDisableBtn_clicked()
ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked());
}
void LauncherPage::refreshUpdateChannelList()
{
// Stop listening for selection changes. It's going to change a lot while we update it and
// we don't need to update the
// description label constantly.
QObject::disconnect(ui->updateChannelComboBox, SIGNAL(currentIndexChanged(int)), this,
SLOT(updateChannelSelectionChanged(int)));
QList<UpdateChecker::ChannelListEntry> channelList = APPLICATION->updateChecker()->getChannelList();
ui->updateChannelComboBox->clear();
int selection = -1;
for (int i = 0; i < channelList.count(); i++)
{
UpdateChecker::ChannelListEntry entry = channelList.at(i);
// When it comes to selection, we'll rely on the indexes of a channel entry being the
// same in the
// combo box as it is in the update checker's channel list.
// This probably isn't very safe, but the channel list doesn't change often enough (or
// at all) for
// this to be a big deal. Hope it doesn't break...
ui->updateChannelComboBox->addItem(entry.name);
// If the update channel we just added was the selected one, set the current index in
// the combo box to it.
if (entry.id == m_currentUpdateChannel)
{
qDebug() << "Selected index" << i << "channel id" << m_currentUpdateChannel;
selection = i;
}
}
ui->updateChannelComboBox->setCurrentIndex(selection);
// Start listening for selection changes again and update the description label.
QObject::connect(ui->updateChannelComboBox, SIGNAL(currentIndexChanged(int)), this,
SLOT(updateChannelSelectionChanged(int)));
refreshUpdateChannelDesc();
// Now that we've updated the channel list, we can enable the combo box.
// It starts off disabled so that if the channel list hasn't been loaded, it will be
// disabled.
ui->updateChannelComboBox->setEnabled(true);
}
void LauncherPage::updateChannelSelectionChanged(int index)
{
refreshUpdateChannelDesc();
}
void LauncherPage::refreshUpdateChannelDesc()
{
// Get the channel list.
QList<UpdateChecker::ChannelListEntry> channelList = APPLICATION->updateChecker()->getChannelList();
int selectedIndex = ui->updateChannelComboBox->currentIndex();
if (selectedIndex < 0)
{
return;
}
if (selectedIndex < channelList.count())
{
// Find the channel list entry with the given index.
UpdateChecker::ChannelListEntry selected = channelList.at(selectedIndex);
// Set the description text.
ui->updateChannelDescLabel->setText(selected.description);
// Set the currently selected channel ID.
m_currentUpdateChannel = selected.id;
}
}
void LauncherPage::applySettings()
{
auto s = APPLICATION->settings();
// Updates
if (BuildConfig.UPDATER_ENABLED && APPLICATION->updateChecker()->getExternalUpdater())
if (APPLICATION->updater())
{
APPLICATION->updateChecker()->getExternalUpdater()->setAutomaticallyChecksForUpdates(
ui->autoUpdateCheckBox->isChecked());
APPLICATION->updater()->setAutomaticallyChecksForUpdates(ui->autoUpdateCheckBox->isChecked());
}
else
{
s->set("AutoUpdate", ui->autoUpdateCheckBox->isChecked());
}
s->set("UpdateChannel", m_currentUpdateChannel);
s->set("MenuBarInsteadOfToolBar", ui->preferMenuBarCheckBox->isChecked());
@ -326,17 +224,11 @@ void LauncherPage::loadSettings()
{
auto s = APPLICATION->settings();
// Updates
if (BuildConfig.UPDATER_ENABLED && APPLICATION->updateChecker()->getExternalUpdater())
if (APPLICATION->updater())
{
ui->autoUpdateCheckBox->setChecked(
APPLICATION->updateChecker()->getExternalUpdater()->getAutomaticallyChecksForUpdates());
}
else
{
ui->autoUpdateCheckBox->setChecked(s->get("AutoUpdate").toBool());
ui->autoUpdateCheckBox->setChecked(APPLICATION->updater()->getAutomaticallyChecksForUpdates());
}
m_currentUpdateChannel = s->get("UpdateChannel").toString();
// Toolbar/menu bar settings (not applicable if native menu bar is present)
ui->toolsBox->setEnabled(!QMenuBar().isNativeMenuBar());

View File

@ -90,23 +90,11 @@ slots:
void on_iconsDirBrowseBtn_clicked();
void on_metadataDisableBtn_clicked();
/*!
* Updates the list of update channels in the combo box.
*/
void refreshUpdateChannelList();
/*!
* Updates the channel description label.
*/
void refreshUpdateChannelDesc();
/*!
* Updates the font preview
*/
void refreshFontPreview();
void updateChannelSelectionChanged(int index);
private:
Ui::LauncherPage *ui;

View File

@ -58,33 +58,6 @@
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="updateChannelLabel">
<property name="text">
<string>Up&amp;date Channel:</string>
</property>
<property name="buddy">
<cstring>updateChannelComboBox</cstring>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="updateChannelComboBox">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="updateChannelDescLabel">
<property name="text">
<string>No channel selected.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@ -440,7 +413,6 @@
<tabstops>
<tabstop>tabWidget</tabstop>
<tabstop>autoUpdateCheckBox</tabstop>
<tabstop>updateChannelComboBox</tabstop>
<tabstop>instDirTextBox</tabstop>
<tabstop>instDirBrowseBtn</tabstop>
<tabstop>modsDirTextBox</tabstop>

View File

@ -40,7 +40,7 @@
#include "Application.h"
#include <QIcon>
#include <QIdentityProxyModel>
#include <QScrollBar>
#include <QShortcut>

View File

@ -44,6 +44,7 @@
class QFileSystemModel;
class QIdentityProxyModel;
class QItemSelection;
namespace Ui
{
class ScreenshotsPage;

View File

@ -50,6 +50,7 @@
#include <QFileSystemWatcher>
#include <QMenu>
#include <QTimer>
static const int COLUMN_COUNT = 2; // 3 , TBD: latency and other nice things.

View File

@ -45,9 +45,9 @@
#include <QKeyEvent>
#include <QClipboard>
#include <QMessageBox>
#include <QSortFilterProxyModel>
#include <QTreeView>
#include <QInputDialog>
#include <QProcess>
#include <Qt>
#include "tools/MCEditTool.h"

View File

@ -35,6 +35,8 @@
#include "ListModel.h"
#include "Application.h"
#include "net/HttpMetaCache.h"
#include "net/NetJob.h"
#include "StringUtils.h"
#include <Version.h>

View File

@ -38,6 +38,7 @@
#include <QAbstractListModel>
#include "modplatform/modrinth/ModrinthPackManifest.h"
#include "net/NetJob.h"
#include "ui/pages/modplatform/modrinth/ModrinthPage.h"
class ModPage;

View File

@ -1,177 +0,0 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "DownloadTask.h"
#include "updater/UpdateChecker.h"
#include "GoUpdate.h"
#include "net/NetJob.h"
#include <QFile>
#include <QTemporaryDir>
#include <QCryptographicHash>
namespace GoUpdate
{
DownloadTask::DownloadTask(
shared_qobject_ptr<QNetworkAccessManager> network,
Status status,
QString target,
QObject *parent
) : Task(parent), m_updateFilesDir(target), m_network(network)
{
m_status = status;
m_updateFilesDir.setAutoRemove(false);
}
void DownloadTask::executeTask()
{
loadVersionInfo();
}
void DownloadTask::loadVersionInfo()
{
setStatus(tr("Loading version information..."));
NetJob *netJob = new NetJob("Version Info", m_network);
// Find the index URL.
QUrl newIndexUrl = QUrl(m_status.newRepoUrl).resolved(QString::number(m_status.newVersionId) + ".json");
qDebug() << m_status.newRepoUrl << " turns into " << newIndexUrl;
netJob->addNetAction(m_newVersionFileListDownload = Net::Download::makeByteArray(newIndexUrl, &newVersionFileListData));
// If we have a current version URL, get that one too.
if (!m_status.currentRepoUrl.isEmpty())
{
QUrl cIndexUrl = QUrl(m_status.currentRepoUrl).resolved(QString::number(m_status.currentVersionId) + ".json");
netJob->addNetAction(m_currentVersionFileListDownload = Net::Download::makeByteArray(cIndexUrl, &currentVersionFileListData));
qDebug() << m_status.currentRepoUrl << " turns into " << cIndexUrl;
}
// connect signals and start the job
connect(netJob, &NetJob::succeeded, this, &DownloadTask::processDownloadedVersionInfo);
connect(netJob, &NetJob::failed, this, &DownloadTask::vinfoDownloadFailed);
m_vinfoNetJob.reset(netJob);
netJob->start();
}
void DownloadTask::vinfoDownloadFailed()
{
// Something failed. We really need the second download (current version info), so parse
// downloads anyways as long as the first one succeeded.
if (m_newVersionFileListDownload->wasSuccessful())
{
processDownloadedVersionInfo();
return;
}
// TODO: Give a more detailed error message.
qCritical() << "Failed to download version info files.";
emitFailed(tr("Failed to download version info files."));
}
void DownloadTask::processDownloadedVersionInfo()
{
VersionFileList m_currentVersionFileList;
VersionFileList m_newVersionFileList;
setStatus(tr("Reading file list for new version..."));
qDebug() << "Reading file list for new version...";
QString error;
if (!parseVersionInfo(newVersionFileListData, m_newVersionFileList, error))
{
qCritical() << error;
emitFailed(error);
return;
}
// if we have the current version info, use it.
if (m_currentVersionFileListDownload && m_currentVersionFileListDownload->wasSuccessful())
{
setStatus(tr("Reading file list for current version..."));
qDebug() << "Reading file list for current version...";
// if this fails, it's not a complete loss.
QString error;
if(!parseVersionInfo( currentVersionFileListData, m_currentVersionFileList, error))
{
qDebug() << error << "This is not a fatal error.";
}
}
// We don't need this any more.
m_currentVersionFileListDownload.reset();
m_newVersionFileListDownload.reset();
m_vinfoNetJob.reset();
setStatus(tr("Processing file lists - figuring out how to install the update..."));
// make a new netjob for the actual update files
NetJob::Ptr netJob = new NetJob("Update Files", m_network);
// fill netJob and operationList
if (!processFileLists(m_currentVersionFileList, m_newVersionFileList, m_status.rootPath, m_updateFilesDir.path(), netJob, m_operations))
{
emitFailed(tr("Failed to process update lists..."));
return;
}
// Now start the download.
QObject::connect(netJob.get(), &NetJob::succeeded, this, &DownloadTask::fileDownloadFinished);
QObject::connect(netJob.get(), &NetJob::progress, this, &DownloadTask::fileDownloadProgressChanged);
QObject::connect(netJob.get(), &NetJob::failed, this, &DownloadTask::fileDownloadFailed);
if(netJob->size() == 1) // Translation issues... see https://github.com/MultiMC/Launcher/issues/1701
{
setStatus(tr("Downloading one update file."));
}
else
{
setStatus(tr("Downloading %1 update files.").arg(QString::number(netJob->size())));
}
qDebug() << "Begin downloading update files to" << m_updateFilesDir.path();
m_filesNetJob = netJob;
m_filesNetJob->start();
}
void DownloadTask::fileDownloadFinished()
{
emitSucceeded();
}
void DownloadTask::fileDownloadFailed(QString reason)
{
qCritical() << "Failed to download update files:" << reason;
emitFailed(tr("Failed to download update files: %1").arg(reason));
}
void DownloadTask::fileDownloadProgressChanged(qint64 current, qint64 total)
{
setProgress(current, total);
}
QString DownloadTask::updateFilesDir()
{
return m_updateFilesDir.path();
}
OperationList DownloadTask::operations()
{
return m_operations;
}
}

View File

@ -1,100 +0,0 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "tasks/Task.h"
#include "net/NetJob.h"
#include "GoUpdate.h"
namespace GoUpdate
{
/*!
* The DownloadTask is a task that takes a given version ID and repository URL,
* downloads that version's files from the repository, and prepares to install them.
*/
class DownloadTask : public Task
{
Q_OBJECT
public:
/**
* Create a download task
*
* target is a template - XXXXXX at the end will be replaced with a random generated string, ensuring uniqueness
*/
explicit DownloadTask(shared_qobject_ptr<QNetworkAccessManager> network, Status status, QString target, QObject* parent = 0);
virtual ~DownloadTask() {};
/// 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);
protected:
//! Entry point for tasks.
virtual void executeTask() override;
/*!
* Downloads the version info files from the repository.
* The files for both the current build, and the build that we're updating to need to be downloaded.
* If the current version's info file can't be found, Prism Launcher will not delete files that
* were removed between versions. It will still replace files that have changed, however.
* Note that although the repository URL for the current version is not given to the update task,
* the task will attempt to look it up in the UpdateChecker's channel list.
* If an error occurs here, the function will call emitFailed and return false.
*/
void loadVersionInfo();
NetJob::Ptr m_vinfoNetJob;
QByteArray currentVersionFileListData;
QByteArray newVersionFileListData;
Net::Download::Ptr m_currentVersionFileListDownload;
Net::Download::Ptr m_newVersionFileListDownload;
NetJob::Ptr m_filesNetJob;
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.
*/
QTemporaryDir m_updateFilesDir;
protected slots:
/*!
* This function is called when version information is finished downloading
* and at least the new file list download succeeded
*/
void processDownloadedVersionInfo();
void vinfoDownloadFailed();
void fileDownloadFinished();
void fileDownloadFailed(QString reason);
void fileDownloadProgressChanged(qint64 current, qint64 total);
private:
shared_qobject_ptr<QNetworkAccessManager> m_network;
};
}

View File

@ -1,198 +0,0 @@
#include "GoUpdate.h"
#include <QDebug>
#include <QDomDocument>
#include <QFile>
#include <FileSystem.h>
#include "net/Download.h"
#include "net/ChecksumValidator.h"
namespace GoUpdate
{
bool parseVersionInfo(const QByteArray &data, VersionFileList &list, QString &error)
{
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
if (jsonError.error != QJsonParseError::NoError)
{
error = QString("Failed to parse version info JSON: %1 at %2")
.arg(jsonError.errorString())
.arg(jsonError.offset);
qCritical() << error;
return false;
}
QJsonObject json = jsonDoc.object();
qDebug() << data;
qDebug() << "Loading version info from JSON.";
QJsonArray filesArray = json.value("Files").toArray();
for (QJsonValue fileValue : filesArray)
{
QJsonObject fileObj = fileValue.toObject();
QString file_path = fileObj.value("Path").toString();
VersionFileEntry file{file_path, fileObj.value("Perms").toVariant().toInt(),
FileSourceList(), fileObj.value("MD5").toString(), };
qDebug() << "File" << file.path << "with perms" << file.mode;
QJsonArray sourceArray = fileObj.value("Sources").toArray();
for (QJsonValue val : sourceArray)
{
QJsonObject sourceObj = val.toObject();
QString type = sourceObj.value("SourceType").toString();
if (type == "http")
{
file.sources.append(FileSource("http", sourceObj.value("Url").toString()));
}
else
{
qWarning() << "Unknown source type" << type << "ignored.";
}
}
qDebug() << "Loaded info for" << file.path;
list.append(file);
}
return true;
}
bool processFileLists
(
const VersionFileList &currentVersion,
const VersionFileList &newVersion,
const QString &rootPath,
const QString &tempPath,
NetJob::Ptr job,
OperationList &ops
)
{
// First, if we've loaded the current version's file list, we need to iterate through it and
// delete anything in the current one version's list that isn't in the new version's list.
for (VersionFileEntry entry : currentVersion)
{
QFileInfo toDelete(FS::PathCombine(rootPath, entry.path));
if (!toDelete.exists())
{
qCritical() << "Expected file " << toDelete.absoluteFilePath()
<< " doesn't exist!";
}
bool keep = false;
//
for (VersionFileEntry newEntry : newVersion)
{
if (newEntry.path == entry.path)
{
qDebug() << "Not deleting" << entry.path
<< "because it is still present in the new version.";
keep = true;
break;
}
}
// If the loop reaches the end and we didn't find a match, delete the file.
if (!keep)
{
if (toDelete.exists())
ops.append(Operation::DeleteOp(entry.path));
}
}
// Next, check each file in Prism Launcher's folder and see if we need to update them.
for (VersionFileEntry entry : newVersion)
{
// TODO: Let's not MD5sum a ton of files on the GUI thread. We should probably find a
// way to do this in the background.
QString fileMD5;
QString realEntryPath = FS::PathCombine(rootPath, entry.path);
QFile entryFile(realEntryPath);
QFileInfo entryInfo(realEntryPath);
bool needs_upgrade = false;
if (!entryFile.exists())
{
needs_upgrade = true;
}
else
{
bool pass = true;
if (!entryInfo.isReadable())
{
qCritical() << "File " << realEntryPath << " is not readable.";
pass = false;
}
if (!entryInfo.isWritable())
{
qCritical() << "File " << realEntryPath << " is not writable.";
pass = false;
}
if (!entryFile.open(QFile::ReadOnly))
{
qCritical() << "File " << realEntryPath << " cannot be opened for reading.";
pass = false;
}
if (!pass)
{
ops.clear();
return false;
}
}
if(!needs_upgrade)
{
QCryptographicHash hash(QCryptographicHash::Md5);
auto foo = entryFile.readAll();
hash.addData(foo);
fileMD5 = hash.result().toHex();
if ((fileMD5 != entry.md5))
{
qDebug() << "MD5Sum does not match!";
qDebug() << "Expected:'" << entry.md5 << "'";
qDebug() << "Got: '" << fileMD5 << "'";
needs_upgrade = true;
}
}
// skip file. it doesn't need an upgrade.
if (!needs_upgrade)
{
qDebug() << "File" << realEntryPath << " does not need updating.";
continue;
}
// yep. this file actually needs an upgrade. PROCEED.
qDebug() << "Found file" << realEntryPath << " that needs updating.";
// Go through the sources list and find one to use.
// TODO: Make a NetAction that takes a source list and tries each of them until one
// works. For now, we'll just use the first http one.
for (FileSource source : entry.sources)
{
if (source.type != "http")
continue;
qDebug() << "Will download" << entry.path << "from" << source.url;
// Download it to updatedir/<filepath>-<md5> where filepath is the file's
// path with slashes replaced by underscores.
QString dlPath = FS::PathCombine(tempPath, QString(entry.path).replace("/", "_"));
// We need to download the file to the updatefiles folder and add a task
// to copy it to its install path.
auto download = Net::Download::makeFile(source.url, dlPath);
auto rawMd5 = QByteArray::fromHex(entry.md5.toLatin1());
download->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
job->addNetAction(download);
ops.append(Operation::CopyOp(dlPath, entry.path, entry.mode));
}
}
return true;
}
}

View File

@ -1,125 +0,0 @@
#pragma once
#include <QByteArray>
#include <net/NetJob.h>
namespace GoUpdate
{
/**
* A temporary object exchanged between updated checker and the actual update task
*/
struct Status
{
bool updateAvailable = false;
int newVersionId = -1;
QString newRepoUrl;
int currentVersionId = -1;
QString currentRepoUrl;
// path to the root of the application
QString rootPath;
};
/**
* Struct that describes an entry in a VersionFileEntry's `Sources` list.
*/
struct FileSource
{
FileSource(QString type, QString url, QString compression="")
{
this->type = type;
this->url = url;
this->compressionType = compression;
}
bool operator==(const FileSource &f2) const
{
return type == f2.type && url == f2.url && compressionType == f2.compressionType;
}
QString type;
QString url;
QString compressionType;
};
typedef QList<FileSource> FileSourceList;
/**
* Structure that describes an entry in a GoUpdate version's `Files` list.
*/
struct VersionFileEntry
{
QString path;
int mode;
FileSourceList sources;
QString md5;
bool operator==(const VersionFileEntry &v2) const
{
return path == v2.path && mode == v2.mode && sources == v2.sources && md5 == v2.md5;
}
};
typedef QList<VersionFileEntry> VersionFileList;
/**
* Structure that describes an operation to perform when installing updates.
*/
struct Operation
{
static Operation CopyOp(QString from, QString to, int fmode=0644)
{
return Operation{OP_REPLACE, from, to, fmode};
}
static Operation DeleteOp(QString file)
{
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 &&
source == u2.source &&
destination == u2.destination &&
destinationMode == u2.destinationMode;
}
//! Specifies the type of operation that this is.
enum Type
{
OP_REPLACE,
OP_DELETE,
} type;
//! The source file, if any
QString source;
//! The destination file.
QString destination;
//! The mode to change the destination file to.
int destinationMode;
};
typedef QList<Operation> OperationList;
/**
* Loads the file list from the given version info JSON object into the given list.
*/
bool parseVersionInfo(const QByteArray &data, VersionFileList& list, QString &error);
/*!
* Takes a list of file entries for the current version's files and the new version's files
* and populates the downloadList and operationList with information about how to download and install the update.
*/
bool processFileLists
(
const VersionFileList &currentVersion,
const VersionFileList &newVersion,
const QString &rootPath,
const QString &tempPath,
NetJob::Ptr job,
OperationList &ops
);
}
Q_DECLARE_METATYPE(GoUpdate::Status)

View File

@ -119,8 +119,6 @@ private:
class Private;
Private *priv;
void loadChannelsFromSettings();
};
#endif //LAUNCHER_MACSPARKLEUPDATER_H

View File

@ -106,8 +106,6 @@ MacSparkleUpdater::MacSparkleUpdater()
priv->updaterObserver.callback = ^(bool canCheck) {
emit canCheckForUpdatesChanged(canCheck);
};
loadChannelsFromSettings();
}
MacSparkleUpdater::~MacSparkleUpdater()
@ -165,7 +163,6 @@ void MacSparkleUpdater::setUpdateCheckInterval(double seconds)
void MacSparkleUpdater::clearAllowedChannels()
{
priv->updaterDelegate.allowedChannels = [NSSet set];
APPLICATION->settings()->set("UpdateChannel", "");
}
void MacSparkleUpdater::setAllowedChannel(const QString &channel)
@ -178,7 +175,6 @@ void MacSparkleUpdater::setAllowedChannel(const QString &channel)
NSSet<NSString *> *nsChannels = [NSSet setWithObject:channel.toNSString()];
priv->updaterDelegate.allowedChannels = nsChannels;
APPLICATION->settings()->set("UpdateChannel", channel);
}
void MacSparkleUpdater::setAllowedChannels(const QSet<QString> &channels)
@ -199,7 +195,6 @@ void MacSparkleUpdater::setAllowedChannels(const QSet<QString> &channels)
}
priv->updaterDelegate.allowedChannels = nsChannels;
APPLICATION->settings()->set("UpdateChannel", channelsConfig.trimmed());
}
void MacSparkleUpdater::setBetaAllowed(bool allowed)
@ -213,10 +208,3 @@ void MacSparkleUpdater::setBetaAllowed(bool allowed)
clearAllowedChannels();
}
}
void MacSparkleUpdater::loadChannelsFromSettings()
{
QStringList channelList = APPLICATION->settings()->get("UpdateChannel").toString().split(" ");
QSet<QString> channels(channelList.begin(), channelList.end());
setAllowedChannels(channels);
}

View File

@ -1,296 +0,0 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "UpdateChecker.h"
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonValue>
#include <QDebug>
#define API_VERSION 0
#define CHANLIST_FORMAT 0
#include "BuildConfig.h"
UpdateChecker::UpdateChecker(shared_qobject_ptr<QNetworkAccessManager> nam, QString channelUrl, QString currentChannel)
{
m_network = nam;
m_channelUrl = channelUrl;
m_currentChannel = currentChannel;
#ifdef Q_OS_MAC
m_externalUpdater = new MacSparkleUpdater();
#endif
}
QList<UpdateChecker::ChannelListEntry> UpdateChecker::getChannelList() const
{
return m_channels;
}
bool UpdateChecker::hasChannels() const
{
return !m_channels.isEmpty();
}
ExternalUpdater* UpdateChecker::getExternalUpdater()
{
return m_externalUpdater;
}
void UpdateChecker::checkForUpdate(const QString& updateChannel, bool notifyNoUpdate)
{
if (m_externalUpdater)
{
m_externalUpdater->setBetaAllowed(updateChannel == "beta");
if (notifyNoUpdate)
{
qDebug() << "Checking for updates.";
m_externalUpdater->checkForUpdates();
} else
{
// The updater library already handles automatic update checks.
return;
}
}
else
{
qDebug() << "Checking for updates.";
// If the channel list hasn't loaded yet, load it and defer checking for updates until
// later.
if (!m_chanListLoaded)
{
qDebug() << "Channel list isn't loaded yet. Loading channel list and deferring update check.";
m_checkUpdateWaiting = true;
m_deferredUpdateChannel = updateChannel;
updateChanList(notifyNoUpdate);
return;
}
if (m_updateChecking)
{
qDebug() << "Ignoring update check request. Already checking for updates.";
return;
}
// Find the desired channel within the channel list and get its repo URL. If if cannot be
// found, error.
QString stableUrl;
m_newRepoUrl = "";
for (ChannelListEntry entry: m_channels)
{
qDebug() << "channelEntry = " << entry.id;
if (entry.id == "stable")
{
stableUrl = entry.url;
}
if (entry.id == updateChannel)
{
m_newRepoUrl = entry.url;
qDebug() << "is intended update channel: " << entry.id;
}
if (entry.id == m_currentChannel)
{
m_currentRepoUrl = entry.url;
qDebug() << "is current update channel: " << entry.id;
}
}
qDebug() << "m_repoUrl = " << m_newRepoUrl;
if (m_newRepoUrl.isEmpty())
{
qWarning() << "m_repoUrl was empty. defaulting to 'stable': " << stableUrl;
m_newRepoUrl = stableUrl;
}
// If nothing applies, error
if (m_newRepoUrl.isEmpty())
{
qCritical() << "failed to select any update repository for: " << updateChannel;
emit updateCheckFailed();
return;
}
m_updateChecking = true;
QUrl indexUrl = QUrl(m_newRepoUrl).resolved(QUrl("index.json"));
indexJob = new NetJob("GoUpdate Repository Index", m_network);
indexJob->addNetAction(Net::Download::makeByteArray(indexUrl, &indexData));
connect(indexJob.get(), &NetJob::succeeded, [this, notifyNoUpdate]() { updateCheckFinished(notifyNoUpdate); });
connect(indexJob.get(), &NetJob::failed, this, &UpdateChecker::updateCheckFailed);
indexJob->start();
}
}
void UpdateChecker::updateCheckFinished(bool notifyNoUpdate)
{
qDebug() << "Finished downloading repo index. Checking for new versions.";
QJsonParseError jsonError;
indexJob.reset();
QJsonDocument jsonDoc = QJsonDocument::fromJson(indexData, &jsonError);
indexData.clear();
if (jsonError.error != QJsonParseError::NoError || !jsonDoc.isObject())
{
qCritical() << "Failed to parse GoUpdate repository index. JSON error"
<< jsonError.errorString() << "at offset" << jsonError.offset;
m_updateChecking = false;
return;
}
QJsonObject object = jsonDoc.object();
bool success = false;
int apiVersion = object.value("ApiVersion").toVariant().toInt(&success);
if (apiVersion != API_VERSION || !success)
{
qCritical() << "Failed to check for updates. API version mismatch. We're using"
<< API_VERSION << "server has" << apiVersion;
m_updateChecking = false;
return;
}
qDebug() << "Processing repository version list.";
QJsonObject newestVersion;
QJsonArray versions = object.value("Versions").toArray();
for (QJsonValue versionVal : versions)
{
QJsonObject version = versionVal.toObject();
if (newestVersion.value("Id").toVariant().toInt() <
version.value("Id").toVariant().toInt())
{
newestVersion = version;
}
}
// We've got the version with the greatest ID number. Now compare it to our current build
// number and update if they're different.
int newBuildNumber = newestVersion.value("Id").toVariant().toInt();
if (newBuildNumber != m_currentBuild)
{
qDebug() << "Found newer version with ID" << newBuildNumber;
// Update!
GoUpdate::Status updateStatus;
updateStatus.updateAvailable = true;
updateStatus.currentVersionId = m_currentBuild;
updateStatus.currentRepoUrl = m_currentRepoUrl;
updateStatus.newVersionId = newBuildNumber;
updateStatus.newRepoUrl = m_newRepoUrl;
emit updateAvailable(updateStatus);
}
else if (notifyNoUpdate)
{
emit noUpdateFound();
}
m_updateChecking = false;
}
void UpdateChecker::updateCheckFailed()
{
qCritical() << "Update check failed for reasons unknown.";
}
void UpdateChecker::updateChanList(bool notifyNoUpdate)
{
qDebug() << "Loading the channel list.";
if (m_chanListLoading)
{
qDebug() << "Ignoring channel list update request. Already grabbing channel list.";
return;
}
m_chanListLoading = true;
chanListJob = new NetJob("Update System Channel List", m_network);
chanListJob->addNetAction(Net::Download::makeByteArray(QUrl(m_channelUrl), &chanlistData));
connect(chanListJob.get(), &NetJob::succeeded, [this, notifyNoUpdate]() { chanListDownloadFinished(notifyNoUpdate); });
connect(chanListJob.get(), &NetJob::failed, this, &UpdateChecker::chanListDownloadFailed);
chanListJob->start();
}
void UpdateChecker::chanListDownloadFinished(bool notifyNoUpdate)
{
chanListJob.reset();
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(chanlistData, &jsonError);
chanlistData.clear();
if (jsonError.error != QJsonParseError::NoError)
{
// TODO: Report errors to the user.
qCritical() << "Failed to parse channel list JSON:" << jsonError.errorString() << "at" << jsonError.offset;
m_chanListLoading = false;
return;
}
QJsonObject object = jsonDoc.object();
bool success = false;
int formatVersion = object.value("format_version").toVariant().toInt(&success);
if (formatVersion != CHANLIST_FORMAT || !success)
{
qCritical()
<< "Failed to check for updates. Channel list format version mismatch. We're using"
<< CHANLIST_FORMAT << "server has" << formatVersion;
m_chanListLoading = false;
return;
}
// Load channels into a temporary array.
QList<ChannelListEntry> loadedChannels;
QJsonArray channelArray = object.value("channels").toArray();
for (QJsonValue chanVal : channelArray)
{
QJsonObject channelObj = chanVal.toObject();
ChannelListEntry entry {
channelObj.value("id").toVariant().toString(),
channelObj.value("name").toVariant().toString(),
channelObj.value("description").toVariant().toString(),
channelObj.value("url").toVariant().toString()
};
if (entry.id.isEmpty() || entry.name.isEmpty() || entry.url.isEmpty())
{
qCritical() << "Channel list entry with empty ID, name, or URL. Skipping.";
continue;
}
loadedChannels.append(entry);
}
// Swap the channel list we just loaded into the object's channel list.
m_channels.swap(loadedChannels);
m_chanListLoading = false;
m_chanListLoaded = true;
qDebug() << "Successfully loaded UpdateChecker channel list.";
// If we're waiting to check for updates, do that now.
if (m_checkUpdateWaiting) {
checkForUpdate(m_deferredUpdateChannel, notifyNoUpdate);
}
emit channelListLoaded();
}
void UpdateChecker::chanListDownloadFailed(QString reason)
{
m_chanListLoading = false;
qCritical() << QString("Failed to download channel list: %1").arg(reason);
emit channelListLoaded();
}

View File

@ -1,140 +0,0 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "net/NetJob.h"
#include "GoUpdate.h"
#include "ExternalUpdater.h"
#ifdef Q_OS_MAC
#include "MacSparkleUpdater.h"
#endif
class UpdateChecker : public QObject
{
Q_OBJECT
public:
UpdateChecker(shared_qobject_ptr<QNetworkAccessManager> nam, QString channelUrl, QString currentChannel);
void checkForUpdate(const QString& updateChannel, bool notifyNoUpdate);
/*!
* Causes the update checker to download the channel list from the URL specified in config.h (generated by CMake).
* If this isn't called before checkForUpdate(), it will automatically be called.
*/
void updateChanList(bool notifyNoUpdate);
/*!
* An entry in the channel list.
*/
struct ChannelListEntry
{
QString id;
QString name;
QString description;
QString url;
};
/*!
* Returns a the current channel list.
* If the channel list hasn't been loaded, this list will be empty.
*/
QList<ChannelListEntry> getChannelList() const;
/*!
* Returns false if the channel list is empty.
*/
bool hasChannels() const;
/*!
* Returns a pointer to an object that controls the external updater, or nullptr if an external updater is not used.
*/
ExternalUpdater *getExternalUpdater();
signals:
//! Signal emitted when an update is available. Passes the URL for the repo and the ID and name for the version.
void updateAvailable(GoUpdate::Status status);
//! Signal emitted when the channel list finishes loading or fails to load.
void channelListLoaded();
void noUpdateFound();
private slots:
void updateCheckFinished(bool notifyNoUpdate);
void updateCheckFailed();
void chanListDownloadFinished(bool notifyNoUpdate);
void chanListDownloadFailed(QString reason);
private:
friend class UpdateCheckerTest;
shared_qobject_ptr<QNetworkAccessManager> m_network;
NetJob::Ptr indexJob;
QByteArray indexData;
NetJob::Ptr chanListJob;
QByteArray chanlistData;
QString m_channelUrl;
QList<ChannelListEntry> m_channels;
/*!
* True while the system is checking for updates.
* If checkForUpdate is called while this is true, it will be ignored.
*/
bool m_updateChecking = false;
/*!
* True if the channel list has loaded.
* If this is false, trying to check for updates will call updateChanList first.
*/
bool m_chanListLoaded = false;
/*!
* Set to true while the channel list is currently loading.
*/
bool m_chanListLoading = false;
/*!
* Set to true when checkForUpdate is called while the channel list isn't loaded.
* When the channel list finishes loading, if this is true, the update checker will check for updates.
*/
bool m_checkUpdateWaiting = false;
/*!
* if m_checkUpdateWaiting, this is the last used update channel
*/
QString m_deferredUpdateChannel;
int m_currentBuild = -1;
QString m_currentChannel;
QString m_currentRepoUrl;
QString m_newRepoUrl;
/*!
* If not a nullptr, then the updater here will be used instead of the old updater that uses GoUpdate when
* checking for updates.
*
* As a result, signals from this class won't be emitted, and most of the functions in this class other
* than checkForUpdate are not useful. Call functions from this external updater object instead.
*/
ExternalUpdater *m_externalUpdater = nullptr;
};