aboutsummaryrefslogtreecommitdiff
path: root/launcher
diff options
context:
space:
mode:
Diffstat (limited to 'launcher')
-rw-r--r--launcher/Application.cpp8
-rw-r--r--launcher/CMakeLists.txt5
-rw-r--r--launcher/InstanceImportTask.cpp9
-rw-r--r--launcher/LaunchController.cpp20
-rw-r--r--launcher/LoggedProcess.cpp32
-rw-r--r--launcher/LoggedProcess.h5
-rw-r--r--launcher/java/JavaUtils.cpp12
-rw-r--r--launcher/minecraft/mod/ModFolderModel.cpp6
-rw-r--r--launcher/minecraft/mod/ModFolderModel.h1
-rw-r--r--launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp17
-rw-r--r--launcher/minecraft/mod/tasks/ModFolderLoadTask.h3
-rw-r--r--launcher/modplatform/atlauncher/ATLPackInstallTask.cpp144
-rw-r--r--launcher/modplatform/atlauncher/ATLPackInstallTask.h10
-rw-r--r--launcher/modplatform/atlauncher/ATLPackManifest.cpp64
-rw-r--r--launcher/modplatform/atlauncher/ATLPackManifest.h23
-rw-r--r--launcher/modplatform/modpacksch/FTBPackInstallTask.cpp9
-rw-r--r--launcher/net/HttpMetaCache.cpp14
-rw-r--r--launcher/net/HttpMetaCache.h10
-rw-r--r--launcher/net/MetaCacheSink.cpp38
-rw-r--r--launcher/ui/MainWindow.cpp3
-rw-r--r--launcher/ui/dialogs/AboutDialog.cpp11
-rw-r--r--launcher/ui/dialogs/AboutDialog.ui20
-rw-r--r--launcher/ui/dialogs/BlockedModsDialog.cpp28
-rw-r--r--launcher/ui/dialogs/BlockedModsDialog.h22
-rw-r--r--launcher/ui/dialogs/BlockedModsDialog.ui84
-rw-r--r--launcher/ui/pages/instance/ExternalResourcesPage.cpp2
-rw-r--r--launcher/ui/pages/instance/ExternalResourcesPage.h2
-rw-r--r--launcher/ui/pages/instance/ModFolderPage.cpp34
-rw-r--r--launcher/ui/pages/instance/ModFolderPage.h1
-rw-r--r--launcher/ui/pages/instance/WorldListPage.cpp12
-rw-r--r--launcher/ui/pages/instance/WorldListPage.h2
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp59
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlPage.h6
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp95
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h56
-rw-r--r--launcher/updater/UpdateChecker.cpp3
-rw-r--r--launcher/updater/UpdateChecker.h2
37 files changed, 741 insertions, 131 deletions
diff --git a/launcher/Application.cpp b/launcher/Application.cpp
index 2bd91fd7..553b3229 100644
--- a/launcher/Application.cpp
+++ b/launcher/Application.cpp
@@ -321,7 +321,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
{
// Root path is used for updates and portable data
-#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
+#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
QDir foo(FS::PathCombine(binPath, "..")); // typically portable-root or /usr
m_rootPath = foo.absolutePath();
#elif defined(Q_OS_WIN32)
@@ -774,7 +774,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
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));
+ m_updateChecker.reset(new UpdateChecker(m_network, channelUrl, BuildConfig.VERSION_CHANNEL));
qDebug() << "<> Updater started.";
}
@@ -864,6 +864,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_metacache->addBase("ModpacksCHPacks", QDir("cache/ModpacksCHPacks").absolutePath());
m_metacache->addBase("TechnicPacks", QDir("cache/TechnicPacks").absolutePath());
m_metacache->addBase("FlamePacks", QDir("cache/FlamePacks").absolutePath());
+ m_metacache->addBase("FlameMods", QDir("cache/FlameMods").absolutePath());
m_metacache->addBase("ModrinthPacks", QDir("cache/ModrinthPacks").absolutePath());
m_metacache->addBase("root", QDir::currentPath());
m_metacache->addBase("translations", QDir("translations").absolutePath());
@@ -1258,6 +1259,9 @@ bool Application::launch(
}
connect(controller.get(), &LaunchController::succeeded, this, &Application::controllerSucceeded);
connect(controller.get(), &LaunchController::failed, this, &Application::controllerFailed);
+ connect(controller.get(), &LaunchController::aborted, this, [this] {
+ controllerFailed(tr("Aborted"));
+ });
addRunningInstance();
controller->start();
return true;
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 9c5c2ea8..cff07b4b 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -764,6 +764,8 @@ SET(LAUNCHER_SOURCES
ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h
ui/pages/modplatform/atlauncher/AtlPage.cpp
ui/pages/modplatform/atlauncher/AtlPage.h
+ ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp
+ ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h
ui/pages/modplatform/ftb/FtbFilterModel.cpp
ui/pages/modplatform/ftb/FtbFilterModel.h
@@ -849,6 +851,8 @@ SET(LAUNCHER_SOURCES
ui/dialogs/ModDownloadDialog.h
ui/dialogs/ScrollMessageBox.cpp
ui/dialogs/ScrollMessageBox.h
+ ui/dialogs/BlockedModsDialog.cpp
+ ui/dialogs/BlockedModsDialog.h
ui/dialogs/ChooseProviderDialog.h
ui/dialogs/ChooseProviderDialog.cpp
ui/dialogs/ModUpdateDialog.cpp
@@ -958,6 +962,7 @@ qt_wrap_ui(LAUNCHER_UI
ui/dialogs/EditAccountDialog.ui
ui/dialogs/ReviewMessageBox.ui
ui/dialogs/ScrollMessageBox.ui
+ ui/dialogs/BlockedModsDialog.ui
ui/dialogs/ChooseProviderDialog.ui
)
diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp
index 14e1cd47..de0afc96 100644
--- a/launcher/InstanceImportTask.cpp
+++ b/launcher/InstanceImportTask.cpp
@@ -60,7 +60,7 @@
#include "net/ChecksumValidator.h"
#include "ui/dialogs/CustomMessageBox.h"
-#include "ui/dialogs/ScrollMessageBox.h"
+#include "ui/dialogs/BlockedModsDialog.h"
#include <algorithm>
@@ -396,21 +396,24 @@ void InstanceImportTask::processFlame()
auto results = m_modIdResolver->getResults();
//first check for blocked mods
QString text;
+ QList<QUrl> urls;
auto anyBlocked = false;
for(const auto& result: results.files.values()) {
if (!result.resolved || result.url.isEmpty()) {
text += QString("%1: <a href='%2'>%2</a><br/>").arg(result.fileName, result.websiteUrl);
+ urls.append(QUrl(result.websiteUrl));
anyBlocked = true;
}
}
if(anyBlocked) {
qWarning() << "Blocked mods found, displaying mod list";
- auto message_dialog = new ScrollMessageBox(m_parent,
+ auto message_dialog = new BlockedModsDialog(m_parent,
tr("Blocked mods found"),
tr("The following mods were blocked on third party launchers.<br/>"
"You will need to manually download them and add them to the modpack"),
- text);
+ text,
+ urls);
message_dialog->setModal(true);
if (message_dialog->exec()) {
diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp
index d36ee3fe..11f9b2bb 100644
--- a/launcher/LaunchController.cpp
+++ b/launcher/LaunchController.cpp
@@ -145,16 +145,26 @@ void LaunchController::login() {
return;
}
- // we try empty password first :)
- QString password;
// we loop until the user succeeds in logging in or gives up
bool tryagain = true;
- // the failure. the default failure.
- const QString needLoginAgain = tr("Your account is currently not logged in. Please enter your password to log in again. <br /> <br /> This could be caused by a password change.");
- QString failReason = needLoginAgain;
+ unsigned int tries = 0;
while (tryagain)
{
+ if (tries > 0 && tries % 3 == 0) {
+ auto result = QMessageBox::question(
+ m_parentWidget,
+ tr("Continue launch?"),
+ tr("It looks like we couldn't launch after %1 tries. Do you want to continue trying?")
+ .arg(tries)
+ );
+
+ if (result == QMessageBox::No) {
+ emitAborted();
+ return;
+ }
+ }
+ tries++;
m_session = std::make_shared<AuthSession>();
m_session->wants_online = m_online;
m_accountToUse->fillSession(m_session);
diff --git a/launcher/LoggedProcess.cpp b/launcher/LoggedProcess.cpp
index fbdeed8f..6447f5c6 100644
--- a/launcher/LoggedProcess.cpp
+++ b/launcher/LoggedProcess.cpp
@@ -34,8 +34,9 @@
*/
#include "LoggedProcess.h"
-#include "MessageLevel.h"
#include <QDebug>
+#include <QTextDecoder>
+#include "MessageLevel.h"
LoggedProcess::LoggedProcess(QObject *parent) : QProcess(parent)
{
@@ -59,25 +60,26 @@ LoggedProcess::~LoggedProcess()
}
}
-QStringList reprocess(const QByteArray & data, QString & leftover)
+QStringList reprocess(const QByteArray& data, QTextDecoder& decoder)
{
- QString str = leftover + QString::fromLocal8Bit(data);
-
- str.remove('\r');
- QStringList lines = str.split("\n");
- leftover = lines.takeLast();
+ auto str = decoder.toUnicode(data);
+#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
+ auto lines = str.remove(QChar::CarriageReturn).split(QChar::LineFeed, QString::SkipEmptyParts);
+#else
+ auto lines = str.remove(QChar::CarriageReturn).split(QChar::LineFeed, Qt::SkipEmptyParts);
+#endif
return lines;
}
void LoggedProcess::on_stdErr()
{
- auto lines = reprocess(readAllStandardError(), m_err_leftover);
+ auto lines = reprocess(readAllStandardError(), m_err_decoder);
emit log(lines, MessageLevel::StdErr);
}
void LoggedProcess::on_stdOut()
{
- auto lines = reprocess(readAllStandardOutput(), m_out_leftover);
+ auto lines = reprocess(readAllStandardOutput(), m_out_decoder);
emit log(lines, MessageLevel::StdOut);
}
@@ -86,18 +88,6 @@ void LoggedProcess::on_exit(int exit_code, QProcess::ExitStatus status)
// save the exit code
m_exit_code = exit_code;
- // Flush console window
- if (!m_err_leftover.isEmpty())
- {
- emit log({m_err_leftover}, MessageLevel::StdErr);
- m_err_leftover.clear();
- }
- if (!m_out_leftover.isEmpty())
- {
- emit log({m_err_leftover}, MessageLevel::StdOut);
- m_out_leftover.clear();
- }
-
// based on state, send signals
if (!m_is_aborting)
{
diff --git a/launcher/LoggedProcess.h b/launcher/LoggedProcess.h
index 61e74bd9..2360d1ea 100644
--- a/launcher/LoggedProcess.h
+++ b/launcher/LoggedProcess.h
@@ -36,6 +36,7 @@
#pragma once
#include <QProcess>
+#include <QTextDecoder>
#include "MessageLevel.h"
/*
@@ -88,8 +89,8 @@ private:
void changeState(LoggedProcess::State state);
private:
- QString m_err_leftover;
- QString m_out_leftover;
+ QTextDecoder m_err_decoder = QTextDecoder(QTextCodec::codecForLocale());
+ QTextDecoder m_out_decoder = QTextDecoder(QTextCodec::codecForLocale());
bool m_killed = false;
State m_state = NotRunning;
int m_exit_code = 0;
diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp
index 2b19fca0..2f91605b 100644
--- a/launcher/java/JavaUtils.cpp
+++ b/launcher/java/JavaUtils.cpp
@@ -174,11 +174,17 @@ JavaInstallPtr JavaUtils::GetDefaultJava()
QStringList addJavasFromEnv(QList<QString> javas)
{
- QByteArray env = qgetenv("POLYMC_JAVA_PATHS");
+ auto env = qEnvironmentVariable("POLYMC_JAVA_PATHS");
#if defined(Q_OS_WIN32)
- QList<QString> javaPaths = QString::fromLocal8Bit(env).replace("\\", "/").split(QLatin1String(";"));
+ QList<QString> javaPaths = env.replace("\\", "/").split(QLatin1String(";"));
+
+ auto envPath = qEnvironmentVariable("PATH");
+ QList<QString> javaPathsfromPath = envPath.replace("\\", "/").split(QLatin1String(";"));
+ for (QString string : javaPathsfromPath) {
+ javaPaths.append(string + "/javaw.exe");
+ }
#else
- QList<QString> javaPaths = QString::fromLocal8Bit(env).split(QLatin1String(":"));
+ QList<QString> javaPaths = env.split(QLatin1String(":"));
#endif
for(QString i : javaPaths)
{
diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp
index 112d219e..d4c5e819 100644
--- a/launcher/minecraft/mod/ModFolderModel.cpp
+++ b/launcher/minecraft/mod/ModFolderModel.cpp
@@ -63,6 +63,9 @@ void ModFolderModel::startWatching()
if(is_watching)
return;
+ // Remove orphaned metadata next time
+ m_first_folder_load = true;
+
update();
// Watch the mods folder
@@ -113,7 +116,8 @@ bool ModFolderModel::update()
}
auto index_dir = indexDir();
- auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed);
+ auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load);
+ m_first_folder_load = false;
m_update = task->result();
diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h
index a7d3ece0..3d6efac3 100644
--- a/launcher/minecraft/mod/ModFolderModel.h
+++ b/launcher/minecraft/mod/ModFolderModel.h
@@ -172,6 +172,7 @@ protected:
bool interaction_disabled = false;
QDir m_dir;
bool m_is_indexed;
+ bool m_first_folder_load = true;
QMap<QString, int> modsIndex;
QMap<int, LocalModParseTask::ResultPtr> activeTickets;
int nextResolutionTicket = 0;
diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp
index a2e055ba..015ead80 100644
--- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp
+++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp
@@ -38,8 +38,8 @@
#include "minecraft/mod/MetadataHandler.h"
-ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed)
- : m_mods_dir(mods_dir), m_index_dir(index_dir), m_is_indexed(is_indexed), m_result(new Result())
+ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed, bool clean_orphan)
+ : m_mods_dir(mods_dir), m_index_dir(index_dir), m_is_indexed(is_indexed), m_clean_orphan(clean_orphan), m_result(new Result())
{}
void ModFolderLoadTask::run()
@@ -83,6 +83,19 @@ void ModFolderLoadTask::run()
}
}
+ // Remove orphan metadata to prevent issues
+ // See https://github.com/PolyMC/PolyMC/issues/996
+ if (m_clean_orphan) {
+ QMutableMapIterator<QString, Mod::Ptr> iter(m_result->mods);
+ while (iter.hasNext()) {
+ auto mod = iter.next().value();
+ if (mod->status() == ModStatus::NotInstalled) {
+ mod->destroy(m_index_dir, false);
+ iter.remove();
+ }
+ }
+ }
+
emit succeeded();
}
diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h
index 0b6bb6cc..1f2015d2 100644
--- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h
+++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h
@@ -56,7 +56,7 @@ public:
}
public:
- ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed);
+ ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed, bool clean_orphan = false);
void run();
signals:
void succeeded();
@@ -67,5 +67,6 @@ private:
private:
QDir& m_mods_dir, m_index_dir;
bool m_is_indexed;
+ bool m_clean_orphan;
ResultPtr m_result;
};
diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp
index 0ed0ad29..5ed13470 100644
--- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp
+++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp
@@ -60,12 +60,13 @@ namespace ATLauncher {
static Meta::VersionPtr getComponentVersion(const QString& uid, const QString& version);
-PackInstallTask::PackInstallTask(UserInteractionSupport *support, QString packName, QString version)
+PackInstallTask::PackInstallTask(UserInteractionSupport *support, QString packName, QString version, InstallMode installMode)
{
m_support = support;
m_pack_name = packName;
m_pack_safe_name = packName.replace(QRegularExpression("[^A-Za-z0-9]"), "");
m_version_name = version;
+ m_install_mode = installMode;
}
bool PackInstallTask::abort()
@@ -117,9 +118,30 @@ void PackInstallTask::onDownloadSucceeded()
}
m_version = version;
- // Display install message if one exists
- if (!m_version.messages.install.isEmpty())
- m_support->displayMessage(m_version.messages.install);
+ // Derived from the installation mode
+ QString message;
+ bool resetDirectory;
+
+ switch (m_install_mode) {
+ case InstallMode::Reinstall:
+ case InstallMode::Update:
+ message = m_version.messages.update;
+ resetDirectory = true;
+ break;
+
+ case InstallMode::Install:
+ message = m_version.messages.install;
+ resetDirectory = false;
+ break;
+
+ default:
+ emitFailed(tr("Unsupported installation mode"));
+ break;
+ }
+
+ // Display message if one exists
+ if (!message.isEmpty())
+ m_support->displayMessage(message);
auto ver = getComponentVersion("net.minecraft", m_version.minecraft);
if (!ver) {
@@ -128,6 +150,10 @@ void PackInstallTask::onDownloadSucceeded()
}
minecraftVersion = ver;
+ if (resetDirectory) {
+ deleteExistingFiles();
+ }
+
if(m_version.noConfigs) {
downloadMods();
}
@@ -143,6 +169,116 @@ void PackInstallTask::onDownloadFailed(QString reason)
emitFailed(reason);
}
+void PackInstallTask::deleteExistingFiles()
+{
+ setStatus(tr("Deleting existing files..."));
+
+ // Setup defaults, as per https://wiki.atlauncher.com/pack-admin/xml/delete
+ VersionDeletes deletes;
+ deletes.folders.append(VersionDelete{ "root", "mods%s%" });
+ deletes.folders.append(VersionDelete{ "root", "configs%s%" });
+ deletes.folders.append(VersionDelete{ "root", "bin%s%" });
+
+ // Setup defaults, as per https://wiki.atlauncher.com/pack-admin/xml/keep
+ VersionKeeps keeps;
+ keeps.files.append(VersionKeep{ "root", "mods%s%PortalGunSounds.pak" });
+ keeps.folders.append(VersionKeep{ "root", "mods%s%rei_minimap%s%" });
+ keeps.folders.append(VersionKeep{ "root", "mods%s%VoxelMods%s%" });
+ keeps.files.append(VersionKeep{ "root", "config%s%NEI.cfg" });
+ keeps.files.append(VersionKeep{ "root", "options.txt" });
+ keeps.files.append(VersionKeep{ "root", "servers.dat" });
+
+ // Merge with version deletes and keeps
+ for (const auto& item : m_version.deletes.files)
+ deletes.files.append(item);
+ for (const auto& item : m_version.deletes.folders)
+ deletes.folders.append(item);
+ for (const auto& item : m_version.keeps.files)
+ keeps.files.append(item);
+ for (const auto& item : m_version.keeps.folders)
+ keeps.folders.append(item);
+
+ auto getPathForBase = [this](const QString& base) {
+ auto minecraftPath = FS::PathCombine(m_stagingPath, "minecraft");
+
+ if (base == "root") {
+ return minecraftPath;
+ }
+ else if (base == "config") {
+ return FS::PathCombine(minecraftPath, "config");
+ }
+ else {
+ qWarning() << "Unrecognised base path" << base;
+ return minecraftPath;
+ }
+ };
+
+ auto convertToSystemPath = [](const QString& path) {
+ auto t = path;
+ t.replace("%s%", QDir::separator());
+ return t;
+ };
+
+ auto shouldKeep = [keeps, getPathForBase, convertToSystemPath](const QString& fullPath) {
+ for (const auto& item : keeps.files) {
+ auto basePath = getPathForBase(item.base);
+ auto targetPath = convertToSystemPath(item.target);
+ auto path = FS::PathCombine(basePath, targetPath);
+
+ if (fullPath == path) {
+ return true;
+ }
+ }
+
+ for (const auto& item : keeps.folders) {
+ auto basePath = getPathForBase(item.base);
+ auto targetPath = convertToSystemPath(item.target);
+ auto path = FS::PathCombine(basePath, targetPath);
+
+ if (fullPath.startsWith(path)) {
+ return true;
+ }
+ }
+
+ return false;
+ };
+
+ // Keep track of files to delete
+ QSet<QString> filesToDelete;
+
+ for (const auto& item : deletes.files) {
+ auto basePath = getPathForBase(item.base);
+ auto targetPath = convertToSystemPath(item.target);
+ auto fullPath = FS::PathCombine(basePath, targetPath);
+
+ if (shouldKeep(fullPath))
+ continue;
+
+ filesToDelete.insert(fullPath);
+ }
+
+ for (const auto& item : deletes.folders) {
+ auto basePath = getPathForBase(item.base);
+ auto targetPath = convertToSystemPath(item.target);
+ auto fullPath = FS::PathCombine(basePath, targetPath);
+
+ QDirIterator it(fullPath, QDirIterator::Subdirectories);
+ while (it.hasNext()) {
+ auto path = it.next();
+
+ if (shouldKeep(path))
+ continue;
+
+ filesToDelete.insert(path);
+ }
+ }
+
+ // Delete the files
+ for (const auto& item : filesToDelete) {
+ QFile::remove(item);
+ }
+}
+
QString PackInstallTask::getDirForModType(ModType type, QString raw)
{
switch (type) {
diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.h b/launcher/modplatform/atlauncher/ATLPackInstallTask.h
index 992ba9c5..a7124d59 100644
--- a/launcher/modplatform/atlauncher/ATLPackInstallTask.h
+++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.h
@@ -50,6 +50,12 @@
namespace ATLauncher {
+enum class InstallMode {
+ Install,
+ Reinstall,
+ Update,
+};
+
class UserInteractionSupport {
public:
@@ -75,7 +81,7 @@ class PackInstallTask : public InstanceTask
Q_OBJECT
public:
- explicit PackInstallTask(UserInteractionSupport *support, QString packName, QString version);
+ explicit PackInstallTask(UserInteractionSupport *support, QString packName, QString version, InstallMode installMode = InstallMode::Install);
virtual ~PackInstallTask(){}
bool canAbort() const override { return true; }
@@ -99,6 +105,7 @@ private:
bool createLibrariesComponent(QString instanceRoot, std::shared_ptr<PackProfile> profile);
bool createPackComponent(QString instanceRoot, std::shared_ptr<PackProfile> profile);
+ void deleteExistingFiles();
void installConfigs();
void extractConfigs();
void downloadMods();
@@ -117,6 +124,7 @@ private:
NetJob::Ptr jobPtr;
QByteArray response;
+ InstallMode m_install_mode;
QString m_pack_name;
QString m_pack_safe_name;
QString m_version_name;
diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.cpp b/launcher/modplatform/atlauncher/ATLPackManifest.cpp
index 3af02a09..5a458f4e 100644
--- a/launcher/modplatform/atlauncher/ATLPackManifest.cpp
+++ b/launcher/modplatform/atlauncher/ATLPackManifest.cpp
@@ -224,6 +224,64 @@ static void loadVersionExtraArguments(ATLauncher::PackVersionExtraArguments& a,
a.depends = Json::ensureString(obj, "depends", "");
}
+static void loadVersionKeep(ATLauncher::VersionKeep& k, QJsonObject& obj)
+{
+ k.base = Json::requireString(obj, "base");
+ k.target = Json::requireString(obj, "target");
+}
+
+static void loadVersionKeeps(ATLauncher::VersionKeeps& k, QJsonObject& obj)
+{
+ if (obj.contains("files")) {
+ auto files = Json::requireArray(obj, "files");
+ for (const auto keepRaw : files) {
+ auto keepObj = Json::requireObject(keepRaw);
+ ATLauncher::VersionKeep keep;
+ loadVersionKeep(keep, keepObj);
+ k.files.append(keep);
+ }
+ }
+
+ if (obj.contains("folders")) {
+ auto folders = Json::requireArray(obj, "folders");
+ for (const auto keepRaw : folders) {
+ auto keepObj = Json::requireObject(keepRaw);
+ ATLauncher::VersionKeep keep;
+ loadVersionKeep(keep, keepObj);
+ k.folders.append(keep);
+ }
+ }
+}
+
+static void loadVersionDelete(ATLauncher::VersionDelete& d, QJsonObject& obj)
+{
+ d.base = Json::requireString(obj, "base");
+ d.target = Json::requireString(obj, "target");
+}
+
+static void loadVersionDeletes(ATLauncher::VersionDeletes& d, QJsonObject& obj)
+{
+ if (obj.contains("files")) {
+ auto files = Json::requireArray(obj, "files");
+ for (const auto deleteRaw : files) {
+ auto deleteObj = Json::requireObject(deleteRaw);
+ ATLauncher::VersionDelete versionDelete;
+ loadVersionDelete(versionDelete, deleteObj);
+ d.files.append(versionDelete);
+ }
+ }
+
+ if (obj.contains("folders")) {
+ auto folders = Json::requireArray(obj, "folders");
+ for (const auto deleteRaw : folders) {
+ auto deleteObj = Json::requireObject(deleteRaw);
+ ATLauncher::VersionDelete versionDelete;
+ loadVersionDelete(versionDelete, deleteObj);
+ d.folders.append(versionDelete);
+ }
+ }
+}
+
void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj)
{
v.version = Json::requireString(obj, "version");
@@ -284,4 +342,10 @@ void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj)
auto messages = Json::ensureObject(obj, "messages");
loadVersionMessages(v.messages, messages);
+
+ auto keeps = Json::ensureObject(obj, "keeps");
+ loadVersionKeeps(v.keeps, keeps);
+
+ auto deletes = Json::ensureObject(obj, "deletes");
+ loadVersionDeletes(v.deletes, deletes);
}
diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.h b/launcher/modplatform/atlauncher/ATLPackManifest.h
index 43510c50..571c976d 100644
--- a/launcher/modplatform/atlauncher/ATLPackManifest.h
+++ b/launcher/modplatform/atlauncher/ATLPackManifest.h
@@ -150,6 +150,26 @@ struct VersionMessages
QString update;
};
+struct VersionKeep {
+ QString base;
+ QString target;
+};
+
+struct VersionKeeps {
+ QVector<VersionKeep> files;
+ QVector<VersionKeep> folders;
+};
+
+struct VersionDelete {
+ QString base;
+ QString target;
+};
+
+struct VersionDeletes {
+ QVector<VersionDelete> files;
+ QVector<VersionDelete> folders;
+};
+
struct PackVersionMainClass
{
QString mainClass;
@@ -178,6 +198,9 @@ struct PackVersion
QMap<QString, QString> colours;
QMap<QString, QString> warnings;
VersionMessages messages;
+
+ VersionKeeps keeps;
+ VersionDeletes deletes;
};
void loadVersion(PackVersion & v, QJsonObject & obj);
diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp
index 16013070..3c15667c 100644
--- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp
+++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp
@@ -48,7 +48,7 @@
#include "Application.h"
#include "BuildConfig.h"
-#include "ui/dialogs/ScrollMessageBox.h"
+#include "ui/dialogs/BlockedModsDialog.h"
namespace ModpacksCH {
@@ -173,6 +173,7 @@ void PackInstallTask::onResolveModsSucceeded()
m_abortable = false;
QString text;
+ QList<QUrl> urls;
auto anyBlocked = false;
Flame::Manifest results = m_mod_id_resolver_task->getResults();
@@ -190,6 +191,7 @@ void PackInstallTask::onResolveModsSucceeded()
type[0] = type[0].toUpper();
text += QString("%1: %2 - <a href='%3'>%3</a><br/>").arg(type, local_file.name, results_file.websiteUrl);
+ urls.append(QUrl(results_file.websiteUrl));
anyBlocked = true;
} else {
local_file.url = results_file.url.toString();
@@ -201,10 +203,11 @@ void PackInstallTask::onResolveModsSucceeded()
if (anyBlocked) {
qDebug() << "Blocked files found, displaying file list";
- auto message_dialog = new ScrollMessageBox(m_parent, tr("Blocked files found"),
+ auto message_dialog = new BlockedModsDialog(m_parent, tr("Blocked files found"),
tr("The following files are not available for download in third party launchers.<br/>"
"You will need to manually download them and add them to the instance."),
- text);
+ text,
+ urls);
if (message_dialog->exec() == QDialog::Accepted)
downloadPack();
diff --git a/launcher/net/HttpMetaCache.cpp b/launcher/net/HttpMetaCache.cpp
index 4d86c0b8..deb2780b 100644
--- a/launcher/net/HttpMetaCache.cpp
+++ b/launcher/net/HttpMetaCache.cpp
@@ -121,6 +121,14 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex
SaveEventually();
}
+ // Get rid of old entries, to prevent cache problems
+ auto current_time = QDateTime::currentSecsSinceEpoch();
+ if (entry->isExpired(current_time - ( file_last_changed / 1000 ))) {
+ qWarning() << "Removing cache entry because of old age!";
+ selected_base.entry_list.remove(resource_path);
+ return staleEntry(base, resource_path);
+ }
+
// entry passed all the checks we cared about.
entry->basePath = getBasePath(base);
return entry;
@@ -221,6 +229,8 @@ void HttpMetaCache::Load()
foo->etag = Json::ensureString(element_obj, "etag");
foo->local_changed_timestamp = Json::ensureDouble(element_obj, "last_changed_timestamp");
foo->remote_changed_timestamp = Json::ensureString(element_obj, "remote_changed_timestamp");
+ foo->current_age = Json::ensureDouble(element_obj, "current_age");
+ foo->max_age = Json::ensureDouble(element_obj, "max_age");
// presumed innocent until closer examination
foo->stale = false;
@@ -240,6 +250,8 @@ void HttpMetaCache::SaveNow()
if (m_index_file.isNull())
return;
+ qDebug() << "[HttpMetaCache]" << "Saving metacache with" << m_entries.size() << "entries";
+
QJsonObject toplevel;
Json::writeString(toplevel, "version", "1");
@@ -259,6 +271,8 @@ void HttpMetaCache::SaveNow()
entryObj.insert("last_changed_timestamp", QJsonValue(double(entry->local_changed_timestamp)));
if (!entry->remote_changed_timestamp.isEmpty())
entryObj.insert("remote_changed_timestamp", QJsonValue(entry->remote_changed_timestamp));
+ entryObj.insert("current_age", QJsonValue(double(entry->current_age)));
+ entryObj.insert("max_age", QJsonValue(double(entry->max_age)));
entriesArr.append(entryObj);
}
}
diff --git a/launcher/net/HttpMetaCache.h b/launcher/net/HttpMetaCache.h
index e944b3d5..df3549e8 100644
--- a/launcher/net/HttpMetaCache.h
+++ b/launcher/net/HttpMetaCache.h
@@ -64,6 +64,14 @@ class MetaEntry {
auto getMD5Sum() -> QString { return md5sum; }
void setMD5Sum(QString md5sum) { this->md5sum = md5sum; }
+ auto getCurrentAge() -> qint64 { return current_age; }
+ void setCurrentAge(qint64 age) { current_age = age; }
+
+ auto getMaximumAge() -> qint64 { return max_age; }
+ void setMaximumAge(qint64 age) { max_age = age; }
+
+ bool isExpired(qint64 offset) { return current_age >= max_age - offset; };
+
protected:
QString baseId;
QString basePath;
@@ -72,6 +80,8 @@ class MetaEntry {
QString etag;
qint64 local_changed_timestamp = 0;
QString remote_changed_timestamp; // QString for now, RFC 2822 encoded time
+ qint64 current_age = 0;
+ qint64 max_age = 0;
bool stale = true;
};
diff --git a/launcher/net/MetaCacheSink.cpp b/launcher/net/MetaCacheSink.cpp
index f86dd870..ab0c9fcb 100644
--- a/launcher/net/MetaCacheSink.cpp
+++ b/launcher/net/MetaCacheSink.cpp
@@ -36,11 +36,16 @@
#include "MetaCacheSink.h"
#include <QFile>
#include <QFileInfo>
-#include "FileSystem.h"
#include "Application.h"
namespace Net {
+/** Maximum time to hold a cache entry
+ * = 1 week in seconds
+ */
+#define MAX_TIME_TO_EXPIRE 1*7*24*60*60
+
+
MetaCacheSink::MetaCacheSink(MetaEntryPtr entry, ChecksumValidator * md5sum)
:Net::FileSink(entry->getFullPath()), m_entry(entry), m_md5Node(md5sum)
{
@@ -88,6 +93,37 @@ Task::State MetaCacheSink::finalizeCache(QNetworkReply & reply)
}
m_entry->setLocalChangedTimestamp(output_file_info.lastModified().toUTC().toMSecsSinceEpoch());
+
+ { // Cache lifetime
+ if (reply.hasRawHeader("Cache-Control")) {
+ auto cache_control_header = reply.rawHeader("Cache-Control");
+ // qDebug() << "[MetaCache] Parsing 'Cache-Control' header with" << cache_control_header;
+
+ QRegularExpression max_age_expr("max-age=([0-9]+)");
+ qint64 max_age = max_age_expr.match(cache_control_header).captured(1).toLongLong();
+ m_entry->setMaximumAge(max_age);
+
+ } else if (reply.hasRawHeader("Expires")) {
+ auto expires_header = reply.rawHeader("Expires");
+ // qDebug() << "[MetaCache] Parsing 'Expires' header with" << expires_header;
+
+ qint64 max_age = QDateTime::fromString(expires_header).toSecsSinceEpoch() - QDateTime::currentSecsSinceEpoch();
+ m_entry->setMaximumAge(max_age);
+ } else {
+ m_entry->setMaximumAge(MAX_TIME_TO_EXPIRE);
+ }
+
+ if (reply.hasRawHeader("Age")) {
+ auto age_header = reply.rawHeader("Age");
+ // qDebug() << "[MetaCache] Parsing 'Age' header with" << age_header;
+
+ qint64 current_age = age_header.toLongLong();
+ m_entry->setCurrentAge(current_age);
+ } else {
+ m_entry->setCurrentAge(0);
+ }
+ }
+
m_entry->setStale(false);
APPLICATION->metacache()->updateEntry(m_entry);
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index c3d95599..299401f5 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -1465,6 +1465,7 @@ void MainWindow::updateNewsLabel()
{
newsLabel->setText(tr("Loading news..."));
newsLabel->setEnabled(false);
+ ui->actionMoreNews->setVisible(false);
}
else
{
@@ -1473,11 +1474,13 @@ void MainWindow::updateNewsLabel()
{
newsLabel->setText(entries[0]->title);
newsLabel->setEnabled(true);
+ ui->actionMoreNews->setVisible(true);
}
else
{
newsLabel->setText(tr("No news available."));
newsLabel->setEnabled(false);
+ ui->actionMoreNews->setVisible(false);
}
}
}
diff --git a/launcher/ui/dialogs/AboutDialog.cpp b/launcher/ui/dialogs/AboutDialog.cpp
index c5367d5b..743c34f1 100644
--- a/launcher/ui/dialogs/AboutDialog.cpp
+++ b/launcher/ui/dialogs/AboutDialog.cpp
@@ -147,10 +147,15 @@ AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDia
else
ui->platformLabel->setVisible(false);
- if (BuildConfig.VERSION_BUILD >= 0)
- ui->buildNumLabel->setText(tr("Build Number") +": " + QString::number(BuildConfig.VERSION_BUILD));
+ if (!BuildConfig.GIT_COMMIT.isEmpty())
+ ui->commitLabel->setText(tr("Commit: %1").arg(BuildConfig.GIT_COMMIT));
else
- ui->buildNumLabel->setVisible(false);
+ ui->commitLabel->setVisible(false);
+
+ if (!BuildConfig.BUILD_DATE.isEmpty())
+ ui->buildDateLabel->setText(tr("Build date: %1").arg(BuildConfig.BUILD_DATE));
+ else
+ ui->buildDateLabel->setVisible(false);
if (!BuildConfig.VERSION_CHANNEL.isEmpty())
ui->channelLabel->setText(tr("Channel") +": " + BuildConfig.VERSION_CHANNEL);
diff --git a/launcher/ui/dialogs/AboutDialog.ui b/launcher/ui/dialogs/AboutDialog.ui
index 6323992b..6eaa0c4e 100644
--- a/launcher/ui/dialogs/AboutDialog.ui
+++ b/launcher/ui/dialogs/AboutDialog.ui
@@ -184,12 +184,28 @@
</widget>
</item>
<item>
- <widget class="QLabel" name="buildNumLabel">
+ <widget class="QLabel" name="buildDateLabel">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
</property>
<property name="text">
- <string>Build Number:</string>
+ <string>Build Date:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="textInteractionFlags">
+ <set>Qt::TextSelectableByMouse</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="commitLabel">
+ <property name="cursor">
+ <cursorShape>IBeamCursor</cursorShape>
+ </property>
+ <property name="text">
+ <string>Commit:</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp
new file mode 100644
index 00000000..fe87b517
--- /dev/null
+++ b/launcher/ui/dialogs/BlockedModsDialog.cpp
@@ -0,0 +1,28 @@
+#include "BlockedModsDialog.h"
+#include "ui_BlockedModsDialog.h"
+#include <QPushButton>
+#include <QDialogButtonBox>
+#include <QDesktopServices>
+
+
+BlockedModsDialog::BlockedModsDialog(QWidget *parent, const QString &title, const QString &text, const QString &body, const QList<QUrl> &urls) :
+ QDialog(parent), ui(new Ui::BlockedModsDialog), urls(urls) {
+ ui->setupUi(this);
+
+ auto openAllButton = ui->buttonBox->addButton(tr("Open All"), QDialogButtonBox::ActionRole);
+ connect(openAllButton, &QPushButton::clicked, this, &BlockedModsDialog::openAll);
+
+ this->setWindowTitle(title);
+ ui->label->setText(text);
+ ui->textBrowser->setText(body);
+}
+
+BlockedModsDialog::~BlockedModsDialog() {
+ delete ui;
+}
+
+void BlockedModsDialog::openAll() {
+ for(auto &url : urls) {
+ QDesktopServices::openUrl(url);
+ }
+}
diff --git a/launcher/ui/dialogs/BlockedModsDialog.h b/launcher/ui/dialogs/BlockedModsDialog.h
new file mode 100644
index 00000000..5f5bd61b
--- /dev/null
+++ b/launcher/ui/dialogs/BlockedModsDialog.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <QDialog>
+
+
+QT_BEGIN_NAMESPACE
+namespace Ui { class BlockedModsDialog; }
+QT_END_NAMESPACE
+
+class BlockedModsDialog : public QDialog {
+Q_OBJECT
+
+public:
+ BlockedModsDialog(QWidget *parent, const QString &title, const QString &text, const QString &body, const QList<QUrl> &urls);
+
+ ~BlockedModsDialog() override;
+
+private:
+ Ui::BlockedModsDialog *ui;
+ const QList<QUrl> &urls;
+ void openAll();
+};
diff --git a/launcher/ui/dialogs/BlockedModsDialog.ui b/launcher/ui/dialogs/BlockedModsDialog.ui
new file mode 100644
index 00000000..f4ae95b6
--- /dev/null
+++ b/launcher/ui/dialogs/BlockedModsDialog.ui
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>BlockedModsDialog</class>
+ <widget class="QDialog" name="BlockedModsDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>455</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string notr="true">BlockedModsDialog</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string notr="true"/>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::RichText</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QTextBrowser" name="textBrowser">
+ <property name="acceptRichText">
+ <bool>true</bool>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>BlockedModsDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>199</x>
+ <y>425</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>199</x>
+ <y>227</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>BlockedModsDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>199</x>
+ <y>425</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>199</x>
+ <y>227</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp
index 69c20309..39fbe3e2 100644
--- a/launcher/ui/pages/instance/ExternalResourcesPage.cpp
+++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp
@@ -101,7 +101,7 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared
{
ui->setupUi(this);
- runningStateChanged(m_instance && m_instance->isRunning());
+ ExternalResourcesPage::runningStateChanged(m_instance && m_instance->isRunning());
ui->actionsToolbar->insertSpacer(ui->actionViewConfigs);
diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.h b/launcher/ui/pages/instance/ExternalResourcesPage.h
index 41237139..ff294678 100644
--- a/launcher/ui/pages/instance/ExternalResourcesPage.h
+++ b/launcher/ui/pages/instance/ExternalResourcesPage.h
@@ -46,7 +46,7 @@ class ExternalResourcesPage : public QMainWindow, public BasePage {
protected slots:
void itemActivated(const QModelIndex& index);
void filterTextChanged(const QString& newContents);
- void runningStateChanged(bool running);
+ virtual void runningStateChanged(bool running);
virtual void addItem();
virtual void removeItem();
diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp
index 14e1f1e5..1b2cde0c 100644
--- a/launcher/ui/pages/instance/ModFolderPage.cpp
+++ b/launcher/ui/pages/instance/ModFolderPage.cpp
@@ -84,18 +84,31 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel>
ui->actionsToolbar->insertActionAfter(ui->actionAddItem, ui->actionUpdateItem);
connect(ui->actionUpdateItem, &QAction::triggered, this, &ModFolderPage::updateMods);
- connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this,
- [this] { ui->actionUpdateItem->setEnabled(ui->treeView->selectionModel()->hasSelection() || !m_model->empty()); });
+ auto check_allow_update = [this] {
+ return (!m_instance || !m_instance->isRunning()) &&
+ (ui->treeView->selectionModel()->hasSelection() || !m_model->empty());
+ };
- connect(mods.get(), &ModFolderModel::rowsInserted, this,
- [this] { ui->actionUpdateItem->setEnabled(ui->treeView->selectionModel()->hasSelection() || !m_model->empty()); });
-
- connect(mods.get(), &ModFolderModel::updateFinished, this, [this, mods] {
- ui->actionUpdateItem->setEnabled(ui->treeView->selectionModel()->hasSelection() || !m_model->empty());
+ connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this, check_allow_update] {
+ ui->actionUpdateItem->setEnabled(check_allow_update());
+ });
+
+ connect(mods.get(), &ModFolderModel::rowsInserted, this, [this, check_allow_update] {
+ ui->actionUpdateItem->setEnabled(check_allow_update());
+ });
+
+ connect(mods.get(), &ModFolderModel::rowsRemoved, this, [this, check_allow_update] {
+ ui->actionUpdateItem->setEnabled(check_allow_update());
+ });
+
+ connect(mods.get(), &ModFolderModel::updateFinished, this, [this, check_allow_update, mods] {
+ ui->actionUpdateItem->setEnabled(check_allow_update());
// Prevent a weird crash when trying to open the mods page twice in a session o.O
disconnect(mods.get(), &ModFolderModel::updateFinished, this, 0);
});
+
+ ModFolderPage::runningStateChanged(m_instance && m_instance->isRunning());
}
}
@@ -103,6 +116,13 @@ CoreModFolderPage::CoreModFolderPage(BaseInstance* inst, std::shared_ptr<ModFold
: ModFolderPage(inst, mods, parent)
{}
+void ModFolderPage::runningStateChanged(bool running)
+{
+ ExternalResourcesPage::runningStateChanged(running);
+ ui->actionDownloadItem->setEnabled(!running);
+ ui->actionUpdateItem->setEnabled(!running);
+}
+
bool ModFolderPage::shouldDisplay() const
{
return true;
diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h
index 0a7fc9fa..93889707 100644
--- a/launcher/ui/pages/instance/ModFolderPage.h
+++ b/launcher/ui/pages/instance/ModFolderPage.h
@@ -53,6 +53,7 @@ class ModFolderPage : public ExternalResourcesPage {
virtual QString helpPage() const override { return "Loader-mods"; }
virtual bool shouldDisplay() const override;
+ void runningStateChanged(bool running) override;
private slots:
void installMods();
diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp
index 647b04a7..85cc01ff 100644
--- a/launcher/ui/pages/instance/WorldListPage.cpp
+++ b/launcher/ui/pages/instance/WorldListPage.cpp
@@ -211,7 +211,7 @@ void WorldListPage::on_actionDatapacks_triggered()
return;
}
- if(!worldSafetyNagQuestion())
+ if(!worldSafetyNagQuestion(tr("Open World Datapacks Folder")))
return;
auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString();
@@ -269,7 +269,7 @@ void WorldListPage::on_actionMCEdit_triggered()
return;
}
- if(!worldSafetyNagQuestion())
+ if(!worldSafetyNagQuestion(tr("Open World in MCEdit")))
return;
auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString();
@@ -373,11 +373,11 @@ bool WorldListPage::isWorldSafe(QModelIndex)
return !m_inst->isRunning();
}
-bool WorldListPage::worldSafetyNagQuestion()
+bool WorldListPage::worldSafetyNagQuestion(const QString &actionType)
{
if(!isWorldSafe(getSelectedWorld()))
{
- auto result = QMessageBox::question(this, tr("Copy World"), tr("Changing a world while Minecraft is running is potentially unsafe.\nDo you wish to proceed?"));
+ auto result = QMessageBox::question(this, actionType, tr("Changing a world while Minecraft is running is potentially unsafe.\nDo you wish to proceed?"));
if(result == QMessageBox::No)
{
return false;
@@ -395,7 +395,7 @@ void WorldListPage::on_actionCopy_triggered()
return;
}
- if(!worldSafetyNagQuestion())
+ if(!worldSafetyNagQuestion(tr("Copy World")))
return;
auto worldVariant = m_worlds->data(index, WorldList::ObjectRole);
@@ -417,7 +417,7 @@ void WorldListPage::on_actionRename_triggered()
return;
}
- if(!worldSafetyNagQuestion())
+ if(!worldSafetyNagQuestion(tr("Rename World")))
return;
auto worldVariant = m_worlds->data(index, WorldList::ObjectRole);
diff --git a/launcher/ui/pages/instance/WorldListPage.h b/launcher/ui/pages/instance/WorldListPage.h
index 17e36a08..1dc9e53e 100644
--- a/launcher/ui/pages/instance/WorldListPage.h
+++ b/launcher/ui/pages/instance/WorldListPage.h
@@ -93,7 +93,7 @@ protected:
private:
QModelIndex getSelectedWorld();
bool isWorldSafe(QModelIndex index);
- bool worldSafetyNagQuestion();
+ bool worldSafetyNagQuestion(const QString &actionType);
void mceditError();
private:
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp
index 8de5211c..7901b90b 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp
@@ -37,13 +37,12 @@
#include "AtlPage.h"
#include "ui_AtlPage.h"
-#include "modplatform/atlauncher/ATLPackInstallTask.h"
+#include "BuildConfig.h"
#include "AtlOptionalModDialog.h"
+#include "AtlUserInteractionSupportImpl.h"
+#include "modplatform/atlauncher/ATLPackInstallTask.h"
#include "ui/dialogs/NewInstanceDialog.h"
-#include "ui/dialogs/VersionSelectDialog.h"
-
-#include <BuildConfig.h>
#include <QMessageBox>
@@ -117,7 +116,9 @@ void AtlPage::suggestCurrent()
return;
}
- dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ATLauncher::PackInstallTask(this, selected.name, selectedVersion));
+ auto uiSupport = new AtlUserInteractionSupportImpl(this);
+ dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ATLauncher::PackInstallTask(uiSupport, selected.name, selectedVersion));
+
auto editedLogoName = selected.safeName;
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(selected.safeName.toLower());
listModel->getLogo(selected.safeName, url, [this, editedLogoName](QString logo)
@@ -172,51 +173,3 @@ void AtlPage::onVersionSelectionChanged(QString data)
selectedVersion = data;
suggestCurrent();
}
-
-QVector<QString> AtlPage::chooseOptionalMods(ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods)
-{
- AtlOptionalModDialog optionalModDialog(this, version, mods);
- optionalModDialog.exec();
- return optionalModDialog.getResult();
-}
-
-QString AtlPage::chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) {
- VersionSelectDialog vselect(vlist.get(), "Choose Version", APPLICATION->activeWindow(), false);
- if (minecraftVersion != Q_NULLPTR) {
- vselect.setExactFilter(BaseVersionList::ParentVersionRole, minecraftVersion);
- vselect.setEmptyString(tr("No versions are currently available for Minecraft %1").arg(minecraftVersion));
- }
- else {
- vselect.setEmptyString(tr("No versions are currently available"));
- }
- vselect.setEmptyErrorString(tr("Couldn't load or download the version lists!"));
-
- // select recommended build
- for (int i = 0; i < vlist->versions().size(); i++) {
- auto version = vlist->versions().at(i);
- auto reqs = version->requires();
-
- // filter by minecraft version, if the loader depends on a certain version.
- if (minecraftVersion != Q_NULLPTR) {
- auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Meta::Require &req) {
- return req.uid == "net.minecraft";
- });
- if (iter == reqs.end()) continue;
- if (iter->equalsVersion != minecraftVersion) continue;
- }
-
- // first recommended build we find, we use.
- if (version->isRecommended()) {
- vselect.setCurrentVersion(version->descriptor());
- break;
- }
- }
-
- vselect.exec();
- return vselect.selectedVersion()->descriptor();
-}
-
-void AtlPage::displayMessage(QString message)
-{
- QMessageBox::information(this, tr("Installing"), message);
-}
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.h b/launcher/ui/pages/modplatform/atlauncher/AtlPage.h
index aa6d5da1..1b3b15c1 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.h
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.h
@@ -52,7 +52,7 @@ namespace Ui
class NewInstanceDialog;
-class AtlPage : public QWidget, public BasePage, public ATLauncher::UserInteractionSupport
+class AtlPage : public QWidget, public BasePage
{
Q_OBJECT
@@ -83,10 +83,6 @@ public:
private:
void suggestCurrent();
- QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) override;
- QVector<QString> chooseOptionalMods(ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods) override;
- void displayMessage(QString message) override;
-
private slots:
void triggerSearch();
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp
new file mode 100644
index 00000000..03196685
--- /dev/null
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
+ *
+ * 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 <QMessageBox>
+#include "AtlUserInteractionSupportImpl.h"
+
+#include "AtlOptionalModDialog.h"
+#include "ui/dialogs/VersionSelectDialog.h"
+
+AtlUserInteractionSupportImpl::AtlUserInteractionSupportImpl(QWidget *parent) : m_parent(parent)
+{
+}
+
+QVector<QString> AtlUserInteractionSupportImpl::chooseOptionalMods(ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods)
+{
+ AtlOptionalModDialog optionalModDialog(m_parent, version, mods);
+ optionalModDialog.exec();
+ return optionalModDialog.getResult();
+}
+
+QString AtlUserInteractionSupportImpl::chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion)
+{
+ VersionSelectDialog vselect(vlist.get(), "Choose Version", m_parent, false);
+ if (minecraftVersion != nullptr) {
+ vselect.setExactFilter(BaseVersionList::ParentVersionRole, minecraftVersion);
+ vselect.setEmptyString(tr("No versions are currently available for Minecraft %1").arg(minecraftVersion));
+ }
+ else {
+ vselect.setEmptyString(tr("No versions are currently available"));
+ }
+ vselect.setEmptyErrorString(tr("Couldn't load or download the version lists!"));
+
+ // select recommended build
+ for (int i = 0; i < vlist->versions().size(); i++) {
+ auto version = vlist->versions().at(i);
+ auto reqs = version->requires();
+
+ // filter by minecraft version, if the loader depends on a certain version.
+ if (minecraftVersion != nullptr) {
+ auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Meta::Require& req) {
+ return req.uid == "net.minecraft";
+ });
+ if (iter == reqs.end())
+ continue;
+ if (iter->equalsVersion != minecraftVersion)
+ continue;
+ }
+
+ // first recommended build we find, we use.
+ if (version->isRecommended()) {
+ vselect.setCurrentVersion(version->descriptor());
+ break;
+ }
+ }
+
+ vselect.exec();
+ return vselect.selectedVersion()->descriptor();
+}
+
+void AtlUserInteractionSupportImpl::displayMessage(QString message)
+{
+ QMessageBox::information(m_parent, tr("Installing"), message);
+}
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h
new file mode 100644
index 00000000..aa22fc73
--- /dev/null
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
+ *
+ * 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 <QObject>
+
+#include "modplatform/atlauncher/ATLPackInstallTask.h"
+
+class AtlUserInteractionSupportImpl : public QObject, public ATLauncher::UserInteractionSupport {
+ Q_OBJECT
+
+public:
+ AtlUserInteractionSupportImpl(QWidget* parent);
+
+private:
+ QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) override;
+ QVector<QString> chooseOptionalMods(ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods) override;
+ void displayMessage(QString message) override;
+
+private:
+ QWidget* m_parent;
+
+};
diff --git a/launcher/updater/UpdateChecker.cpp b/launcher/updater/UpdateChecker.cpp
index fa6e5a97..78d979ff 100644
--- a/launcher/updater/UpdateChecker.cpp
+++ b/launcher/updater/UpdateChecker.cpp
@@ -25,12 +25,11 @@
#include "BuildConfig.h"
-UpdateChecker::UpdateChecker(shared_qobject_ptr<QNetworkAccessManager> nam, QString channelUrl, QString currentChannel, int currentBuild)
+UpdateChecker::UpdateChecker(shared_qobject_ptr<QNetworkAccessManager> nam, QString channelUrl, QString currentChannel)
{
m_network = nam;
m_channelUrl = channelUrl;
m_currentChannel = currentChannel;
- m_currentBuild = currentBuild;
#ifdef Q_OS_MAC
m_externalUpdater = new MacSparkleUpdater();
diff --git a/launcher/updater/UpdateChecker.h b/launcher/updater/UpdateChecker.h
index 94e4312b..42ef318b 100644
--- a/launcher/updater/UpdateChecker.h
+++ b/launcher/updater/UpdateChecker.h
@@ -28,7 +28,7 @@ class UpdateChecker : public QObject
Q_OBJECT
public:
- UpdateChecker(shared_qobject_ptr<QNetworkAccessManager> nam, QString channelUrl, QString currentChannel, int currentBuild);
+ UpdateChecker(shared_qobject_ptr<QNetworkAccessManager> nam, QString channelUrl, QString currentChannel);
void checkForUpdate(const QString& updateChannel, bool notifyNoUpdate);
/*!