Merge pull request #458 from Scrumplex/remove-updater
This commit is contained in:
commit
0546345182
@ -101,7 +101,7 @@
|
|||||||
|
|
||||||
#include "java/JavaUtils.h"
|
#include "java/JavaUtils.h"
|
||||||
|
|
||||||
#include "updater/UpdateChecker.h"
|
#include "updater/ExternalUpdater.h"
|
||||||
|
|
||||||
#include "tools/JProfiler.h"
|
#include "tools/JProfiler.h"
|
||||||
#include "tools/JVisualVM.h"
|
#include "tools/JVisualVM.h"
|
||||||
@ -125,6 +125,10 @@
|
|||||||
#include "MangoHud.h"
|
#include "MangoHud.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef Q_OS_MAC
|
||||||
|
#include "updater/MacSparkleUpdater.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#if defined Q_OS_WIN32
|
#if defined Q_OS_WIN32
|
||||||
#ifndef WIN32_LEAN_AND_MEAN
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
@ -155,45 +159,6 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QSt
|
|||||||
fflush(stderr);
|
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)
|
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
|
// Provide a fallback for migration from PolyMC
|
||||||
m_settings.reset(new INISettingsObject({ BuildConfig.LAUNCHER_CONFIGFILE, "polymc.cfg", "multimc.cfg" }, this));
|
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
|
// Theming
|
||||||
m_settings->registerSetting("IconTheme", QString("pe_colored"));
|
m_settings->registerSetting("IconTheme", QString("pe_colored"));
|
||||||
@ -740,10 +701,10 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
// initialize the updater
|
// initialize the updater
|
||||||
if(BuildConfig.UPDATER_ENABLED)
|
if(BuildConfig.UPDATER_ENABLED)
|
||||||
{
|
{
|
||||||
auto platform = getIdealPlatform(BuildConfig.BUILD_PLATFORM);
|
qDebug() << "Initializing updater";
|
||||||
auto channelUrl = BuildConfig.UPDATER_BASE + platform + "/channels.json";
|
#ifdef Q_OS_MAC
|
||||||
qDebug() << "Initializing updater with platform: " << platform << " -- " << channelUrl;
|
m_updater.reset(new MacSparkleUpdater());
|
||||||
m_updateChecker.reset(new UpdateChecker(m_network, channelUrl, BuildConfig.VERSION_CHANNEL));
|
#endif
|
||||||
qDebug() << "<> Updater started.";
|
qDebug() << "<> Updater started.";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1698,3 +1659,14 @@ bool Application::handleDataMigration(const QString& currentData,
|
|||||||
}
|
}
|
||||||
return true;
|
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.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -43,7 +43,6 @@
|
|||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <updater/GoUpdate.h>
|
|
||||||
|
|
||||||
#include <BaseInstance.h>
|
#include <BaseInstance.h>
|
||||||
|
|
||||||
@ -63,7 +62,7 @@ class AccountList;
|
|||||||
class IconList;
|
class IconList;
|
||||||
class QNetworkAccessManager;
|
class QNetworkAccessManager;
|
||||||
class JavaInstallList;
|
class JavaInstallList;
|
||||||
class UpdateChecker;
|
class ExternalUpdater;
|
||||||
class BaseProfilerFactory;
|
class BaseProfilerFactory;
|
||||||
class BaseDetachedToolFactory;
|
class BaseDetachedToolFactory;
|
||||||
class TranslationsModel;
|
class TranslationsModel;
|
||||||
@ -126,10 +125,12 @@ public:
|
|||||||
|
|
||||||
void setApplicationTheme(const QString& name);
|
void setApplicationTheme(const QString& name);
|
||||||
|
|
||||||
shared_qobject_ptr<UpdateChecker> updateChecker() {
|
shared_qobject_ptr<ExternalUpdater> updater() {
|
||||||
return m_updateChecker;
|
return m_updater;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void triggerUpdateCheck();
|
||||||
|
|
||||||
std::shared_ptr<TranslationsModel> translations();
|
std::shared_ptr<TranslationsModel> translations();
|
||||||
|
|
||||||
std::shared_ptr<JavaInstallList> javalist();
|
std::shared_ptr<JavaInstallList> javalist();
|
||||||
@ -251,7 +252,7 @@ private:
|
|||||||
|
|
||||||
shared_qobject_ptr<QNetworkAccessManager> m_network;
|
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<AccountList> m_accounts;
|
||||||
|
|
||||||
shared_qobject_ptr<HttpMetaCache> m_metacache;
|
shared_qobject_ptr<HttpMetaCache> m_metacache;
|
||||||
@ -310,4 +311,3 @@ public:
|
|||||||
QString m_instanceIdToShowWindowOf;
|
QString m_instanceIdToShowWindowOf;
|
||||||
std::unique_ptr<QFile> logFile;
|
std::unique_ptr<QFile> logFile;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -161,12 +161,6 @@ set(LAUNCH_SOURCES
|
|||||||
|
|
||||||
# Old update system
|
# Old update system
|
||||||
set(UPDATE_SOURCES
|
set(UPDATE_SOURCES
|
||||||
updater/GoUpdate.h
|
|
||||||
updater/GoUpdate.cpp
|
|
||||||
updater/UpdateChecker.h
|
|
||||||
updater/UpdateChecker.cpp
|
|
||||||
updater/DownloadTask.h
|
|
||||||
updater/DownloadTask.cpp
|
|
||||||
updater/ExternalUpdater.h
|
updater/ExternalUpdater.h
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -621,8 +615,6 @@ SET(LAUNCHER_SOURCES
|
|||||||
Application.cpp
|
Application.cpp
|
||||||
DataMigrationTask.h
|
DataMigrationTask.h
|
||||||
DataMigrationTask.cpp
|
DataMigrationTask.cpp
|
||||||
UpdateController.cpp
|
|
||||||
UpdateController.h
|
|
||||||
ApplicationMessage.h
|
ApplicationMessage.h
|
||||||
ApplicationMessage.cpp
|
ApplicationMessage.cpp
|
||||||
|
|
||||||
@ -868,8 +860,6 @@ SET(LAUNCHER_SOURCES
|
|||||||
ui/dialogs/ProgressDialog.h
|
ui/dialogs/ProgressDialog.h
|
||||||
ui/dialogs/ReviewMessageBox.cpp
|
ui/dialogs/ReviewMessageBox.cpp
|
||||||
ui/dialogs/ReviewMessageBox.h
|
ui/dialogs/ReviewMessageBox.h
|
||||||
ui/dialogs/UpdateDialog.cpp
|
|
||||||
ui/dialogs/UpdateDialog.h
|
|
||||||
ui/dialogs/VersionSelectDialog.cpp
|
ui/dialogs/VersionSelectDialog.cpp
|
||||||
ui/dialogs/VersionSelectDialog.h
|
ui/dialogs/VersionSelectDialog.h
|
||||||
ui/dialogs/SkinUploadDialog.cpp
|
ui/dialogs/SkinUploadDialog.cpp
|
||||||
@ -986,7 +976,6 @@ qt_wrap_ui(LAUNCHER_UI
|
|||||||
ui/dialogs/ProfileSetupDialog.ui
|
ui/dialogs/ProfileSetupDialog.ui
|
||||||
ui/dialogs/ProgressDialog.ui
|
ui/dialogs/ProgressDialog.ui
|
||||||
ui/dialogs/NewInstanceDialog.ui
|
ui/dialogs/NewInstanceDialog.ui
|
||||||
ui/dialogs/UpdateDialog.ui
|
|
||||||
ui/dialogs/NewComponentDialog.ui
|
ui/dialogs/NewComponentDialog.ui
|
||||||
ui/dialogs/NewsDialog.ui
|
ui/dialogs/NewsDialog.ui
|
||||||
ui/dialogs/ProfileSelectDialog.ui
|
ui/dialogs/ProfileSelectDialog.ui
|
||||||
|
@ -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;
|
|
||||||
}
|
|
@ -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;
|
|
||||||
};
|
|
@ -36,6 +36,7 @@
|
|||||||
#include "LauncherPartLaunch.h"
|
#include "LauncherPartLaunch.h"
|
||||||
|
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
|
||||||
#include "launch/LaunchTask.h"
|
#include "launch/LaunchTask.h"
|
||||||
#include "minecraft/MinecraftInstance.h"
|
#include "minecraft/MinecraftInstance.h"
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "BuildConfig.h"
|
#include "BuildConfig.h"
|
||||||
#include "Json.h"
|
#include "Json.h"
|
||||||
|
#include "net/NetJob.h"
|
||||||
#include "net/Upload.h"
|
#include "net/Upload.h"
|
||||||
|
|
||||||
Task::Ptr FlameAPI::matchFingerprints(const QList<uint>& fingerprints, QByteArray* response)
|
Task::Ptr FlameAPI::matchFingerprints(const QList<uint>& fingerprints, QByteArray* response)
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "Json.h"
|
#include "Json.h"
|
||||||
|
#include "net/NetJob.h"
|
||||||
#include "net/Upload.h"
|
#include "net/Upload.h"
|
||||||
|
|
||||||
Task::Ptr ModrinthAPI::currentVersion(QString hash, QString hash_format, QByteArray* response)
|
Task::Ptr ModrinthAPI::currentVersion(QString hash, QString hash_format, QByteArray* response)
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
#include "MetaCacheSink.h"
|
#include "MetaCacheSink.h"
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
#include <QRegularExpression>
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
|
||||||
namespace Net {
|
namespace Net {
|
||||||
|
@ -41,9 +41,11 @@
|
|||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
#include <QHttpPart>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
#include <QUrlQuery>
|
||||||
|
|
||||||
std::array<PasteUpload::PasteTypeInfo, 4> PasteUpload::PasteTypes = {
|
std::array<PasteUpload::PasteTypeInfo, 4> PasteUpload::PasteTypes = {
|
||||||
{{"0x0.st", "https://0x0.st", ""},
|
{{"0x0.st", "https://0x0.st", ""},
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
|
||||||
#include "ui/dialogs/ProgressDialog.h"
|
#include "ui/dialogs/ProgressDialog.h"
|
||||||
#include "ui/dialogs/CustomMessageBox.h"
|
#include "ui/dialogs/CustomMessageBox.h"
|
||||||
|
@ -86,8 +86,7 @@
|
|||||||
#include <net/Download.h>
|
#include <net/Download.h>
|
||||||
#include <news/NewsChecker.h>
|
#include <news/NewsChecker.h>
|
||||||
#include <tools/BaseProfiler.h>
|
#include <tools/BaseProfiler.h>
|
||||||
#include <updater/DownloadTask.h>
|
#include <updater/ExternalUpdater.h>
|
||||||
#include <updater/UpdateChecker.h>
|
|
||||||
#include <DesktopServices.h>
|
#include <DesktopServices.h>
|
||||||
#include "InstanceWindow.h"
|
#include "InstanceWindow.h"
|
||||||
#include "InstancePageProvider.h"
|
#include "InstancePageProvider.h"
|
||||||
@ -102,11 +101,9 @@
|
|||||||
#include "ui/dialogs/NewsDialog.h"
|
#include "ui/dialogs/NewsDialog.h"
|
||||||
#include "ui/dialogs/ProgressDialog.h"
|
#include "ui/dialogs/ProgressDialog.h"
|
||||||
#include "ui/dialogs/AboutDialog.h"
|
#include "ui/dialogs/AboutDialog.h"
|
||||||
#include "ui/dialogs/VersionSelectDialog.h"
|
|
||||||
#include "ui/dialogs/CustomMessageBox.h"
|
#include "ui/dialogs/CustomMessageBox.h"
|
||||||
#include "ui/dialogs/IconPickerDialog.h"
|
#include "ui/dialogs/IconPickerDialog.h"
|
||||||
#include "ui/dialogs/CopyInstanceDialog.h"
|
#include "ui/dialogs/CopyInstanceDialog.h"
|
||||||
#include "ui/dialogs/UpdateDialog.h"
|
|
||||||
#include "ui/dialogs/EditAccountDialog.h"
|
#include "ui/dialogs/EditAccountDialog.h"
|
||||||
#include "ui/dialogs/ExportInstanceDialog.h"
|
#include "ui/dialogs/ExportInstanceDialog.h"
|
||||||
#include "ui/dialogs/ImportResourceDialog.h"
|
#include "ui/dialogs/ImportResourceDialog.h"
|
||||||
@ -117,7 +114,6 @@
|
|||||||
#include "minecraft/mod/ModFolderModel.h"
|
#include "minecraft/mod/ModFolderModel.h"
|
||||||
#include "minecraft/WorldList.h"
|
#include "minecraft/WorldList.h"
|
||||||
|
|
||||||
#include "UpdateController.h"
|
|
||||||
#include "KonamiCode.h"
|
#include "KonamiCode.h"
|
||||||
|
|
||||||
#include "InstanceImportTask.h"
|
#include "InstanceImportTask.h"
|
||||||
@ -1061,9 +1057,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
|
|||||||
updateNewsLabel();
|
updateNewsLabel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (BuildConfig.UPDATER_ENABLED) {
|
||||||
if(BuildConfig.UPDATER_ENABLED)
|
|
||||||
{
|
|
||||||
bool updatesAllowed = APPLICATION->updatesAreAllowed();
|
bool updatesAllowed = APPLICATION->updatesAreAllowed();
|
||||||
updatesAllowedChanged(updatesAllowed);
|
updatesAllowedChanged(updatesAllowed);
|
||||||
|
|
||||||
@ -1071,21 +1065,10 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
|
|||||||
connect(ui->actionCheckUpdate.operator->(), &QAction::triggered, this, &MainWindow::checkForUpdates);
|
connect(ui->actionCheckUpdate.operator->(), &QAction::triggered, this, &MainWindow::checkForUpdates);
|
||||||
|
|
||||||
// set up the updater object.
|
// set up the updater object.
|
||||||
auto updater = APPLICATION->updateChecker();
|
auto updater = APPLICATION->updater();
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (APPLICATION->updateChecker()->getExternalUpdater())
|
if (updater) {
|
||||||
{
|
connect(updater.get(), &ExternalUpdater::canCheckForUpdatesChanged, this, &MainWindow::updatesAllowedChanged);
|
||||||
connect(APPLICATION->updateChecker()->getExternalUpdater(),
|
|
||||||
&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)
|
QList<int> stringToIntList(const QString &string)
|
||||||
{
|
{
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||||
@ -1613,40 +1570,6 @@ QString intListToString(const QList<int> &list)
|
|||||||
return slist.join(',');
|
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)
|
void MainWindow::onCatToggled(bool state)
|
||||||
{
|
{
|
||||||
setCatBackground(state);
|
setCatBackground(state);
|
||||||
@ -1983,8 +1906,7 @@ void MainWindow::checkForUpdates()
|
|||||||
{
|
{
|
||||||
if(BuildConfig.UPDATER_ENABLED)
|
if(BuildConfig.UPDATER_ENABLED)
|
||||||
{
|
{
|
||||||
auto updater = APPLICATION->updateChecker();
|
APPLICATION->triggerUpdateCheck();
|
||||||
updater->checkForUpdate(APPLICATION->settings()->get("UpdateChannel").toString(), true);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -48,7 +48,6 @@
|
|||||||
#include "BaseInstance.h"
|
#include "BaseInstance.h"
|
||||||
#include "minecraft/auth/MinecraftAccount.h"
|
#include "minecraft/auth/MinecraftAccount.h"
|
||||||
#include "net/NetJob.h"
|
#include "net/NetJob.h"
|
||||||
#include "updater/GoUpdate.h"
|
|
||||||
|
|
||||||
class LaunchController;
|
class LaunchController;
|
||||||
class NewsChecker;
|
class NewsChecker;
|
||||||
@ -192,10 +191,6 @@ private slots:
|
|||||||
|
|
||||||
void startTask(Task *task);
|
void startTask(Task *task);
|
||||||
|
|
||||||
void updateAvailable(GoUpdate::Status status);
|
|
||||||
|
|
||||||
void updateNotAvailable();
|
|
||||||
|
|
||||||
void defaultAccountChanged();
|
void defaultAccountChanged();
|
||||||
|
|
||||||
void changeActiveAccount();
|
void changeActiveAccount();
|
||||||
@ -204,10 +199,6 @@ private slots:
|
|||||||
|
|
||||||
void updateNewsLabel();
|
void updateNewsLabel();
|
||||||
|
|
||||||
/*!
|
|
||||||
* Runs the DownloadTask and installs updates.
|
|
||||||
*/
|
|
||||||
void downloadUpdates(GoUpdate::Status status);
|
|
||||||
|
|
||||||
void konamiTriggered();
|
void konamiTriggered();
|
||||||
|
|
||||||
@ -256,4 +247,3 @@ private:
|
|||||||
// managed by the application object
|
// managed by the application object
|
||||||
Task *m_versionLoadTask = nullptr;
|
Task *m_versionLoadTask = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -32,10 +32,14 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
#include <QDialogButtonBox>
|
#include <QDialogButtonBox>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QDirIterator>
|
||||||
#include <QDragEnterEvent>
|
#include <QDragEnterEvent>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
#include <QMimeData>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
|
#include <QMimeData>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
|
|
||||||
BlockedModsDialog::BlockedModsDialog(QWidget* parent, const QString& title, const QString& text, QList<BlockedMod>& mods)
|
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
|
if (url.scheme().isEmpty()) { // ensure isLocalFile() works correctly
|
||||||
url.setScheme("file");
|
url.setScheme("file");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!url.isLocalFile()) { // can't drop external files here.
|
if (!url.isLocalFile()) { // can't drop external files here.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@
|
|||||||
#include <QSortFilterProxyModel>
|
#include <QSortFilterProxyModel>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QSaveFile>
|
#include <QSaveFile>
|
||||||
|
#include <QStack>
|
||||||
#include "StringUtils.h"
|
#include "StringUtils.h"
|
||||||
#include "SeparatorPrefixTree.h"
|
#include "SeparatorPrefixTree.h"
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
@ -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);
|
|
||||||
}
|
|
@ -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;
|
|
||||||
};
|
|
@ -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>
|
|
@ -44,14 +44,13 @@
|
|||||||
#include <QTextCharFormat>
|
#include <QTextCharFormat>
|
||||||
#include <QMenuBar>
|
#include <QMenuBar>
|
||||||
|
|
||||||
#include "updater/UpdateChecker.h"
|
|
||||||
|
|
||||||
#include "settings/SettingsObject.h"
|
#include "settings/SettingsObject.h"
|
||||||
#include <FileSystem.h>
|
#include <FileSystem.h>
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "BuildConfig.h"
|
#include "BuildConfig.h"
|
||||||
#include "DesktopServices.h"
|
#include "DesktopServices.h"
|
||||||
#include "ui/themes/ITheme.h"
|
#include "ui/themes/ITheme.h"
|
||||||
|
#include "updater/ExternalUpdater.h"
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
@ -80,30 +79,8 @@ LauncherPage::LauncherPage(QWidget *parent) : QWidget(parent), ui(new Ui::Launch
|
|||||||
m_languageModel = APPLICATION->translations();
|
m_languageModel = APPLICATION->translations();
|
||||||
loadSettings();
|
loadSettings();
|
||||||
|
|
||||||
if(BuildConfig.UPDATER_ENABLED)
|
ui->updateSettingsBox->setHidden(!APPLICATION->updater());
|
||||||
{
|
|
||||||
QObject::connect(APPLICATION->updateChecker().get(), &UpdateChecker::channelListLoaded, this, &LauncherPage::refreshUpdateChannelList);
|
|
||||||
|
|
||||||
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->fontSizeBox, SIGNAL(valueChanged(int)), SLOT(refreshFontPreview()));
|
||||||
connect(ui->consoleFont, SIGNAL(currentFontChanged(QFont)), 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());
|
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()
|
void LauncherPage::applySettings()
|
||||||
{
|
{
|
||||||
auto s = APPLICATION->settings();
|
auto s = APPLICATION->settings();
|
||||||
|
|
||||||
// Updates
|
// Updates
|
||||||
if (BuildConfig.UPDATER_ENABLED && APPLICATION->updateChecker()->getExternalUpdater())
|
if (APPLICATION->updater())
|
||||||
{
|
{
|
||||||
APPLICATION->updateChecker()->getExternalUpdater()->setAutomaticallyChecksForUpdates(
|
APPLICATION->updater()->setAutomaticallyChecksForUpdates(ui->autoUpdateCheckBox->isChecked());
|
||||||
ui->autoUpdateCheckBox->isChecked());
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
s->set("AutoUpdate", ui->autoUpdateCheckBox->isChecked());
|
|
||||||
}
|
|
||||||
|
|
||||||
s->set("UpdateChannel", m_currentUpdateChannel);
|
|
||||||
|
|
||||||
s->set("MenuBarInsteadOfToolBar", ui->preferMenuBarCheckBox->isChecked());
|
s->set("MenuBarInsteadOfToolBar", ui->preferMenuBarCheckBox->isChecked());
|
||||||
|
|
||||||
@ -326,17 +224,11 @@ void LauncherPage::loadSettings()
|
|||||||
{
|
{
|
||||||
auto s = APPLICATION->settings();
|
auto s = APPLICATION->settings();
|
||||||
// Updates
|
// Updates
|
||||||
if (BuildConfig.UPDATER_ENABLED && APPLICATION->updateChecker()->getExternalUpdater())
|
if (APPLICATION->updater())
|
||||||
{
|
{
|
||||||
ui->autoUpdateCheckBox->setChecked(
|
ui->autoUpdateCheckBox->setChecked(APPLICATION->updater()->getAutomaticallyChecksForUpdates());
|
||||||
APPLICATION->updateChecker()->getExternalUpdater()->getAutomaticallyChecksForUpdates());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ui->autoUpdateCheckBox->setChecked(s->get("AutoUpdate").toBool());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_currentUpdateChannel = s->get("UpdateChannel").toString();
|
|
||||||
|
|
||||||
// Toolbar/menu bar settings (not applicable if native menu bar is present)
|
// Toolbar/menu bar settings (not applicable if native menu bar is present)
|
||||||
ui->toolsBox->setEnabled(!QMenuBar().isNativeMenuBar());
|
ui->toolsBox->setEnabled(!QMenuBar().isNativeMenuBar());
|
||||||
|
@ -90,23 +90,11 @@ slots:
|
|||||||
void on_iconsDirBrowseBtn_clicked();
|
void on_iconsDirBrowseBtn_clicked();
|
||||||
void on_metadataDisableBtn_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
|
* Updates the font preview
|
||||||
*/
|
*/
|
||||||
void refreshFontPreview();
|
void refreshFontPreview();
|
||||||
|
|
||||||
void updateChannelSelectionChanged(int index);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::LauncherPage *ui;
|
Ui::LauncherPage *ui;
|
||||||
|
|
||||||
|
@ -58,33 +58,6 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="updateChannelLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>Up&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>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -440,7 +413,6 @@
|
|||||||
<tabstops>
|
<tabstops>
|
||||||
<tabstop>tabWidget</tabstop>
|
<tabstop>tabWidget</tabstop>
|
||||||
<tabstop>autoUpdateCheckBox</tabstop>
|
<tabstop>autoUpdateCheckBox</tabstop>
|
||||||
<tabstop>updateChannelComboBox</tabstop>
|
|
||||||
<tabstop>instDirTextBox</tabstop>
|
<tabstop>instDirTextBox</tabstop>
|
||||||
<tabstop>instDirBrowseBtn</tabstop>
|
<tabstop>instDirBrowseBtn</tabstop>
|
||||||
<tabstop>modsDirTextBox</tabstop>
|
<tabstop>modsDirTextBox</tabstop>
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
|
||||||
#include <QIcon>
|
#include <QIdentityProxyModel>
|
||||||
#include <QScrollBar>
|
#include <QScrollBar>
|
||||||
#include <QShortcut>
|
#include <QShortcut>
|
||||||
|
|
||||||
|
@ -44,6 +44,7 @@
|
|||||||
|
|
||||||
class QFileSystemModel;
|
class QFileSystemModel;
|
||||||
class QIdentityProxyModel;
|
class QIdentityProxyModel;
|
||||||
|
class QItemSelection;
|
||||||
namespace Ui
|
namespace Ui
|
||||||
{
|
{
|
||||||
class ScreenshotsPage;
|
class ScreenshotsPage;
|
||||||
|
@ -50,6 +50,7 @@
|
|||||||
|
|
||||||
#include <QFileSystemWatcher>
|
#include <QFileSystemWatcher>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
static const int COLUMN_COUNT = 2; // 3 , TBD: latency and other nice things.
|
static const int COLUMN_COUNT = 2; // 3 , TBD: latency and other nice things.
|
||||||
|
|
||||||
|
@ -45,9 +45,9 @@
|
|||||||
#include <QKeyEvent>
|
#include <QKeyEvent>
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
|
#include <QSortFilterProxyModel>
|
||||||
#include <QTreeView>
|
#include <QTreeView>
|
||||||
#include <QInputDialog>
|
#include <QInputDialog>
|
||||||
#include <QProcess>
|
|
||||||
#include <Qt>
|
#include <Qt>
|
||||||
|
|
||||||
#include "tools/MCEditTool.h"
|
#include "tools/MCEditTool.h"
|
||||||
|
@ -35,6 +35,8 @@
|
|||||||
|
|
||||||
#include "ListModel.h"
|
#include "ListModel.h"
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
#include "net/HttpMetaCache.h"
|
||||||
|
#include "net/NetJob.h"
|
||||||
|
|
||||||
#include "StringUtils.h"
|
#include "StringUtils.h"
|
||||||
#include <Version.h>
|
#include <Version.h>
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
|
|
||||||
#include "modplatform/modrinth/ModrinthPackManifest.h"
|
#include "modplatform/modrinth/ModrinthPackManifest.h"
|
||||||
|
#include "net/NetJob.h"
|
||||||
#include "ui/pages/modplatform/modrinth/ModrinthPage.h"
|
#include "ui/pages/modplatform/modrinth/ModrinthPage.h"
|
||||||
|
|
||||||
class ModPage;
|
class ModPage;
|
||||||
|
@ -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, ¤tVersionFileListData));
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -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 ¤tVersion,
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 ¤tVersion,
|
|
||||||
const VersionFileList &newVersion,
|
|
||||||
const QString &rootPath,
|
|
||||||
const QString &tempPath,
|
|
||||||
NetJob::Ptr job,
|
|
||||||
OperationList &ops
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
Q_DECLARE_METATYPE(GoUpdate::Status)
|
|
@ -119,8 +119,6 @@ private:
|
|||||||
class Private;
|
class Private;
|
||||||
|
|
||||||
Private *priv;
|
Private *priv;
|
||||||
|
|
||||||
void loadChannelsFromSettings();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //LAUNCHER_MACSPARKLEUPDATER_H
|
#endif //LAUNCHER_MACSPARKLEUPDATER_H
|
||||||
|
@ -106,8 +106,6 @@ MacSparkleUpdater::MacSparkleUpdater()
|
|||||||
priv->updaterObserver.callback = ^(bool canCheck) {
|
priv->updaterObserver.callback = ^(bool canCheck) {
|
||||||
emit canCheckForUpdatesChanged(canCheck);
|
emit canCheckForUpdatesChanged(canCheck);
|
||||||
};
|
};
|
||||||
|
|
||||||
loadChannelsFromSettings();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MacSparkleUpdater::~MacSparkleUpdater()
|
MacSparkleUpdater::~MacSparkleUpdater()
|
||||||
@ -165,7 +163,6 @@ void MacSparkleUpdater::setUpdateCheckInterval(double seconds)
|
|||||||
void MacSparkleUpdater::clearAllowedChannels()
|
void MacSparkleUpdater::clearAllowedChannels()
|
||||||
{
|
{
|
||||||
priv->updaterDelegate.allowedChannels = [NSSet set];
|
priv->updaterDelegate.allowedChannels = [NSSet set];
|
||||||
APPLICATION->settings()->set("UpdateChannel", "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacSparkleUpdater::setAllowedChannel(const QString &channel)
|
void MacSparkleUpdater::setAllowedChannel(const QString &channel)
|
||||||
@ -178,7 +175,6 @@ void MacSparkleUpdater::setAllowedChannel(const QString &channel)
|
|||||||
|
|
||||||
NSSet<NSString *> *nsChannels = [NSSet setWithObject:channel.toNSString()];
|
NSSet<NSString *> *nsChannels = [NSSet setWithObject:channel.toNSString()];
|
||||||
priv->updaterDelegate.allowedChannels = nsChannels;
|
priv->updaterDelegate.allowedChannels = nsChannels;
|
||||||
APPLICATION->settings()->set("UpdateChannel", channel);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacSparkleUpdater::setAllowedChannels(const QSet<QString> &channels)
|
void MacSparkleUpdater::setAllowedChannels(const QSet<QString> &channels)
|
||||||
@ -199,7 +195,6 @@ void MacSparkleUpdater::setAllowedChannels(const QSet<QString> &channels)
|
|||||||
}
|
}
|
||||||
|
|
||||||
priv->updaterDelegate.allowedChannels = nsChannels;
|
priv->updaterDelegate.allowedChannels = nsChannels;
|
||||||
APPLICATION->settings()->set("UpdateChannel", channelsConfig.trimmed());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacSparkleUpdater::setBetaAllowed(bool allowed)
|
void MacSparkleUpdater::setBetaAllowed(bool allowed)
|
||||||
@ -213,10 +208,3 @@ void MacSparkleUpdater::setBetaAllowed(bool allowed)
|
|||||||
clearAllowedChannels();
|
clearAllowedChannels();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MacSparkleUpdater::loadChannelsFromSettings()
|
|
||||||
{
|
|
||||||
QStringList channelList = APPLICATION->settings()->get("UpdateChannel").toString().split(" ");
|
|
||||||
QSet<QString> channels(channelList.begin(), channelList.end());
|
|
||||||
setAllowedChannels(channels);
|
|
||||||
}
|
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
};
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user