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 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 version numbers ########
|
||||
@ -60,9 +60,6 @@ set(Launcher_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number
|
||||
# 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.")
|
||||
|
||||
# Channel list URL
|
||||
set(Launcher_UPDATER_BASE "" CACHE STRING "Base URL for the updater.")
|
||||
|
||||
# Notification URL
|
||||
set(Launcher_NOTIFICATION_URL "" CACHE STRING "URL for checking for notifications.")
|
||||
|
||||
|
@ -24,7 +24,6 @@ Config::Config()
|
||||
VERSION_BUILD = @Launcher_VERSION_BUILD@;
|
||||
|
||||
BUILD_PLATFORM = "@Launcher_BUILD_PLATFORM@";
|
||||
UPDATER_BASE = "@Launcher_UPDATER_BASE@";
|
||||
NOTIFICATION_URL = "@Launcher_NOTIFICATION_URL@";
|
||||
FULL_VERSION_STR = "@Launcher_VERSION_MAJOR@.@Launcher_VERSION_MINOR@.@Launcher_VERSION_BUILD@";
|
||||
|
||||
@ -34,9 +33,6 @@ Config::Config()
|
||||
{
|
||||
VERSION_CHANNEL = GIT_REFSPEC;
|
||||
VERSION_CHANNEL.remove("refs/heads/");
|
||||
if(!UPDATER_BASE.isEmpty() && !BUILD_PLATFORM.isEmpty() && VERSION_BUILD >= 0) {
|
||||
UPDATER_ENABLED = true;
|
||||
}
|
||||
}
|
||||
else if (!GIT_COMMIT.isEmpty())
|
||||
{
|
||||
|
@ -25,27 +25,16 @@ public:
|
||||
/// The build number.
|
||||
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".
|
||||
QString BUILD_PLATFORM;
|
||||
|
||||
/// URL for the updater's channel
|
||||
QString UPDATER_BASE;
|
||||
|
||||
|
||||
/// User-Agent to use.
|
||||
QString USER_AGENT;
|
||||
|
||||
/// User-Agent to use for uncached requests.
|
||||
QString USER_AGENT_UNCACHED;
|
||||
|
||||
QString VERSION_CHANNEL;
|
||||
|
||||
/// URL for notifications
|
||||
QString NOTIFICATION_URL;
|
||||
|
@ -54,8 +54,6 @@
|
||||
|
||||
#include "java/JavaUtils.h"
|
||||
|
||||
#include "updater/UpdateChecker.h"
|
||||
|
||||
#include "tools/JProfiler.h"
|
||||
#include "tools/JVisualVM.h"
|
||||
#include "tools/MCEditTool.h"
|
||||
@ -115,45 +113,6 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QSt
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
QString getIdealPlatform(QString currentPlatform) {
|
||||
auto info = Sys::getKernelInfo();
|
||||
switch(info.kernelType) {
|
||||
case Sys::KernelType::Darwin: {
|
||||
if(info.kernelMajor >= 17) {
|
||||
// macOS 10.13 or newer
|
||||
return "osx64-5.15.2";
|
||||
}
|
||||
else {
|
||||
// macOS 10.12 or older
|
||||
return "osx64";
|
||||
}
|
||||
}
|
||||
case Sys::KernelType::Windows: {
|
||||
// FIXME: 5.15.2 is not stable on Windows, due to a large number of completely unpredictable and hard to reproduce issues
|
||||
break;
|
||||
/*
|
||||
if(info.kernelMajor == 6 && info.kernelMinor >= 1) {
|
||||
// Windows 7
|
||||
return "win32-5.15.2";
|
||||
}
|
||||
else if (info.kernelMajor > 6) {
|
||||
// Above Windows 7
|
||||
return "win32-5.15.2";
|
||||
}
|
||||
else {
|
||||
// Below Windows 7
|
||||
return "win32";
|
||||
}
|
||||
*/
|
||||
}
|
||||
case Sys::KernelType::Undetermined:
|
||||
case Sys::KernelType::Linux: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return currentPlatform;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
@ -319,7 +278,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
QString xdgDataHome = QFile::decodeName(qgetenv("XDG_DATA_HOME"));
|
||||
if (xdgDataHome.isEmpty())
|
||||
xdgDataHome = QDir::homePath() + QLatin1String("/.local/share");
|
||||
dataPath = xdgDataHome + "/polymc";
|
||||
dataPath = xdgDataHome + "/sneedmc";
|
||||
adjustedBy += "XDG standard " + dataPath;
|
||||
#elif defined(Q_OS_MAC)
|
||||
QDir foo(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), ".."));
|
||||
@ -582,9 +541,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
// Initialize application settings
|
||||
{
|
||||
m_settings.reset(new INISettingsObject(BuildConfig.LAUNCHER_CONFIGFILE, this));
|
||||
// Updates
|
||||
m_settings->registerSetting("UpdateChannel", BuildConfig.VERSION_CHANNEL);
|
||||
m_settings->registerSetting("AutoUpdate", true);
|
||||
|
||||
// Theming
|
||||
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("UpdateDialogGeometry", "");
|
||||
|
||||
// pastebin URL
|
||||
m_settings->registerSetting("PastebinURL", "https://0x0.st");
|
||||
|
||||
@ -755,16 +709,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
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
|
||||
{
|
||||
auto setting = APPLICATION->settings()->getSetting("IconsDir");
|
||||
@ -1145,7 +1089,7 @@ void Application::setIconTheme(const QString& name)
|
||||
QIcon Application::getThemedIcon(const QString& name)
|
||||
{
|
||||
if(name == "logo") {
|
||||
return QIcon(":/org.polymc.PolyMC.svg");
|
||||
return QIcon(":/org.sneederix.SneedMC.svg");
|
||||
}
|
||||
return XdgIcon::fromTheme(name);
|
||||
}
|
||||
@ -1171,11 +1115,7 @@ bool Application::launch(
|
||||
MinecraftServerTargetPtr serverToJoin,
|
||||
MinecraftAccountPtr accountToUse
|
||||
) {
|
||||
if(m_updateRunning)
|
||||
{
|
||||
qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
|
||||
}
|
||||
else if(instance->canLaunch())
|
||||
if(instance->canLaunch())
|
||||
{
|
||||
auto & extras = m_instanceExtras[instance->id()];
|
||||
auto & window = extras.window;
|
||||
@ -1240,10 +1180,6 @@ bool Application::kill(InstancePtr instance)
|
||||
void Application::addRunningInstance()
|
||||
{
|
||||
m_runningInstances ++;
|
||||
if(m_runningInstances == 1)
|
||||
{
|
||||
emit updateAllowedChanged(false);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::subRunningInstance()
|
||||
@ -1254,10 +1190,6 @@ void Application::subRunningInstance()
|
||||
return;
|
||||
}
|
||||
m_runningInstances --;
|
||||
if(m_runningInstances == 0)
|
||||
{
|
||||
emit updateAllowedChanged(true);
|
||||
}
|
||||
}
|
||||
|
||||
bool Application::shouldExitNow() const
|
||||
@ -1265,17 +1197,6 @@ bool Application::shouldExitNow() const
|
||||
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()
|
||||
{
|
||||
auto controller = qobject_cast<LaunchController *>(QObject::sender());
|
||||
@ -1361,7 +1282,6 @@ MainWindow* Application::showMainWindow(bool minimized)
|
||||
}
|
||||
|
||||
m_mainWindow->checkInstancePathForProblems();
|
||||
connect(this, &Application::updateAllowedChanged, m_mainWindow, &MainWindow::updatesAllowedChanged);
|
||||
connect(m_mainWindow, &MainWindow::isClosing, this, &Application::on_windowClose);
|
||||
m_openWindows++;
|
||||
}
|
||||
|
@ -7,7 +7,6 @@
|
||||
#include <QIcon>
|
||||
#include <QDateTime>
|
||||
#include <QUrl>
|
||||
#include <updater/GoUpdate.h>
|
||||
|
||||
#include <BaseInstance.h>
|
||||
|
||||
@ -27,7 +26,6 @@ class AccountList;
|
||||
class IconList;
|
||||
class QNetworkAccessManager;
|
||||
class JavaInstallList;
|
||||
class UpdateChecker;
|
||||
class BaseProfilerFactory;
|
||||
class BaseDetachedToolFactory;
|
||||
class TranslationsModel;
|
||||
@ -75,10 +73,6 @@ public:
|
||||
|
||||
void setApplicationTheme(const QString& name, bool initial);
|
||||
|
||||
shared_qobject_ptr<UpdateChecker> updateChecker() {
|
||||
return m_updateChecker;
|
||||
}
|
||||
|
||||
std::shared_ptr<TranslationsModel> translations();
|
||||
|
||||
std::shared_ptr<JavaInstallList> javalist();
|
||||
@ -133,13 +127,9 @@ public:
|
||||
InstanceWindow *showInstanceWindow(InstancePtr instance, QString page = QString());
|
||||
MainWindow *showMainWindow(bool minimized = false);
|
||||
|
||||
void updateIsRunning(bool running);
|
||||
bool updatesAreAllowed();
|
||||
|
||||
void ShowGlobalSettings(class QWidget * parent, QString open_page = QString());
|
||||
|
||||
signals:
|
||||
void updateAllowedChanged(bool status);
|
||||
void globalSettingsAboutToOpen();
|
||||
void globalSettingsClosed();
|
||||
|
||||
@ -177,7 +167,6 @@ private:
|
||||
|
||||
shared_qobject_ptr<QNetworkAccessManager> m_network;
|
||||
|
||||
shared_qobject_ptr<UpdateChecker> m_updateChecker;
|
||||
shared_qobject_ptr<AccountList> m_accounts;
|
||||
|
||||
shared_qobject_ptr<HttpMetaCache> m_metacache;
|
||||
@ -214,7 +203,6 @@ private:
|
||||
// main state variables
|
||||
size_t m_openWindows = 0;
|
||||
size_t m_runningInstances = 0;
|
||||
bool m_updateRunning = false;
|
||||
|
||||
// main window, if any
|
||||
MainWindow * m_mainWindow = nullptr;
|
||||
|
@ -152,28 +152,6 @@ set(LAUNCH_SOURCES
|
||||
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
|
||||
set(NOTIFICATIONS_SOURCES
|
||||
# Notifications - short warning messages
|
||||
@ -567,7 +545,6 @@ set(LOGIC_SOURCES
|
||||
${PATHMATCHER_SOURCES}
|
||||
${NET_SOURCES}
|
||||
${LAUNCH_SOURCES}
|
||||
${UPDATE_SOURCES}
|
||||
${NOTIFICATIONS_SOURCES}
|
||||
${NEWS_SOURCES}
|
||||
${MINECRAFT_SOURCES}
|
||||
@ -591,8 +568,6 @@ SET(LAUNCHER_SOURCES
|
||||
# Application base
|
||||
Application.h
|
||||
Application.cpp
|
||||
UpdateController.cpp
|
||||
UpdateController.h
|
||||
ApplicationMessage.h
|
||||
ApplicationMessage.cpp
|
||||
|
||||
@ -806,8 +781,6 @@ SET(LAUNCHER_SOURCES
|
||||
ui/pagedialog/PageDialog.h
|
||||
ui/dialogs/ProgressDialog.cpp
|
||||
ui/dialogs/ProgressDialog.h
|
||||
ui/dialogs/UpdateDialog.cpp
|
||||
ui/dialogs/UpdateDialog.h
|
||||
ui/dialogs/VersionSelectDialog.cpp
|
||||
ui/dialogs/VersionSelectDialog.h
|
||||
ui/dialogs/SkinUploadDialog.cpp
|
||||
@ -904,7 +877,6 @@ qt5_wrap_ui(LAUNCHER_UI
|
||||
ui/dialogs/ProgressDialog.ui
|
||||
ui/dialogs/NewInstanceDialog.ui
|
||||
ui/dialogs/NotificationDialog.ui
|
||||
ui/dialogs/UpdateDialog.ui
|
||||
ui/dialogs/NewComponentDialog.ui
|
||||
ui/dialogs/ProfileSelectDialog.ui
|
||||
ui/dialogs/SkinUploadDialog.ui
|
||||
@ -959,7 +931,7 @@ target_link_libraries(Launcher_logic
|
||||
Launcher_iconfix
|
||||
QuaZip::QuaZip
|
||||
hoedown
|
||||
PolyMC_rainbow
|
||||
SneedMC_rainbow
|
||||
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;
|
||||
Net::Mode m_mode = Net::Mode::Offline;
|
||||
};
|
||||
|
||||
|
@ -40,7 +40,7 @@ int main(int argc, char *argv[])
|
||||
Q_INIT_RESOURCE(multimc);
|
||||
Q_INIT_RESOURCE(backgrounds);
|
||||
Q_INIT_RESOURCE(documents);
|
||||
Q_INIT_RESOURCE(polymc);
|
||||
Q_INIT_RESOURCE(sneedmc);
|
||||
|
||||
Q_INIT_RESOURCE(pe_dark);
|
||||
Q_INIT_RESOURCE(pe_light);
|
||||
|
@ -16,10 +16,10 @@
|
||||
#include "launch/LaunchTask.h"
|
||||
#include "launch/steps/LookupServerAddress.h"
|
||||
#include "launch/steps/PostLaunchCommand.h"
|
||||
#include "launch/steps/Update.h"
|
||||
#include "launch/steps/PreLaunchCommand.h"
|
||||
#include "launch/steps/TextPrint.h"
|
||||
#include "launch/steps/CheckJava.h"
|
||||
#include "launch/steps/Update.h"
|
||||
|
||||
#include "minecraft/launch/LauncherPartLaunch.h"
|
||||
#include "minecraft/launch/DirectJavaLaunch.h"
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QFile>
|
||||
#include <QHttpMultiPart>
|
||||
|
||||
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 <QApplication>
|
||||
#include <QFileDialog>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include "ui/dialogs/ProgressDialog.h"
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
|
@ -61,8 +61,6 @@
|
||||
#include <news/NewsChecker.h>
|
||||
#include <notifications/NotificationChecker.h>
|
||||
#include <tools/BaseProfiler.h>
|
||||
#include <updater/DownloadTask.h>
|
||||
#include <updater/UpdateChecker.h>
|
||||
#include <DesktopServices.h>
|
||||
#include "InstanceWindow.h"
|
||||
#include "InstancePageProvider.h"
|
||||
@ -80,12 +78,10 @@
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
#include "ui/dialogs/IconPickerDialog.h"
|
||||
#include "ui/dialogs/CopyInstanceDialog.h"
|
||||
#include "ui/dialogs/UpdateDialog.h"
|
||||
#include "ui/dialogs/EditAccountDialog.h"
|
||||
#include "ui/dialogs/NotificationDialog.h"
|
||||
#include "ui/dialogs/ExportInstanceDialog.h"
|
||||
|
||||
#include "UpdateController.h"
|
||||
#include "KonamiCode.h"
|
||||
|
||||
#include "InstanceImportTask.h"
|
||||
@ -199,7 +195,6 @@ class MainWindow::Ui
|
||||
public:
|
||||
TranslatedAction actionAddInstance;
|
||||
//TranslatedAction actionRefresh;
|
||||
TranslatedAction actionCheckUpdate;
|
||||
TranslatedAction actionSettings;
|
||||
TranslatedAction actionMoreNews;
|
||||
TranslatedAction actionManageAccounts;
|
||||
@ -386,17 +381,6 @@ public:
|
||||
helpButtonAction->setDefaultWidget(helpMenuButton);
|
||||
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();
|
||||
|
||||
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();
|
||||
checker->setNotificationsUrl(QUrl(BuildConfig.NOTIFICATION_URL));
|
||||
@ -1099,15 +1064,6 @@ void MainWindow::repopulateAccountsMenu()
|
||||
accountMenu->addAction(ui->actionManageAccounts);
|
||||
}
|
||||
|
||||
void MainWindow::updatesAllowedChanged(bool allowed)
|
||||
{
|
||||
if(!BuildConfig.UPDATER_ENABLED)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ui->actionCheckUpdate->setEnabled(allowed);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
QStringList split = string.split(',', QString::SkipEmptyParts);
|
||||
@ -1276,40 +1206,6 @@ void MainWindow::notificationsChanged()
|
||||
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)
|
||||
{
|
||||
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()
|
||||
{
|
||||
APPLICATION->ShowGlobalSettings(this, "global-settings");
|
||||
|
@ -24,7 +24,6 @@
|
||||
#include "BaseInstance.h"
|
||||
#include "minecraft/auth/MinecraftAccount.h"
|
||||
#include "net/NetJob.h"
|
||||
#include "updater/GoUpdate.h"
|
||||
|
||||
class LaunchController;
|
||||
class NewsChecker;
|
||||
@ -55,8 +54,6 @@ public:
|
||||
|
||||
void checkInstancePathForProblems();
|
||||
|
||||
void updatesAllowedChanged(bool allowed);
|
||||
|
||||
void droppedURLs(QList<QUrl> urls);
|
||||
signals:
|
||||
void isClosing();
|
||||
@ -99,8 +96,6 @@ private slots:
|
||||
|
||||
void on_actionViewCentralModsFolder_triggered();
|
||||
|
||||
void checkForUpdates();
|
||||
|
||||
void on_actionSettings_triggered();
|
||||
|
||||
void on_actionInstanceSettings_triggered();
|
||||
@ -162,10 +157,6 @@ private slots:
|
||||
|
||||
void startTask(Task *task);
|
||||
|
||||
void updateAvailable(GoUpdate::Status status);
|
||||
|
||||
void updateNotAvailable();
|
||||
|
||||
void notificationsChanged();
|
||||
|
||||
void defaultAccountChanged();
|
||||
@ -176,11 +167,6 @@ private slots:
|
||||
|
||||
void updateNewsLabel();
|
||||
|
||||
/*!
|
||||
* Runs the DownloadTask and installs updates.
|
||||
*/
|
||||
void downloadUpdates(GoUpdate::Status status);
|
||||
|
||||
void konamiTriggered();
|
||||
|
||||
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 <QTextCharFormat>
|
||||
|
||||
#include "updater/UpdateChecker.h"
|
||||
|
||||
#include "settings/SettingsObject.h"
|
||||
#include <FileSystem.h>
|
||||
#include "Application.h"
|
||||
@ -56,23 +54,6 @@ LauncherPage::LauncherPage(QWidget *parent) : QWidget(parent), ui(new Ui::Launch
|
||||
m_languageModel = APPLICATION->translations();
|
||||
loadSettings();
|
||||
|
||||
if(BuildConfig.UPDATER_ENABLED)
|
||||
{
|
||||
QObject::connect(APPLICATION->updateChecker().get(), &UpdateChecker::channelListLoaded, this, &LauncherPage::refreshUpdateChannelList);
|
||||
|
||||
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->consoleFont, SIGNAL(currentFontChanged(QFont)), SLOT(refreshFontPreview()));
|
||||
|
||||
@ -158,78 +139,6 @@ void LauncherPage::on_migrateDataFolderMacBtn_clicked()
|
||||
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()
|
||||
{
|
||||
auto s = APPLICATION->settings();
|
||||
@ -239,9 +148,6 @@ void LauncherPage::applySettings()
|
||||
s->set("ShownNotifications", QString());
|
||||
}
|
||||
|
||||
// Updates
|
||||
s->set("AutoUpdate", ui->autoUpdateCheckBox->isChecked());
|
||||
s->set("UpdateChannel", m_currentUpdateChannel);
|
||||
auto original = s->get("IconTheme").toString();
|
||||
//FIXME: make generic
|
||||
switch (ui->themeComboBox->currentIndex())
|
||||
@ -319,9 +225,6 @@ void LauncherPage::applySettings()
|
||||
void LauncherPage::loadSettings()
|
||||
{
|
||||
auto s = APPLICATION->settings();
|
||||
// Updates
|
||||
ui->autoUpdateCheckBox->setChecked(s->get("AutoUpdate").toBool());
|
||||
m_currentUpdateChannel = s->get("UpdateChannel").toString();
|
||||
//FIXME: make generic
|
||||
auto theme = s->get("IconTheme").toString();
|
||||
if (theme == "pe_dark")
|
||||
|
@ -69,31 +69,14 @@ slots:
|
||||
void on_iconsDirBrowseBtn_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
|
||||
*/
|
||||
void refreshFontPreview();
|
||||
|
||||
void updateChannelSelectionChanged(int index);
|
||||
|
||||
private:
|
||||
Ui::LauncherPage *ui;
|
||||
|
||||
/*!
|
||||
* Stores the currently selected update channel.
|
||||
*/
|
||||
QString m_currentUpdateChannel;
|
||||
|
||||
// default format for the font preview...
|
||||
QTextCharFormat *defaultFormat;
|
||||
|
||||
@ -101,3 +84,4 @@ private:
|
||||
|
||||
std::shared_ptr<TranslationsModel> m_languageModel;
|
||||
};
|
||||
|
||||
|
@ -45,49 +45,6 @@
|
||||
<string>Features</string>
|
||||
</attribute>
|
||||
<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>
|
||||
<widget class="QGroupBox" name="foldersBox">
|
||||
<property name="title">
|
||||
@ -491,8 +448,6 @@
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>tabWidget</tabstop>
|
||||
<tabstop>autoUpdateCheckBox</tabstop>
|
||||
<tabstop>updateChannelComboBox</tabstop>
|
||||
<tabstop>instDirTextBox</tabstop>
|
||||
<tabstop>instDirBrowseBtn</tabstop>
|
||||
<tabstop>modsDirTextBox</tabstop>
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <QIcon>
|
||||
#include <QScrollBar>
|
||||
#include <QShortcut>
|
||||
#include <QIdentityProxyModel>
|
||||
|
||||
#include "launch/LaunchTask.h"
|
||||
#include "settings/Setting.h"
|
||||
|
@ -16,6 +16,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "ui/pages/BasePage.h"
|
||||
@ -119,3 +120,4 @@ public:
|
||||
}
|
||||
virtual bool shouldDisplay() const;
|
||||
};
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QMenu>
|
||||
#include <QTimer>
|
||||
|
||||
static const int COLUMN_COUNT = 2; // 3 , TBD: latency and other nice things.
|
||||
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include <QTreeView>
|
||||
#include <QInputDialog>
|
||||
#include <QProcess>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
#include "tools/MCEditTool.h"
|
||||
#include "FileSystem.h"
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "ListModel.h"
|
||||
#include "Application.h"
|
||||
#include "net/NetJob.h"
|
||||
|
||||
#include <MMCStrings.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_library(SneedMC SHARED ${RAINBOW_SOURCES})
|
||||
add_library(SneedMC_rainbow SHARED ${RAINBOW_SOURCES})
|
||||
target_include_directories(SneedMC_rainbow PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||
|
||||
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_Name "${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_Git "https://github.com/Sneederix/SneedMC" PARENT_SCOPE)
|
||||
set(Launcher_DesktopFileName "org.sneederix.SneedMC.desktop" PARENT_SCOPE)
|
||||
|
Loading…
Reference in New Issue
Block a user