Fully remove updater.
This commit is contained in:
parent
33000fe333
commit
b507c69d09
@ -46,7 +46,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_DEPRECATED_WARNINGS=Y")
|
|||||||
##################################### Set Application options #####################################
|
##################################### Set Application options #####################################
|
||||||
|
|
||||||
######## Set URLs ########
|
######## Set URLs ########
|
||||||
set(Launcher_NEWS_RSS_URL "https://multimc.org/rss.xml" CACHE STRING "URL to fetch PolyMC's news RSS feed from.")
|
set(Launcher_NEWS_RSS_URL "https://multimc.org/rss.xml" CACHE STRING "URL to fetch SneedMC's news RSS feed from.")
|
||||||
set(Launcher_NEWS_OPEN_URL "https://multimc.org/posts.html" CACHE STRING "URL that gets opened when the user clicks 'More News'")
|
set(Launcher_NEWS_OPEN_URL "https://multimc.org/posts.html" CACHE STRING "URL that gets opened when the user clicks 'More News'")
|
||||||
|
|
||||||
######## Set version numbers ########
|
######## Set version numbers ########
|
||||||
@ -60,9 +60,6 @@ set(Launcher_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number
|
|||||||
# Build platform.
|
# Build platform.
|
||||||
set(Launcher_BUILD_PLATFORM "" CACHE STRING "A short string identifying the platform that this build was built for. Only used by the notification system and to display in the about dialog.")
|
set(Launcher_BUILD_PLATFORM "" CACHE STRING "A short string identifying the platform that this build was built for. Only used by the notification system and to display in the about dialog.")
|
||||||
|
|
||||||
# Channel list URL
|
|
||||||
set(Launcher_UPDATER_BASE "" CACHE STRING "Base URL for the updater.")
|
|
||||||
|
|
||||||
# Notification URL
|
# Notification URL
|
||||||
set(Launcher_NOTIFICATION_URL "" CACHE STRING "URL for checking for notifications.")
|
set(Launcher_NOTIFICATION_URL "" CACHE STRING "URL for checking for notifications.")
|
||||||
|
|
||||||
|
@ -24,7 +24,6 @@ Config::Config()
|
|||||||
VERSION_BUILD = @Launcher_VERSION_BUILD@;
|
VERSION_BUILD = @Launcher_VERSION_BUILD@;
|
||||||
|
|
||||||
BUILD_PLATFORM = "@Launcher_BUILD_PLATFORM@";
|
BUILD_PLATFORM = "@Launcher_BUILD_PLATFORM@";
|
||||||
UPDATER_BASE = "@Launcher_UPDATER_BASE@";
|
|
||||||
NOTIFICATION_URL = "@Launcher_NOTIFICATION_URL@";
|
NOTIFICATION_URL = "@Launcher_NOTIFICATION_URL@";
|
||||||
FULL_VERSION_STR = "@Launcher_VERSION_MAJOR@.@Launcher_VERSION_MINOR@.@Launcher_VERSION_BUILD@";
|
FULL_VERSION_STR = "@Launcher_VERSION_MAJOR@.@Launcher_VERSION_MINOR@.@Launcher_VERSION_BUILD@";
|
||||||
|
|
||||||
@ -34,9 +33,6 @@ Config::Config()
|
|||||||
{
|
{
|
||||||
VERSION_CHANNEL = GIT_REFSPEC;
|
VERSION_CHANNEL = GIT_REFSPEC;
|
||||||
VERSION_CHANNEL.remove("refs/heads/");
|
VERSION_CHANNEL.remove("refs/heads/");
|
||||||
if(!UPDATER_BASE.isEmpty() && !BUILD_PLATFORM.isEmpty() && VERSION_BUILD >= 0) {
|
|
||||||
UPDATER_ENABLED = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (!GIT_COMMIT.isEmpty())
|
else if (!GIT_COMMIT.isEmpty())
|
||||||
{
|
{
|
||||||
|
@ -25,27 +25,16 @@ public:
|
|||||||
/// The build number.
|
/// The build number.
|
||||||
int VERSION_BUILD;
|
int VERSION_BUILD;
|
||||||
|
|
||||||
/**
|
|
||||||
* The version channel
|
|
||||||
* This is used by the updater to determine what channel the current version came from.
|
|
||||||
*/
|
|
||||||
QString VERSION_CHANNEL;
|
|
||||||
|
|
||||||
bool UPDATER_ENABLED = false;
|
|
||||||
|
|
||||||
/// A short string identifying this build's platform. For example, "lin64" or "win32".
|
/// A short string identifying this build's platform. For example, "lin64" or "win32".
|
||||||
QString BUILD_PLATFORM;
|
QString BUILD_PLATFORM;
|
||||||
|
|
||||||
/// URL for the updater's channel
|
|
||||||
QString UPDATER_BASE;
|
|
||||||
|
|
||||||
|
|
||||||
/// User-Agent to use.
|
/// User-Agent to use.
|
||||||
QString USER_AGENT;
|
QString USER_AGENT;
|
||||||
|
|
||||||
/// User-Agent to use for uncached requests.
|
/// User-Agent to use for uncached requests.
|
||||||
QString USER_AGENT_UNCACHED;
|
QString USER_AGENT_UNCACHED;
|
||||||
|
|
||||||
|
QString VERSION_CHANNEL;
|
||||||
|
|
||||||
/// URL for notifications
|
/// URL for notifications
|
||||||
QString NOTIFICATION_URL;
|
QString NOTIFICATION_URL;
|
||||||
|
@ -54,8 +54,6 @@
|
|||||||
|
|
||||||
#include "java/JavaUtils.h"
|
#include "java/JavaUtils.h"
|
||||||
|
|
||||||
#include "updater/UpdateChecker.h"
|
|
||||||
|
|
||||||
#include "tools/JProfiler.h"
|
#include "tools/JProfiler.h"
|
||||||
#include "tools/JVisualVM.h"
|
#include "tools/JVisualVM.h"
|
||||||
#include "tools/MCEditTool.h"
|
#include "tools/MCEditTool.h"
|
||||||
@ -115,45 +113,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)
|
||||||
@ -319,7 +278,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
QString xdgDataHome = QFile::decodeName(qgetenv("XDG_DATA_HOME"));
|
QString xdgDataHome = QFile::decodeName(qgetenv("XDG_DATA_HOME"));
|
||||||
if (xdgDataHome.isEmpty())
|
if (xdgDataHome.isEmpty())
|
||||||
xdgDataHome = QDir::homePath() + QLatin1String("/.local/share");
|
xdgDataHome = QDir::homePath() + QLatin1String("/.local/share");
|
||||||
dataPath = xdgDataHome + "/polymc";
|
dataPath = xdgDataHome + "/sneedmc";
|
||||||
adjustedBy += "XDG standard " + dataPath;
|
adjustedBy += "XDG standard " + dataPath;
|
||||||
#elif defined(Q_OS_MAC)
|
#elif defined(Q_OS_MAC)
|
||||||
QDir foo(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), ".."));
|
QDir foo(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), ".."));
|
||||||
@ -582,9 +541,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
// Initialize application settings
|
// Initialize application settings
|
||||||
{
|
{
|
||||||
m_settings.reset(new INISettingsObject(BuildConfig.LAUNCHER_CONFIGFILE, this));
|
m_settings.reset(new INISettingsObject(BuildConfig.LAUNCHER_CONFIGFILE, this));
|
||||||
// Updates
|
|
||||||
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"));
|
||||||
@ -704,8 +660,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
|
|
||||||
m_settings->registerSetting("NewInstanceGeometry", "");
|
m_settings->registerSetting("NewInstanceGeometry", "");
|
||||||
|
|
||||||
m_settings->registerSetting("UpdateDialogGeometry", "");
|
|
||||||
|
|
||||||
// pastebin URL
|
// pastebin URL
|
||||||
m_settings->registerSetting("PastebinURL", "https://0x0.st");
|
m_settings->registerSetting("PastebinURL", "https://0x0.st");
|
||||||
|
|
||||||
@ -755,16 +709,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
qDebug() << "<> Translations loaded.";
|
qDebug() << "<> Translations loaded.";
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize the updater
|
|
||||||
if(BuildConfig.UPDATER_ENABLED)
|
|
||||||
{
|
|
||||||
auto platform = getIdealPlatform(BuildConfig.BUILD_PLATFORM);
|
|
||||||
auto channelUrl = BuildConfig.UPDATER_BASE + platform + "/channels.json";
|
|
||||||
qDebug() << "Initializing updater with platform: " << platform << " -- " << channelUrl;
|
|
||||||
m_updateChecker.reset(new UpdateChecker(m_network, channelUrl, BuildConfig.VERSION_CHANNEL, BuildConfig.VERSION_BUILD));
|
|
||||||
qDebug() << "<> Updater started.";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Instance icons
|
// Instance icons
|
||||||
{
|
{
|
||||||
auto setting = APPLICATION->settings()->getSetting("IconsDir");
|
auto setting = APPLICATION->settings()->getSetting("IconsDir");
|
||||||
@ -1145,7 +1089,7 @@ void Application::setIconTheme(const QString& name)
|
|||||||
QIcon Application::getThemedIcon(const QString& name)
|
QIcon Application::getThemedIcon(const QString& name)
|
||||||
{
|
{
|
||||||
if(name == "logo") {
|
if(name == "logo") {
|
||||||
return QIcon(":/org.polymc.PolyMC.svg");
|
return QIcon(":/org.sneederix.SneedMC.svg");
|
||||||
}
|
}
|
||||||
return XdgIcon::fromTheme(name);
|
return XdgIcon::fromTheme(name);
|
||||||
}
|
}
|
||||||
@ -1171,11 +1115,7 @@ bool Application::launch(
|
|||||||
MinecraftServerTargetPtr serverToJoin,
|
MinecraftServerTargetPtr serverToJoin,
|
||||||
MinecraftAccountPtr accountToUse
|
MinecraftAccountPtr accountToUse
|
||||||
) {
|
) {
|
||||||
if(m_updateRunning)
|
if(instance->canLaunch())
|
||||||
{
|
|
||||||
qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
|
|
||||||
}
|
|
||||||
else if(instance->canLaunch())
|
|
||||||
{
|
{
|
||||||
auto & extras = m_instanceExtras[instance->id()];
|
auto & extras = m_instanceExtras[instance->id()];
|
||||||
auto & window = extras.window;
|
auto & window = extras.window;
|
||||||
@ -1240,10 +1180,6 @@ bool Application::kill(InstancePtr instance)
|
|||||||
void Application::addRunningInstance()
|
void Application::addRunningInstance()
|
||||||
{
|
{
|
||||||
m_runningInstances ++;
|
m_runningInstances ++;
|
||||||
if(m_runningInstances == 1)
|
|
||||||
{
|
|
||||||
emit updateAllowedChanged(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::subRunningInstance()
|
void Application::subRunningInstance()
|
||||||
@ -1254,10 +1190,6 @@ void Application::subRunningInstance()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_runningInstances --;
|
m_runningInstances --;
|
||||||
if(m_runningInstances == 0)
|
|
||||||
{
|
|
||||||
emit updateAllowedChanged(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Application::shouldExitNow() const
|
bool Application::shouldExitNow() const
|
||||||
@ -1265,17 +1197,6 @@ bool Application::shouldExitNow() const
|
|||||||
return m_runningInstances == 0 && m_openWindows == 0;
|
return m_runningInstances == 0 && m_openWindows == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Application::updatesAreAllowed()
|
|
||||||
{
|
|
||||||
return m_runningInstances == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Application::updateIsRunning(bool running)
|
|
||||||
{
|
|
||||||
m_updateRunning = running;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Application::controllerSucceeded()
|
void Application::controllerSucceeded()
|
||||||
{
|
{
|
||||||
auto controller = qobject_cast<LaunchController *>(QObject::sender());
|
auto controller = qobject_cast<LaunchController *>(QObject::sender());
|
||||||
@ -1361,7 +1282,6 @@ MainWindow* Application::showMainWindow(bool minimized)
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_mainWindow->checkInstancePathForProblems();
|
m_mainWindow->checkInstancePathForProblems();
|
||||||
connect(this, &Application::updateAllowedChanged, m_mainWindow, &MainWindow::updatesAllowedChanged);
|
|
||||||
connect(m_mainWindow, &MainWindow::isClosing, this, &Application::on_windowClose);
|
connect(m_mainWindow, &MainWindow::isClosing, this, &Application::on_windowClose);
|
||||||
m_openWindows++;
|
m_openWindows++;
|
||||||
}
|
}
|
||||||
@ -1512,7 +1432,7 @@ QString Application::getJarsPath()
|
|||||||
return m_jarsPath;
|
return m_jarsPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Application::getMSAClientID()
|
QString Application::getMSAClientID()
|
||||||
{
|
{
|
||||||
QString clientIDOverride = m_settings->get("MSAClientIDOverride").toString();
|
QString clientIDOverride = m_settings->get("MSAClientIDOverride").toString();
|
||||||
if (!clientIDOverride.isEmpty()) {
|
if (!clientIDOverride.isEmpty()) {
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <updater/GoUpdate.h>
|
|
||||||
|
|
||||||
#include <BaseInstance.h>
|
#include <BaseInstance.h>
|
||||||
|
|
||||||
@ -27,7 +26,6 @@ class AccountList;
|
|||||||
class IconList;
|
class IconList;
|
||||||
class QNetworkAccessManager;
|
class QNetworkAccessManager;
|
||||||
class JavaInstallList;
|
class JavaInstallList;
|
||||||
class UpdateChecker;
|
|
||||||
class BaseProfilerFactory;
|
class BaseProfilerFactory;
|
||||||
class BaseDetachedToolFactory;
|
class BaseDetachedToolFactory;
|
||||||
class TranslationsModel;
|
class TranslationsModel;
|
||||||
@ -75,10 +73,6 @@ public:
|
|||||||
|
|
||||||
void setApplicationTheme(const QString& name, bool initial);
|
void setApplicationTheme(const QString& name, bool initial);
|
||||||
|
|
||||||
shared_qobject_ptr<UpdateChecker> updateChecker() {
|
|
||||||
return m_updateChecker;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<TranslationsModel> translations();
|
std::shared_ptr<TranslationsModel> translations();
|
||||||
|
|
||||||
std::shared_ptr<JavaInstallList> javalist();
|
std::shared_ptr<JavaInstallList> javalist();
|
||||||
@ -133,13 +127,9 @@ public:
|
|||||||
InstanceWindow *showInstanceWindow(InstancePtr instance, QString page = QString());
|
InstanceWindow *showInstanceWindow(InstancePtr instance, QString page = QString());
|
||||||
MainWindow *showMainWindow(bool minimized = false);
|
MainWindow *showMainWindow(bool minimized = false);
|
||||||
|
|
||||||
void updateIsRunning(bool running);
|
|
||||||
bool updatesAreAllowed();
|
|
||||||
|
|
||||||
void ShowGlobalSettings(class QWidget * parent, QString open_page = QString());
|
void ShowGlobalSettings(class QWidget * parent, QString open_page = QString());
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void updateAllowedChanged(bool status);
|
|
||||||
void globalSettingsAboutToOpen();
|
void globalSettingsAboutToOpen();
|
||||||
void globalSettingsClosed();
|
void globalSettingsClosed();
|
||||||
|
|
||||||
@ -177,7 +167,6 @@ private:
|
|||||||
|
|
||||||
shared_qobject_ptr<QNetworkAccessManager> m_network;
|
shared_qobject_ptr<QNetworkAccessManager> m_network;
|
||||||
|
|
||||||
shared_qobject_ptr<UpdateChecker> m_updateChecker;
|
|
||||||
shared_qobject_ptr<AccountList> m_accounts;
|
shared_qobject_ptr<AccountList> m_accounts;
|
||||||
|
|
||||||
shared_qobject_ptr<HttpMetaCache> m_metacache;
|
shared_qobject_ptr<HttpMetaCache> m_metacache;
|
||||||
@ -214,7 +203,6 @@ private:
|
|||||||
// main state variables
|
// main state variables
|
||||||
size_t m_openWindows = 0;
|
size_t m_openWindows = 0;
|
||||||
size_t m_runningInstances = 0;
|
size_t m_runningInstances = 0;
|
||||||
bool m_updateRunning = false;
|
|
||||||
|
|
||||||
// main window, if any
|
// main window, if any
|
||||||
MainWindow * m_mainWindow = nullptr;
|
MainWindow * m_mainWindow = nullptr;
|
||||||
|
@ -152,28 +152,6 @@ set(LAUNCH_SOURCES
|
|||||||
launch/LogModel.h
|
launch/LogModel.h
|
||||||
)
|
)
|
||||||
|
|
||||||
# Old update system
|
|
||||||
set(UPDATE_SOURCES
|
|
||||||
updater/GoUpdate.h
|
|
||||||
updater/GoUpdate.cpp
|
|
||||||
updater/UpdateChecker.h
|
|
||||||
updater/UpdateChecker.cpp
|
|
||||||
updater/DownloadTask.h
|
|
||||||
updater/DownloadTask.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
add_unit_test(UpdateChecker
|
|
||||||
SOURCES updater/UpdateChecker_test.cpp
|
|
||||||
LIBS Launcher_logic
|
|
||||||
DATA updater/testdata
|
|
||||||
)
|
|
||||||
|
|
||||||
add_unit_test(DownloadTask
|
|
||||||
SOURCES updater/DownloadTask_test.cpp
|
|
||||||
LIBS Launcher_logic
|
|
||||||
DATA updater/testdata
|
|
||||||
)
|
|
||||||
|
|
||||||
# Rarely used notifications
|
# Rarely used notifications
|
||||||
set(NOTIFICATIONS_SOURCES
|
set(NOTIFICATIONS_SOURCES
|
||||||
# Notifications - short warning messages
|
# Notifications - short warning messages
|
||||||
@ -567,7 +545,6 @@ set(LOGIC_SOURCES
|
|||||||
${PATHMATCHER_SOURCES}
|
${PATHMATCHER_SOURCES}
|
||||||
${NET_SOURCES}
|
${NET_SOURCES}
|
||||||
${LAUNCH_SOURCES}
|
${LAUNCH_SOURCES}
|
||||||
${UPDATE_SOURCES}
|
|
||||||
${NOTIFICATIONS_SOURCES}
|
${NOTIFICATIONS_SOURCES}
|
||||||
${NEWS_SOURCES}
|
${NEWS_SOURCES}
|
||||||
${MINECRAFT_SOURCES}
|
${MINECRAFT_SOURCES}
|
||||||
@ -591,8 +568,6 @@ SET(LAUNCHER_SOURCES
|
|||||||
# Application base
|
# Application base
|
||||||
Application.h
|
Application.h
|
||||||
Application.cpp
|
Application.cpp
|
||||||
UpdateController.cpp
|
|
||||||
UpdateController.h
|
|
||||||
ApplicationMessage.h
|
ApplicationMessage.h
|
||||||
ApplicationMessage.cpp
|
ApplicationMessage.cpp
|
||||||
|
|
||||||
@ -806,8 +781,6 @@ SET(LAUNCHER_SOURCES
|
|||||||
ui/pagedialog/PageDialog.h
|
ui/pagedialog/PageDialog.h
|
||||||
ui/dialogs/ProgressDialog.cpp
|
ui/dialogs/ProgressDialog.cpp
|
||||||
ui/dialogs/ProgressDialog.h
|
ui/dialogs/ProgressDialog.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
|
||||||
@ -904,7 +877,6 @@ qt5_wrap_ui(LAUNCHER_UI
|
|||||||
ui/dialogs/ProgressDialog.ui
|
ui/dialogs/ProgressDialog.ui
|
||||||
ui/dialogs/NewInstanceDialog.ui
|
ui/dialogs/NewInstanceDialog.ui
|
||||||
ui/dialogs/NotificationDialog.ui
|
ui/dialogs/NotificationDialog.ui
|
||||||
ui/dialogs/UpdateDialog.ui
|
|
||||||
ui/dialogs/NewComponentDialog.ui
|
ui/dialogs/NewComponentDialog.ui
|
||||||
ui/dialogs/ProfileSelectDialog.ui
|
ui/dialogs/ProfileSelectDialog.ui
|
||||||
ui/dialogs/SkinUploadDialog.ui
|
ui/dialogs/SkinUploadDialog.ui
|
||||||
@ -959,7 +931,7 @@ target_link_libraries(Launcher_logic
|
|||||||
Launcher_iconfix
|
Launcher_iconfix
|
||||||
QuaZip::QuaZip
|
QuaZip::QuaZip
|
||||||
hoedown
|
hoedown
|
||||||
PolyMC_rainbow
|
SneedMC_rainbow
|
||||||
LocalPeer
|
LocalPeer
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,457 +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));
|
|
||||||
#ifdef Q_OS_WIN32
|
|
||||||
if(QSysInfo::windowsVersion() < QSysInfo::WV_VISTA)
|
|
||||||
{
|
|
||||||
if(destination.fileName() == windowsExeName)
|
|
||||||
{
|
|
||||||
QDir rootDir(m_root);
|
|
||||||
exeOrigin = rootDir.relativeFilePath(op.source);
|
|
||||||
exePath = rootDir.relativeFilePath(op.destination);
|
|
||||||
exeBackup = rootDir.relativeFilePath(FS::PathCombine(backupPath, destination.fileName()));
|
|
||||||
useXPHack = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
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_NAME);
|
|
||||||
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_NAME);
|
|
||||||
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_NAME);
|
|
||||||
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;
|
|
||||||
};
|
|
@ -43,3 +43,4 @@ private:
|
|||||||
bool m_aborted = false;
|
bool m_aborted = false;
|
||||||
Net::Mode m_mode = Net::Mode::Offline;
|
Net::Mode m_mode = Net::Mode::Offline;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ int main(int argc, char *argv[])
|
|||||||
Q_INIT_RESOURCE(multimc);
|
Q_INIT_RESOURCE(multimc);
|
||||||
Q_INIT_RESOURCE(backgrounds);
|
Q_INIT_RESOURCE(backgrounds);
|
||||||
Q_INIT_RESOURCE(documents);
|
Q_INIT_RESOURCE(documents);
|
||||||
Q_INIT_RESOURCE(polymc);
|
Q_INIT_RESOURCE(sneedmc);
|
||||||
|
|
||||||
Q_INIT_RESOURCE(pe_dark);
|
Q_INIT_RESOURCE(pe_dark);
|
||||||
Q_INIT_RESOURCE(pe_light);
|
Q_INIT_RESOURCE(pe_light);
|
||||||
|
@ -16,10 +16,10 @@
|
|||||||
#include "launch/LaunchTask.h"
|
#include "launch/LaunchTask.h"
|
||||||
#include "launch/steps/LookupServerAddress.h"
|
#include "launch/steps/LookupServerAddress.h"
|
||||||
#include "launch/steps/PostLaunchCommand.h"
|
#include "launch/steps/PostLaunchCommand.h"
|
||||||
#include "launch/steps/Update.h"
|
|
||||||
#include "launch/steps/PreLaunchCommand.h"
|
#include "launch/steps/PreLaunchCommand.h"
|
||||||
#include "launch/steps/TextPrint.h"
|
#include "launch/steps/TextPrint.h"
|
||||||
#include "launch/steps/CheckJava.h"
|
#include "launch/steps/CheckJava.h"
|
||||||
|
#include "launch/steps/Update.h"
|
||||||
|
|
||||||
#include "minecraft/launch/LauncherPartLaunch.h"
|
#include "minecraft/launch/LauncherPartLaunch.h"
|
||||||
#include "minecraft/launch/DirectJavaLaunch.h"
|
#include "minecraft/launch/DirectJavaLaunch.h"
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
#include <QHttpMultiPart>
|
||||||
|
|
||||||
PasteUpload::PasteUpload(QWidget *window, QString text, QString url) : m_window(window), m_uploadUrl(url), m_text(text.toUtf8())
|
PasteUpload::PasteUpload(QWidget *window, QString text, QString url) : m_window(window), m_uploadUrl(url), m_text(text.toUtf8())
|
||||||
{
|
{
|
||||||
|
@ -3,6 +3,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"
|
||||||
|
@ -61,8 +61,6 @@
|
|||||||
#include <news/NewsChecker.h>
|
#include <news/NewsChecker.h>
|
||||||
#include <notifications/NotificationChecker.h>
|
#include <notifications/NotificationChecker.h>
|
||||||
#include <tools/BaseProfiler.h>
|
#include <tools/BaseProfiler.h>
|
||||||
#include <updater/DownloadTask.h>
|
|
||||||
#include <updater/UpdateChecker.h>
|
|
||||||
#include <DesktopServices.h>
|
#include <DesktopServices.h>
|
||||||
#include "InstanceWindow.h"
|
#include "InstanceWindow.h"
|
||||||
#include "InstancePageProvider.h"
|
#include "InstancePageProvider.h"
|
||||||
@ -80,12 +78,10 @@
|
|||||||
#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/NotificationDialog.h"
|
#include "ui/dialogs/NotificationDialog.h"
|
||||||
#include "ui/dialogs/ExportInstanceDialog.h"
|
#include "ui/dialogs/ExportInstanceDialog.h"
|
||||||
|
|
||||||
#include "UpdateController.h"
|
|
||||||
#include "KonamiCode.h"
|
#include "KonamiCode.h"
|
||||||
|
|
||||||
#include "InstanceImportTask.h"
|
#include "InstanceImportTask.h"
|
||||||
@ -199,7 +195,6 @@ class MainWindow::Ui
|
|||||||
public:
|
public:
|
||||||
TranslatedAction actionAddInstance;
|
TranslatedAction actionAddInstance;
|
||||||
//TranslatedAction actionRefresh;
|
//TranslatedAction actionRefresh;
|
||||||
TranslatedAction actionCheckUpdate;
|
|
||||||
TranslatedAction actionSettings;
|
TranslatedAction actionSettings;
|
||||||
TranslatedAction actionMoreNews;
|
TranslatedAction actionMoreNews;
|
||||||
TranslatedAction actionManageAccounts;
|
TranslatedAction actionManageAccounts;
|
||||||
@ -386,17 +381,6 @@ public:
|
|||||||
helpButtonAction->setDefaultWidget(helpMenuButton);
|
helpButtonAction->setDefaultWidget(helpMenuButton);
|
||||||
mainToolBar->addAction(helpButtonAction);
|
mainToolBar->addAction(helpButtonAction);
|
||||||
|
|
||||||
if(BuildConfig.UPDATER_ENABLED)
|
|
||||||
{
|
|
||||||
actionCheckUpdate = TranslatedAction(MainWindow);
|
|
||||||
actionCheckUpdate->setObjectName(QStringLiteral("actionCheckUpdate"));
|
|
||||||
actionCheckUpdate->setIcon(APPLICATION->getThemedIcon("checkupdate"));
|
|
||||||
actionCheckUpdate.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Update"));
|
|
||||||
actionCheckUpdate.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Check for new updates for %1."));
|
|
||||||
all_actions.append(&actionCheckUpdate);
|
|
||||||
mainToolBar->addAction(actionCheckUpdate);
|
|
||||||
}
|
|
||||||
|
|
||||||
mainToolBar->addSeparator();
|
mainToolBar->addSeparator();
|
||||||
|
|
||||||
actionCAT = TranslatedAction(MainWindow);
|
actionCAT = TranslatedAction(MainWindow);
|
||||||
@ -816,25 +800,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if(BuildConfig.UPDATER_ENABLED)
|
|
||||||
{
|
|
||||||
bool updatesAllowed = APPLICATION->updatesAreAllowed();
|
|
||||||
updatesAllowedChanged(updatesAllowed);
|
|
||||||
|
|
||||||
// NOTE: calling the operator like that is an ugly hack to appease ancient gcc...
|
|
||||||
connect(ui->actionCheckUpdate.operator->(), &QAction::triggered, this, &MainWindow::checkForUpdates);
|
|
||||||
|
|
||||||
// set up the updater object.
|
|
||||||
auto updater = APPLICATION->updateChecker();
|
|
||||||
connect(updater.get(), &UpdateChecker::updateAvailable, this, &MainWindow::updateAvailable);
|
|
||||||
connect(updater.get(), &UpdateChecker::noUpdateFound, this, &MainWindow::updateNotAvailable);
|
|
||||||
// if automatic update checks are allowed, start one.
|
|
||||||
if (APPLICATION->settings()->get("AutoUpdate").toBool() && updatesAllowed)
|
|
||||||
{
|
|
||||||
updater->checkForUpdate(APPLICATION->settings()->get("UpdateChannel").toString(), false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
auto checker = new NotificationChecker();
|
auto checker = new NotificationChecker();
|
||||||
checker->setNotificationsUrl(QUrl(BuildConfig.NOTIFICATION_URL));
|
checker->setNotificationsUrl(QUrl(BuildConfig.NOTIFICATION_URL));
|
||||||
@ -1099,15 +1064,6 @@ void MainWindow::repopulateAccountsMenu()
|
|||||||
accountMenu->addAction(ui->actionManageAccounts);
|
accountMenu->addAction(ui->actionManageAccounts);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::updatesAllowedChanged(bool allowed)
|
|
||||||
{
|
|
||||||
if(!BuildConfig.UPDATER_ENABLED)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ui->actionCheckUpdate->setEnabled(allowed);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Assumes the sender is a QAction
|
* Assumes the sender is a QAction
|
||||||
*/
|
*/
|
||||||
@ -1212,32 +1168,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)
|
||||||
{
|
{
|
||||||
QStringList split = string.split(',', QString::SkipEmptyParts);
|
QStringList split = string.split(',', QString::SkipEmptyParts);
|
||||||
@ -1276,40 +1206,6 @@ void MainWindow::notificationsChanged()
|
|||||||
APPLICATION->settings()->set("ShownNotifications", intListToString(shownNotifications));
|
APPLICATION->settings()->set("ShownNotifications", intListToString(shownNotifications));
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
@ -1616,19 +1512,6 @@ void MainWindow::on_actionConfig_Folder_triggered()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::checkForUpdates()
|
|
||||||
{
|
|
||||||
if(BuildConfig.UPDATER_ENABLED)
|
|
||||||
{
|
|
||||||
auto updater = APPLICATION->updateChecker();
|
|
||||||
updater->checkForUpdate(APPLICATION->settings()->get("UpdateChannel").toString(), true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
qWarning() << "Updater not set up. Cannot check for updates.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::on_actionSettings_triggered()
|
void MainWindow::on_actionSettings_triggered()
|
||||||
{
|
{
|
||||||
APPLICATION->ShowGlobalSettings(this, "global-settings");
|
APPLICATION->ShowGlobalSettings(this, "global-settings");
|
||||||
|
@ -24,7 +24,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;
|
||||||
@ -55,8 +54,6 @@ public:
|
|||||||
|
|
||||||
void checkInstancePathForProblems();
|
void checkInstancePathForProblems();
|
||||||
|
|
||||||
void updatesAllowedChanged(bool allowed);
|
|
||||||
|
|
||||||
void droppedURLs(QList<QUrl> urls);
|
void droppedURLs(QList<QUrl> urls);
|
||||||
signals:
|
signals:
|
||||||
void isClosing();
|
void isClosing();
|
||||||
@ -99,8 +96,6 @@ private slots:
|
|||||||
|
|
||||||
void on_actionViewCentralModsFolder_triggered();
|
void on_actionViewCentralModsFolder_triggered();
|
||||||
|
|
||||||
void checkForUpdates();
|
|
||||||
|
|
||||||
void on_actionSettings_triggered();
|
void on_actionSettings_triggered();
|
||||||
|
|
||||||
void on_actionInstanceSettings_triggered();
|
void on_actionInstanceSettings_triggered();
|
||||||
@ -162,10 +157,6 @@ private slots:
|
|||||||
|
|
||||||
void startTask(Task *task);
|
void startTask(Task *task);
|
||||||
|
|
||||||
void updateAvailable(GoUpdate::Status status);
|
|
||||||
|
|
||||||
void updateNotAvailable();
|
|
||||||
|
|
||||||
void notificationsChanged();
|
void notificationsChanged();
|
||||||
|
|
||||||
void defaultAccountChanged();
|
void defaultAccountChanged();
|
||||||
@ -176,11 +167,6 @@ private slots:
|
|||||||
|
|
||||||
void updateNewsLabel();
|
void updateNewsLabel();
|
||||||
|
|
||||||
/*!
|
|
||||||
* Runs the DownloadTask and installs updates.
|
|
||||||
*/
|
|
||||||
void downloadUpdates(GoUpdate::Status status);
|
|
||||||
|
|
||||||
void konamiTriggered();
|
void konamiTriggered();
|
||||||
|
|
||||||
void globalSettingsClosed();
|
void globalSettingsClosed();
|
||||||
|
@ -1,182 +0,0 @@
|
|||||||
#include "UpdateDialog.h"
|
|
||||||
#include "ui_UpdateDialog.h"
|
|
||||||
#include <QDebug>
|
|
||||||
#include "Application.h"
|
|
||||||
#include <settings/SettingsObject.h>
|
|
||||||
#include <Json.h>
|
|
||||||
|
|
||||||
#include "BuildConfig.h"
|
|
||||||
#include "HoeDown.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/PolyMC/PolyMC/%1/changelog.md").arg(channel);
|
|
||||||
m_changelogType = CHANGELOG_MARKDOWN;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
url = QString("https://api.github.com/repos/PolyMC/PolyMC/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)
|
|
||||||
{
|
|
||||||
HoeDown hoedown;
|
|
||||||
QString output = hoedown.process(markdown);
|
|
||||||
|
|
||||||
// HACK: easier than customizing hoedown
|
|
||||||
output.replace(QRegExp("GH-([0-9]+)"), "<a href=\"https://github.com/PolyMC/PolyMC/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/PolyMC/PolyMC/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>PolyMC 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>
|
|
@ -21,8 +21,6 @@
|
|||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QTextCharFormat>
|
#include <QTextCharFormat>
|
||||||
|
|
||||||
#include "updater/UpdateChecker.h"
|
|
||||||
|
|
||||||
#include "settings/SettingsObject.h"
|
#include "settings/SettingsObject.h"
|
||||||
#include <FileSystem.h>
|
#include <FileSystem.h>
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
@ -56,23 +54,6 @@ LauncherPage::LauncherPage(QWidget *parent) : QWidget(parent), ui(new Ui::Launch
|
|||||||
m_languageModel = APPLICATION->translations();
|
m_languageModel = APPLICATION->translations();
|
||||||
loadSettings();
|
loadSettings();
|
||||||
|
|
||||||
if(BuildConfig.UPDATER_ENABLED)
|
|
||||||
{
|
|
||||||
QObject::connect(APPLICATION->updateChecker().get(), &UpdateChecker::channelListLoaded, this, &LauncherPage::refreshUpdateChannelList);
|
|
||||||
|
|
||||||
if (APPLICATION->updateChecker()->hasChannels())
|
|
||||||
{
|
|
||||||
refreshUpdateChannelList();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
APPLICATION->updateChecker()->updateChanList(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()));
|
||||||
|
|
||||||
@ -158,78 +139,6 @@ void LauncherPage::on_migrateDataFolderMacBtn_clicked()
|
|||||||
qApp->quit();
|
qApp->quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
@ -239,9 +148,6 @@ void LauncherPage::applySettings()
|
|||||||
s->set("ShownNotifications", QString());
|
s->set("ShownNotifications", QString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates
|
|
||||||
s->set("AutoUpdate", ui->autoUpdateCheckBox->isChecked());
|
|
||||||
s->set("UpdateChannel", m_currentUpdateChannel);
|
|
||||||
auto original = s->get("IconTheme").toString();
|
auto original = s->get("IconTheme").toString();
|
||||||
//FIXME: make generic
|
//FIXME: make generic
|
||||||
switch (ui->themeComboBox->currentIndex())
|
switch (ui->themeComboBox->currentIndex())
|
||||||
@ -319,9 +225,6 @@ void LauncherPage::applySettings()
|
|||||||
void LauncherPage::loadSettings()
|
void LauncherPage::loadSettings()
|
||||||
{
|
{
|
||||||
auto s = APPLICATION->settings();
|
auto s = APPLICATION->settings();
|
||||||
// Updates
|
|
||||||
ui->autoUpdateCheckBox->setChecked(s->get("AutoUpdate").toBool());
|
|
||||||
m_currentUpdateChannel = s->get("UpdateChannel").toString();
|
|
||||||
//FIXME: make generic
|
//FIXME: make generic
|
||||||
auto theme = s->get("IconTheme").toString();
|
auto theme = s->get("IconTheme").toString();
|
||||||
if (theme == "pe_dark")
|
if (theme == "pe_dark")
|
||||||
|
@ -69,31 +69,14 @@ slots:
|
|||||||
void on_iconsDirBrowseBtn_clicked();
|
void on_iconsDirBrowseBtn_clicked();
|
||||||
void on_migrateDataFolderMacBtn_clicked();
|
void on_migrateDataFolderMacBtn_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;
|
||||||
|
|
||||||
/*!
|
|
||||||
* Stores the currently selected update channel.
|
|
||||||
*/
|
|
||||||
QString m_currentUpdateChannel;
|
|
||||||
|
|
||||||
// default format for the font preview...
|
// default format for the font preview...
|
||||||
QTextCharFormat *defaultFormat;
|
QTextCharFormat *defaultFormat;
|
||||||
|
|
||||||
@ -101,3 +84,4 @@ private:
|
|||||||
|
|
||||||
std::shared_ptr<TranslationsModel> m_languageModel;
|
std::shared_ptr<TranslationsModel> m_languageModel;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -45,49 +45,6 @@
|
|||||||
<string>Features</string>
|
<string>Features</string>
|
||||||
</attribute>
|
</attribute>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_9">
|
<layout class="QVBoxLayout" name="verticalLayout_9">
|
||||||
<item>
|
|
||||||
<widget class="QGroupBox" name="updateSettingsBox">
|
|
||||||
<property name="title">
|
|
||||||
<string>Update Settings</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
|
||||||
<item>
|
|
||||||
<widget class="QCheckBox" name="autoUpdateCheckBox">
|
|
||||||
<property name="text">
|
|
||||||
<string>Check for updates on start?</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</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>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
<item>
|
||||||
<widget class="QGroupBox" name="foldersBox">
|
<widget class="QGroupBox" name="foldersBox">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
@ -491,8 +448,6 @@
|
|||||||
</widget>
|
</widget>
|
||||||
<tabstops>
|
<tabstops>
|
||||||
<tabstop>tabWidget</tabstop>
|
<tabstop>tabWidget</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>
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QScrollBar>
|
#include <QScrollBar>
|
||||||
#include <QShortcut>
|
#include <QShortcut>
|
||||||
|
#include <QIdentityProxyModel>
|
||||||
|
|
||||||
#include "launch/LaunchTask.h"
|
#include "launch/LaunchTask.h"
|
||||||
#include "settings/Setting.h"
|
#include "settings/Setting.h"
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
|
#include <QSortFilterProxyModel>
|
||||||
|
|
||||||
#include "minecraft/MinecraftInstance.h"
|
#include "minecraft/MinecraftInstance.h"
|
||||||
#include "ui/pages/BasePage.h"
|
#include "ui/pages/BasePage.h"
|
||||||
@ -119,3 +120,4 @@ public:
|
|||||||
}
|
}
|
||||||
virtual bool shouldDisplay() const;
|
virtual bool shouldDisplay() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -12,6 +12,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.
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
#include <QTreeView>
|
#include <QTreeView>
|
||||||
#include <QInputDialog>
|
#include <QInputDialog>
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
|
#include <QSortFilterProxyModel>
|
||||||
|
|
||||||
#include "tools/MCEditTool.h"
|
#include "tools/MCEditTool.h"
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "ListModel.h"
|
#include "ListModel.h"
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
#include "net/NetJob.h"
|
||||||
|
|
||||||
#include <MMCStrings.h>
|
#include <MMCStrings.h>
|
||||||
#include <Version.h>
|
#include <Version.h>
|
||||||
|
@ -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, PolyMC 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,196 +0,0 @@
|
|||||||
#include <QTest>
|
|
||||||
#include <QSignalSpy>
|
|
||||||
|
|
||||||
#include "TestUtil.h"
|
|
||||||
|
|
||||||
#include "updater/GoUpdate.h"
|
|
||||||
#include "updater/DownloadTask.h"
|
|
||||||
#include "updater/UpdateChecker.h"
|
|
||||||
#include <FileSystem.h>
|
|
||||||
|
|
||||||
using namespace GoUpdate;
|
|
||||||
|
|
||||||
FileSourceList encodeBaseFile(const char *suffix)
|
|
||||||
{
|
|
||||||
auto base = QDir::currentPath();
|
|
||||||
QUrl localFile = QUrl::fromLocalFile(base + suffix);
|
|
||||||
QString localUrlString = localFile.toString(QUrl::FullyEncoded);
|
|
||||||
auto item = FileSource("http", localUrlString);
|
|
||||||
return FileSourceList({item});
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(VersionFileList)
|
|
||||||
Q_DECLARE_METATYPE(Operation)
|
|
||||||
|
|
||||||
QDebug operator<<(QDebug dbg, const FileSource &f)
|
|
||||||
{
|
|
||||||
dbg.nospace() << "FileSource(type=" << f.type << " url=" << f.url
|
|
||||||
<< " comp=" << f.compressionType << ")";
|
|
||||||
return dbg.maybeSpace();
|
|
||||||
}
|
|
||||||
|
|
||||||
QDebug operator<<(QDebug dbg, const VersionFileEntry &v)
|
|
||||||
{
|
|
||||||
dbg.nospace() << "VersionFileEntry(path=" << v.path << " mode=" << v.mode
|
|
||||||
<< " md5=" << v.md5 << " sources=" << v.sources << ")";
|
|
||||||
return dbg.maybeSpace();
|
|
||||||
}
|
|
||||||
|
|
||||||
QDebug operator<<(QDebug dbg, const Operation::Type &t)
|
|
||||||
{
|
|
||||||
switch (t)
|
|
||||||
{
|
|
||||||
case Operation::OP_REPLACE:
|
|
||||||
dbg << "OP_COPY";
|
|
||||||
break;
|
|
||||||
case Operation::OP_DELETE:
|
|
||||||
dbg << "OP_DELETE";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return dbg.maybeSpace();
|
|
||||||
}
|
|
||||||
|
|
||||||
QDebug operator<<(QDebug dbg, const Operation &u)
|
|
||||||
{
|
|
||||||
dbg.nospace() << "Operation(type=" << u.type << " file=" << u.source
|
|
||||||
<< " dest=" << u.destination << " mode=" << u.destinationMode << ")";
|
|
||||||
return dbg.maybeSpace();
|
|
||||||
}
|
|
||||||
|
|
||||||
class DownloadTaskTest : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
private
|
|
||||||
slots:
|
|
||||||
void initTestCase()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
void cleanupTestCase()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void test_parseVersionInfo_data()
|
|
||||||
{
|
|
||||||
QTest::addColumn<QByteArray>("data");
|
|
||||||
QTest::addColumn<VersionFileList>("list");
|
|
||||||
QTest::addColumn<QString>("error");
|
|
||||||
QTest::addColumn<bool>("ret");
|
|
||||||
|
|
||||||
QTest::newRow("one")
|
|
||||||
<< GET_TEST_FILE("data/1.json")
|
|
||||||
<< (VersionFileList()
|
|
||||||
<< VersionFileEntry{"fileOne",
|
|
||||||
493,
|
|
||||||
encodeBaseFile("/data/fileOneA"),
|
|
||||||
"9eb84090956c484e32cb6c08455a667b"}
|
|
||||||
<< VersionFileEntry{"fileTwo",
|
|
||||||
644,
|
|
||||||
encodeBaseFile("/data/fileTwo"),
|
|
||||||
"38f94f54fa3eb72b0ea836538c10b043"}
|
|
||||||
<< VersionFileEntry{"fileThree",
|
|
||||||
750,
|
|
||||||
encodeBaseFile("/data/fileThree"),
|
|
||||||
"f12df554b21e320be6471d7154130e70"})
|
|
||||||
<< QString() << true;
|
|
||||||
QTest::newRow("two")
|
|
||||||
<< GET_TEST_FILE("data/2.json")
|
|
||||||
<< (VersionFileList()
|
|
||||||
<< VersionFileEntry{"fileOne",
|
|
||||||
493,
|
|
||||||
encodeBaseFile("/data/fileOneB"),
|
|
||||||
"42915a71277c9016668cce7b82c6b577"}
|
|
||||||
<< VersionFileEntry{"fileTwo",
|
|
||||||
644,
|
|
||||||
encodeBaseFile("/data/fileTwo"),
|
|
||||||
"38f94f54fa3eb72b0ea836538c10b043"})
|
|
||||||
<< QString() << true;
|
|
||||||
}
|
|
||||||
void test_parseVersionInfo()
|
|
||||||
{
|
|
||||||
QFETCH(QByteArray, data);
|
|
||||||
QFETCH(VersionFileList, list);
|
|
||||||
QFETCH(QString, error);
|
|
||||||
QFETCH(bool, ret);
|
|
||||||
|
|
||||||
VersionFileList outList;
|
|
||||||
QString outError;
|
|
||||||
bool outRet = parseVersionInfo(data, outList, outError);
|
|
||||||
QCOMPARE(outRet, ret);
|
|
||||||
QCOMPARE(outList, list);
|
|
||||||
QCOMPARE(outError, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
void test_processFileLists_data()
|
|
||||||
{
|
|
||||||
QTest::addColumn<QString>("tempFolder");
|
|
||||||
QTest::addColumn<VersionFileList>("currentVersion");
|
|
||||||
QTest::addColumn<VersionFileList>("newVersion");
|
|
||||||
QTest::addColumn<OperationList>("expectedOperations");
|
|
||||||
|
|
||||||
QTemporaryDir tempFolderObj;
|
|
||||||
QString tempFolder = tempFolderObj.path();
|
|
||||||
// update fileOne, keep fileTwo, remove fileThree
|
|
||||||
QTest::newRow("test 1")
|
|
||||||
<< tempFolder << (VersionFileList()
|
|
||||||
<< VersionFileEntry{
|
|
||||||
"data/fileOne", 493,
|
|
||||||
FileSourceList()
|
|
||||||
<< FileSource(
|
|
||||||
"http", "http://host/path/fileOne-1"),
|
|
||||||
"9eb84090956c484e32cb6c08455a667b"}
|
|
||||||
<< VersionFileEntry{
|
|
||||||
"data/fileTwo", 644,
|
|
||||||
FileSourceList()
|
|
||||||
<< FileSource(
|
|
||||||
"http", "http://host/path/fileTwo-1"),
|
|
||||||
"38f94f54fa3eb72b0ea836538c10b043"}
|
|
||||||
<< VersionFileEntry{
|
|
||||||
"data/fileThree", 420,
|
|
||||||
FileSourceList()
|
|
||||||
<< FileSource(
|
|
||||||
"http", "http://host/path/fileThree-1"),
|
|
||||||
"f12df554b21e320be6471d7154130e70"})
|
|
||||||
<< (VersionFileList()
|
|
||||||
<< VersionFileEntry{
|
|
||||||
"data/fileOne", 493,
|
|
||||||
FileSourceList()
|
|
||||||
<< FileSource("http",
|
|
||||||
"http://host/path/fileOne-2"),
|
|
||||||
"42915a71277c9016668cce7b82c6b577"}
|
|
||||||
<< VersionFileEntry{
|
|
||||||
"data/fileTwo", 644,
|
|
||||||
FileSourceList()
|
|
||||||
<< FileSource("http",
|
|
||||||
"http://host/path/fileTwo-2"),
|
|
||||||
"38f94f54fa3eb72b0ea836538c10b043"})
|
|
||||||
<< (OperationList()
|
|
||||||
<< Operation::DeleteOp("data/fileThree")
|
|
||||||
<< Operation::CopyOp(
|
|
||||||
FS::PathCombine(tempFolder,
|
|
||||||
QString("data/fileOne").replace("/", "_")),
|
|
||||||
"data/fileOne", 493));
|
|
||||||
}
|
|
||||||
void test_processFileLists()
|
|
||||||
{
|
|
||||||
QFETCH(QString, tempFolder);
|
|
||||||
QFETCH(VersionFileList, currentVersion);
|
|
||||||
QFETCH(VersionFileList, newVersion);
|
|
||||||
QFETCH(OperationList, expectedOperations);
|
|
||||||
|
|
||||||
OperationList operations;
|
|
||||||
|
|
||||||
shared_qobject_ptr<QNetworkAccessManager> network = new QNetworkAccessManager();
|
|
||||||
processFileLists(currentVersion, newVersion, QDir::currentPath(), tempFolder, new NetJob("Dummy", network), operations);
|
|
||||||
qDebug() << (operations == expectedOperations);
|
|
||||||
qDebug() << operations;
|
|
||||||
qDebug() << expectedOperations;
|
|
||||||
QCOMPARE(operations, expectedOperations);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
extern "C"
|
|
||||||
{
|
|
||||||
QTEST_GUILESS_MAIN(DownloadTaskTest)
|
|
||||||
}
|
|
||||||
|
|
||||||
#include "DownloadTask_test.moc"
|
|
@ -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 PolyMC'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)
|
|
@ -1,270 +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"
|
|
||||||
#include "sys.h"
|
|
||||||
|
|
||||||
UpdateChecker::UpdateChecker(shared_qobject_ptr<QNetworkAccessManager> nam, QString channelUrl, QString currentChannel, int currentBuild)
|
|
||||||
{
|
|
||||||
m_network = nam;
|
|
||||||
m_channelUrl = channelUrl;
|
|
||||||
m_currentChannel = currentChannel;
|
|
||||||
m_currentBuild = currentBuild;
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<UpdateChecker::ChannelListEntry> UpdateChecker::getChannelList() const
|
|
||||||
{
|
|
||||||
return m_channels;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool UpdateChecker::hasChannels() const
|
|
||||||
{
|
|
||||||
return !m_channels.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
void UpdateChecker::checkForUpdate(QString updateChannel, bool notifyNoUpdate)
|
|
||||||
{
|
|
||||||
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,121 +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"
|
|
||||||
|
|
||||||
class UpdateChecker : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
UpdateChecker(shared_qobject_ptr<QNetworkAccessManager> nam, QString channelUrl, QString currentChannel, int currentBuild);
|
|
||||||
void checkForUpdate(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;
|
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
@ -1,149 +0,0 @@
|
|||||||
#include <QTest>
|
|
||||||
#include <QSignalSpy>
|
|
||||||
|
|
||||||
#include "TestUtil.h"
|
|
||||||
#include "updater/UpdateChecker.h"
|
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(UpdateChecker::ChannelListEntry)
|
|
||||||
|
|
||||||
bool operator==(const UpdateChecker::ChannelListEntry &e1, const UpdateChecker::ChannelListEntry &e2)
|
|
||||||
{
|
|
||||||
qDebug() << e1.url << "vs" << e2.url;
|
|
||||||
return e1.id == e2.id &&
|
|
||||||
e1.name == e2.name &&
|
|
||||||
e1.description == e2.description &&
|
|
||||||
e1.url == e2.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
QDebug operator<<(QDebug dbg, const UpdateChecker::ChannelListEntry &c)
|
|
||||||
{
|
|
||||||
dbg.nospace() << "ChannelListEntry(id=" << c.id << " name=" << c.name << " description=" << c.description << " url=" << c.url << ")";
|
|
||||||
return dbg.maybeSpace();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString findTestDataUrl(const char *file)
|
|
||||||
{
|
|
||||||
return QUrl::fromLocalFile(QFINDTESTDATA(file)).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
class UpdateCheckerTest : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
private
|
|
||||||
slots:
|
|
||||||
void initTestCase()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
void cleanupTestCase()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void tst_ChannelListParsing_data()
|
|
||||||
{
|
|
||||||
QTest::addColumn<QString>("channel");
|
|
||||||
QTest::addColumn<QString>("channelUrl");
|
|
||||||
QTest::addColumn<bool>("hasChannels");
|
|
||||||
QTest::addColumn<bool>("valid");
|
|
||||||
QTest::addColumn<QList<UpdateChecker::ChannelListEntry> >("result");
|
|
||||||
|
|
||||||
QTest::newRow("garbage")
|
|
||||||
<< QString()
|
|
||||||
<< findTestDataUrl("data/garbageChannels.json")
|
|
||||||
<< false
|
|
||||||
<< false
|
|
||||||
<< QList<UpdateChecker::ChannelListEntry>();
|
|
||||||
QTest::newRow("errors")
|
|
||||||
<< QString()
|
|
||||||
<< findTestDataUrl("data/errorChannels.json")
|
|
||||||
<< false
|
|
||||||
<< true
|
|
||||||
<< QList<UpdateChecker::ChannelListEntry>();
|
|
||||||
QTest::newRow("no channels")
|
|
||||||
<< QString()
|
|
||||||
<< findTestDataUrl("data/noChannels.json")
|
|
||||||
<< false
|
|
||||||
<< true
|
|
||||||
<< QList<UpdateChecker::ChannelListEntry>();
|
|
||||||
QTest::newRow("one channel")
|
|
||||||
<< QString("develop")
|
|
||||||
<< findTestDataUrl("data/oneChannel.json")
|
|
||||||
<< true
|
|
||||||
<< true
|
|
||||||
<< (QList<UpdateChecker::ChannelListEntry>() << UpdateChecker::ChannelListEntry{"develop", "Develop", "The channel called \"develop\"", "http://example.org/stuff"});
|
|
||||||
QTest::newRow("several channels")
|
|
||||||
<< QString("develop")
|
|
||||||
<< findTestDataUrl("data/channels.json")
|
|
||||||
<< true
|
|
||||||
<< true
|
|
||||||
<< (QList<UpdateChecker::ChannelListEntry>()
|
|
||||||
<< UpdateChecker::ChannelListEntry{"develop", "Develop", "The channel called \"develop\"", findTestDataUrl("data")}
|
|
||||||
<< UpdateChecker::ChannelListEntry{"stable", "Stable", "It's stable at least", findTestDataUrl("data")}
|
|
||||||
<< UpdateChecker::ChannelListEntry{"42", "The Channel", "This is the channel that is going to answer all of your questions", "https://dent.me/tea"});
|
|
||||||
}
|
|
||||||
void tst_ChannelListParsing()
|
|
||||||
{
|
|
||||||
|
|
||||||
QFETCH(QString, channel);
|
|
||||||
QFETCH(QString, channelUrl);
|
|
||||||
QFETCH(bool, hasChannels);
|
|
||||||
QFETCH(bool, valid);
|
|
||||||
QFETCH(QList<UpdateChecker::ChannelListEntry>, result);
|
|
||||||
|
|
||||||
shared_qobject_ptr<QNetworkAccessManager> nam = new QNetworkAccessManager();
|
|
||||||
UpdateChecker checker(nam, channelUrl, channel, 0);
|
|
||||||
|
|
||||||
QSignalSpy channelListLoadedSpy(&checker, SIGNAL(channelListLoaded()));
|
|
||||||
QVERIFY(channelListLoadedSpy.isValid());
|
|
||||||
|
|
||||||
checker.updateChanList(false);
|
|
||||||
|
|
||||||
if (valid)
|
|
||||||
{
|
|
||||||
QVERIFY(channelListLoadedSpy.wait());
|
|
||||||
QCOMPARE(channelListLoadedSpy.size(), 1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
channelListLoadedSpy.wait();
|
|
||||||
QCOMPARE(channelListLoadedSpy.size(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
QCOMPARE(checker.hasChannels(), hasChannels);
|
|
||||||
QCOMPARE(checker.getChannelList(), result);
|
|
||||||
}
|
|
||||||
|
|
||||||
void tst_UpdateChecking()
|
|
||||||
{
|
|
||||||
QString channel = "develop";
|
|
||||||
QString channelUrl = findTestDataUrl("data/channels.json");
|
|
||||||
int currentBuild = 2;
|
|
||||||
|
|
||||||
shared_qobject_ptr<QNetworkAccessManager> nam = new QNetworkAccessManager();
|
|
||||||
UpdateChecker checker(nam, channelUrl, channel, currentBuild);
|
|
||||||
|
|
||||||
QSignalSpy updateAvailableSpy(&checker, SIGNAL(updateAvailable(GoUpdate::Status)));
|
|
||||||
QVERIFY(updateAvailableSpy.isValid());
|
|
||||||
QSignalSpy channelListLoadedSpy(&checker, SIGNAL(channelListLoaded()));
|
|
||||||
QVERIFY(channelListLoadedSpy.isValid());
|
|
||||||
|
|
||||||
checker.updateChanList(false);
|
|
||||||
QVERIFY(channelListLoadedSpy.wait());
|
|
||||||
|
|
||||||
qDebug() << "CWD:" << QDir::current().absolutePath();
|
|
||||||
checker.m_channels[0].url = findTestDataUrl("data/");
|
|
||||||
checker.checkForUpdate(channel, false);
|
|
||||||
|
|
||||||
QVERIFY(updateAvailableSpy.wait());
|
|
||||||
|
|
||||||
auto status = updateAvailableSpy.first().first().value<GoUpdate::Status>();
|
|
||||||
QCOMPARE(checker.m_channels[0].url, status.newRepoUrl);
|
|
||||||
QCOMPARE(3, status.newVersionId);
|
|
||||||
QCOMPARE(currentBuild, status.currentVersionId);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
QTEST_GUILESS_MAIN(UpdateCheckerTest)
|
|
||||||
|
|
||||||
#include "UpdateChecker_test.moc"
|
|
BIN
launcher/updater/testdata/1.json
vendored
BIN
launcher/updater/testdata/1.json
vendored
Binary file not shown.
BIN
launcher/updater/testdata/2.json
vendored
BIN
launcher/updater/testdata/2.json
vendored
Binary file not shown.
BIN
launcher/updater/testdata/channels.json
vendored
BIN
launcher/updater/testdata/channels.json
vendored
Binary file not shown.
BIN
launcher/updater/testdata/errorChannels.json
vendored
BIN
launcher/updater/testdata/errorChannels.json
vendored
Binary file not shown.
BIN
launcher/updater/testdata/fileOneA
vendored
BIN
launcher/updater/testdata/fileOneA
vendored
Binary file not shown.
BIN
launcher/updater/testdata/fileOneB
vendored
BIN
launcher/updater/testdata/fileOneB
vendored
Binary file not shown.
BIN
launcher/updater/testdata/fileThree
vendored
BIN
launcher/updater/testdata/fileThree
vendored
Binary file not shown.
BIN
launcher/updater/testdata/fileTwo
vendored
BIN
launcher/updater/testdata/fileTwo
vendored
Binary file not shown.
BIN
launcher/updater/testdata/garbageChannels.json
vendored
BIN
launcher/updater/testdata/garbageChannels.json
vendored
Binary file not shown.
BIN
launcher/updater/testdata/index.json
vendored
BIN
launcher/updater/testdata/index.json
vendored
Binary file not shown.
BIN
launcher/updater/testdata/noChannels.json
vendored
BIN
launcher/updater/testdata/noChannels.json
vendored
Binary file not shown.
BIN
launcher/updater/testdata/oneChannel.json
vendored
BIN
launcher/updater/testdata/oneChannel.json
vendored
Binary file not shown.
Binary file not shown.
@ -9,7 +9,7 @@ src/rainbow.cpp
|
|||||||
)
|
)
|
||||||
|
|
||||||
add_definitions(-DRAINBOW_LIBRARY)
|
add_definitions(-DRAINBOW_LIBRARY)
|
||||||
add_library(SneedMC SHARED ${RAINBOW_SOURCES})
|
add_library(SneedMC_rainbow SHARED ${RAINBOW_SOURCES})
|
||||||
target_include_directories(SneedMC_rainbow PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
target_include_directories(SneedMC_rainbow PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||||
|
|
||||||
target_link_libraries(SneedMC_rainbow Qt5::Core Qt5::Gui)
|
target_link_libraries(SneedMC_rainbow Qt5::Core Qt5::Gui)
|
||||||
|
@ -4,7 +4,7 @@ set(Launcher_Copyright "Sneederix 2022" PARENT_SCOPE)
|
|||||||
set(Launcher_Domain "sneedmc.org" PARENT_SCOPE)
|
set(Launcher_Domain "sneedmc.org" PARENT_SCOPE)
|
||||||
set(Launcher_Name "${Launcher_CommonName}" PARENT_SCOPE)
|
set(Launcher_Name "${Launcher_CommonName}" PARENT_SCOPE)
|
||||||
set(Launcher_DisplayName "${Launcher_CommonName}" PARENT_SCOPE)
|
set(Launcher_DisplayName "${Launcher_CommonName}" PARENT_SCOPE)
|
||||||
set(Launcher_UserAgent "${Launcher_CommonName}/${Launcher_RELEASE_VERSION_NAME}" PARENT_SCOPE)
|
set(Launcher_UserAgent "MultiMC/0.6.14" PARENT_SCOPE)
|
||||||
set(Launcher_ConfigFile "sneedmc.cfg" PARENT_SCOPE)
|
set(Launcher_ConfigFile "sneedmc.cfg" PARENT_SCOPE)
|
||||||
set(Launcher_Git "https://github.com/Sneederix/SneedMC" PARENT_SCOPE)
|
set(Launcher_Git "https://github.com/Sneederix/SneedMC" PARENT_SCOPE)
|
||||||
set(Launcher_DesktopFileName "org.sneederix.SneedMC.desktop" PARENT_SCOPE)
|
set(Launcher_DesktopFileName "org.sneederix.SneedMC.desktop" PARENT_SCOPE)
|
||||||
|
Loading…
Reference in New Issue
Block a user