Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into skin_selector
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
@ -122,6 +122,7 @@
|
||||
#include <FileSystem.h>
|
||||
#include <LocalPeer.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <sys.h>
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
@ -130,9 +131,13 @@
|
||||
#include "gamemode_client.h"
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_MAC) && defined(SPARKLE_ENABLED)
|
||||
#if defined(Q_OS_MAC)
|
||||
#if defined(SPARKLE_ENABLED)
|
||||
#include "updater/MacSparkleUpdater.h"
|
||||
#endif
|
||||
#else
|
||||
#include "updater/PrismExternalUpdater.h"
|
||||
#endif
|
||||
|
||||
#if defined Q_OS_WIN32
|
||||
#include "WindowsConsole.h"
|
||||
@ -164,6 +169,34 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext& context, const QSt
|
||||
|
||||
} // namespace
|
||||
|
||||
std::tuple<QDateTime, QString, QString, QString, QString> read_lock_File(const QString& path)
|
||||
{
|
||||
auto contents = QString(FS::read(path));
|
||||
auto lines = contents.split('\n');
|
||||
|
||||
QDateTime timestamp;
|
||||
QString from, to, target, data_path;
|
||||
for (auto line : lines) {
|
||||
auto index = line.indexOf("=");
|
||||
if (index < 0)
|
||||
continue;
|
||||
auto left = line.left(index);
|
||||
auto right = line.mid(index + 1);
|
||||
if (left.toLower() == "timestamp") {
|
||||
timestamp = QDateTime::fromString(right, Qt::ISODate);
|
||||
} else if (left.toLower() == "from") {
|
||||
from = right;
|
||||
} else if (left.toLower() == "to") {
|
||||
to = right;
|
||||
} else if (left.toLower() == "target") {
|
||||
target = right;
|
||||
} else if (left.toLower() == "data_path") {
|
||||
data_path = right;
|
||||
}
|
||||
}
|
||||
return std::make_tuple(timestamp, from, to, target, data_path);
|
||||
}
|
||||
|
||||
Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||
{
|
||||
#if defined Q_OS_WIN32
|
||||
@ -296,6 +329,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||
.arg(dataPath));
|
||||
return;
|
||||
}
|
||||
m_dataPath = dataPath;
|
||||
|
||||
/*
|
||||
* Establish the mechanism for communication with an already running PrismLauncher that uses the same data path.
|
||||
@ -450,11 +484,16 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||
}
|
||||
|
||||
{
|
||||
qDebug() << BuildConfig.LAUNCHER_DISPLAYNAME << ", (c) 2013-2021 " << BuildConfig.LAUNCHER_COPYRIGHT;
|
||||
qDebug() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME) << ", (c) 2022-2023 "
|
||||
<< qPrintable(QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", "));
|
||||
qDebug() << "Version : " << BuildConfig.printableVersionString();
|
||||
qDebug() << "Platform : " << BuildConfig.BUILD_PLATFORM;
|
||||
qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT;
|
||||
qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC;
|
||||
qDebug() << "Compiled for : " << BuildConfig.systemID();
|
||||
qDebug() << "Compiled by : " << BuildConfig.compilerID();
|
||||
qDebug() << "Build Artifact : " << BuildConfig.BUILD_ARTIFACT;
|
||||
qDebug() << "Updates Enabled : " << (updaterEnabled() ? "Yes" : "No");
|
||||
if (adjustedBy.size()) {
|
||||
qDebug() << "Work dir before adjustment : " << origcwdPath;
|
||||
qDebug() << "Work dir after adjustment : " << QDir::currentPath();
|
||||
@ -583,6 +622,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||
m_settings->registerSetting("IgnoreJavaCompatibility", false);
|
||||
m_settings->registerSetting("IgnoreJavaWizard", false);
|
||||
|
||||
// Legacy settings
|
||||
m_settings->registerSetting("OnlineFixes", false);
|
||||
|
||||
// Native library workarounds
|
||||
m_settings->registerSetting("UseNativeOpenAL", false);
|
||||
m_settings->registerSetting("CustomOpenALPath", "");
|
||||
@ -739,15 +781,6 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||
qDebug() << "<> Translations loaded.";
|
||||
}
|
||||
|
||||
// initialize the updater
|
||||
if (BuildConfig.UPDATER_ENABLED) {
|
||||
qDebug() << "Initializing updater";
|
||||
#if defined(Q_OS_MAC) && defined(SPARKLE_ENABLED)
|
||||
m_updater.reset(new MacSparkleUpdater());
|
||||
#endif
|
||||
qDebug() << "<> Updater started.";
|
||||
}
|
||||
|
||||
// Instance icons
|
||||
{
|
||||
auto setting = APPLICATION->settings()->getSetting("IconsDir");
|
||||
@ -850,6 +883,107 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||
|
||||
detectLibraries();
|
||||
|
||||
// check update locks
|
||||
{
|
||||
auto update_log_path = FS::PathCombine(m_dataPath, "logs", "prism_launcher_update.log");
|
||||
|
||||
auto update_lock = QFileInfo(FS::PathCombine(m_dataPath, ".prism_launcher_update.lock"));
|
||||
if (update_lock.exists()) {
|
||||
auto [timestamp, from, to, target, data_path] = read_lock_File(update_lock.absoluteFilePath());
|
||||
auto infoMsg = tr("This installation has a update lock file present at: %1\n"
|
||||
"\n"
|
||||
"Timestamp: %2\n"
|
||||
"Updating from version %3 to %4\n"
|
||||
"Target install path: %5\n"
|
||||
"Data Path: %6"
|
||||
"\n"
|
||||
"This likely means that a update attempt failed. Please ensure your installation is in working order before "
|
||||
"proceeding.\n"
|
||||
"Check the Prism Launcher updater log at: \n"
|
||||
"%7\n"
|
||||
"for details on the last update attempt.\n"
|
||||
"\n"
|
||||
"To delete this lock and proceed select \"Ignore\" below.")
|
||||
.arg(update_lock.absoluteFilePath())
|
||||
.arg(timestamp.toString(Qt::ISODate), from, to, target, data_path)
|
||||
.arg(update_log_path);
|
||||
auto msgBox = QMessageBox(QMessageBox::Warning, tr("Update In Progress"), infoMsg, QMessageBox::Ignore | QMessageBox::Abort);
|
||||
msgBox.setDefaultButton(QMessageBox::Abort);
|
||||
msgBox.setModal(true);
|
||||
msgBox.setDetailedText(FS::read(update_log_path));
|
||||
msgBox.setMinimumWidth(460);
|
||||
msgBox.adjustSize();
|
||||
auto res = msgBox.exec();
|
||||
switch (res) {
|
||||
case QMessageBox::Ignore: {
|
||||
FS::deletePath(update_lock.absoluteFilePath());
|
||||
break;
|
||||
}
|
||||
case QMessageBox::Abort:
|
||||
[[fallthrough]];
|
||||
default: {
|
||||
qDebug() << "Exiting because update lockfile is present";
|
||||
QMetaObject::invokeMethod(
|
||||
this, []() { exit(1); }, Qt::QueuedConnection);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto update_fail_marker = QFileInfo(FS::PathCombine(m_dataPath, ".prism_launcher_update.fail"));
|
||||
if (update_fail_marker.exists()) {
|
||||
auto infoMsg = tr("An update attempt failed\n"
|
||||
"\n"
|
||||
"Please ensure your installation is in working order before "
|
||||
"proceeding.\n"
|
||||
"Check the Prism Launcher updater log at: \n"
|
||||
"%1\n"
|
||||
"for details on the last update attempt.")
|
||||
.arg(update_log_path);
|
||||
auto msgBox = QMessageBox(QMessageBox::Warning, tr("Update Failed"), infoMsg, QMessageBox::Ignore | QMessageBox::Abort);
|
||||
msgBox.setDefaultButton(QMessageBox::Abort);
|
||||
msgBox.setModal(true);
|
||||
msgBox.setDetailedText(FS::read(update_log_path));
|
||||
msgBox.setMinimumWidth(460);
|
||||
msgBox.adjustSize();
|
||||
auto res = msgBox.exec();
|
||||
switch (res) {
|
||||
case QMessageBox::Ignore: {
|
||||
FS::deletePath(update_fail_marker.absoluteFilePath());
|
||||
break;
|
||||
}
|
||||
case QMessageBox::Abort:
|
||||
[[fallthrough]];
|
||||
default: {
|
||||
qDebug() << "Exiting because update lockfile is present";
|
||||
QMetaObject::invokeMethod(
|
||||
this, []() { exit(1); }, Qt::QueuedConnection);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto update_success_marker = QFileInfo(FS::PathCombine(m_dataPath, ".prism_launcher_update.success"));
|
||||
if (update_success_marker.exists()) {
|
||||
auto infoMsg = tr("Update succeeded\n"
|
||||
"\n"
|
||||
"You are now running %1 .\n"
|
||||
"Check the Prism Launcher updater log at: \n"
|
||||
"%1\n"
|
||||
"for details.")
|
||||
.arg(BuildConfig.printableVersionString())
|
||||
.arg(update_log_path);
|
||||
auto msgBox = new QMessageBox(QMessageBox::Information, tr("Update Succeeded"), infoMsg, QMessageBox::Ok);
|
||||
msgBox->setDefaultButton(QMessageBox::Ok);
|
||||
msgBox->setDetailedText(FS::read(update_log_path));
|
||||
msgBox->setAttribute(Qt::WA_DeleteOnClose);
|
||||
msgBox->setMinimumWidth(460);
|
||||
msgBox->adjustSize();
|
||||
msgBox->open();
|
||||
FS::deletePath(update_success_marker.absoluteFilePath());
|
||||
}
|
||||
}
|
||||
|
||||
if (createSetupWizard()) {
|
||||
return;
|
||||
}
|
||||
@ -918,6 +1052,26 @@ bool Application::createSetupWizard()
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Application::updaterEnabled()
|
||||
{
|
||||
#if defined(Q_OS_MAC)
|
||||
return BuildConfig.UPDATER_ENABLED;
|
||||
#else
|
||||
return BuildConfig.UPDATER_ENABLED && QFileInfo(FS::PathCombine(m_rootPath, updaterBinaryName())).isFile();
|
||||
#endif
|
||||
}
|
||||
|
||||
QString Application::updaterBinaryName()
|
||||
{
|
||||
auto exe_name = QStringLiteral("%1_updater").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME);
|
||||
#if defined Q_OS_WIN32
|
||||
exe_name.append(".exe");
|
||||
#else
|
||||
exe_name.prepend("bin/");
|
||||
#endif
|
||||
return exe_name;
|
||||
}
|
||||
|
||||
bool Application::event(QEvent* event)
|
||||
{
|
||||
#ifdef Q_OS_MACOS
|
||||
@ -986,6 +1140,20 @@ void Application::performMainStartupAction()
|
||||
showMainWindow(false);
|
||||
qDebug() << "<> Main window shown.";
|
||||
}
|
||||
|
||||
// initialize the updater
|
||||
if (updaterEnabled()) {
|
||||
qDebug() << "Initializing updater";
|
||||
#ifdef Q_OS_MAC
|
||||
#if defined(SPARKLE_ENABLED)
|
||||
m_updater.reset(new MacSparkleUpdater());
|
||||
#endif
|
||||
#else
|
||||
m_updater.reset(new PrismExternalUpdater(m_mainWindow, m_rootPath, m_dataPath));
|
||||
#endif
|
||||
qDebug() << "<> Updater started.";
|
||||
}
|
||||
|
||||
if (!m_urlsToImport.isEmpty()) {
|
||||
qDebug() << "<> Importing from url:" << m_urlsToImport;
|
||||
m_mainWindow->processURLs(m_urlsToImport);
|
||||
|
@ -159,6 +159,9 @@ class Application : public QApplication {
|
||||
/// this is the root of the 'installation'. Used for automatic updates
|
||||
const QString& root() { return m_rootPath; }
|
||||
|
||||
/// the data path the application is using
|
||||
const QString& dataRoot() { return m_dataPath; }
|
||||
|
||||
bool isPortable() { return m_portable; }
|
||||
|
||||
const Capabilities capabilities() { return m_capabilities; }
|
||||
@ -179,6 +182,9 @@ class Application : public QApplication {
|
||||
|
||||
int suitableMaxMem();
|
||||
|
||||
bool updaterEnabled();
|
||||
QString updaterBinaryName();
|
||||
|
||||
QUrl normalizeImportUrl(QString const& url);
|
||||
|
||||
signals:
|
||||
@ -244,6 +250,7 @@ class Application : public QApplication {
|
||||
QMap<QString, std::shared_ptr<BaseProfilerFactory>> m_profilers;
|
||||
|
||||
QString m_rootPath;
|
||||
QString m_dataPath;
|
||||
Status m_status = Application::StartingUp;
|
||||
Capabilities m_capabilities;
|
||||
bool m_portable = false;
|
||||
|
@ -182,6 +182,11 @@ set(MAC_UPDATE_SOURCES
|
||||
updater/MacSparkleUpdater.mm
|
||||
)
|
||||
|
||||
set(PRISM_UPDATE_SOURCES
|
||||
updater/PrismExternalUpdater.h
|
||||
updater/PrismExternalUpdater.cpp
|
||||
)
|
||||
|
||||
# Backend for the news bar... there's usually no news.
|
||||
set(NEWS_SOURCES
|
||||
# News System
|
||||
@ -584,6 +589,63 @@ set(LINKEXE_SOURCES
|
||||
DesktopServices.cpp
|
||||
)
|
||||
|
||||
set(PRISMUPDATER_SOURCES
|
||||
updater/prismupdater/PrismUpdater.h
|
||||
updater/prismupdater/PrismUpdater.cpp
|
||||
updater/prismupdater/UpdaterDialogs.h
|
||||
updater/prismupdater/UpdaterDialogs.cpp
|
||||
updater/prismupdater/GitHubRelease.h
|
||||
updater/prismupdater/GitHubRelease.cpp
|
||||
|
||||
Json.h
|
||||
Json.cpp
|
||||
FileSystem.h
|
||||
FileSystem.cpp
|
||||
StringUtils.h
|
||||
StringUtils.cpp
|
||||
DesktopServices.h
|
||||
DesktopServices.cpp
|
||||
Version.h
|
||||
Version.cpp
|
||||
Markdown.h
|
||||
Markdown.cpp
|
||||
|
||||
# Zip
|
||||
MMCZip.h
|
||||
MMCZip.cpp
|
||||
|
||||
# Time
|
||||
MMCTime.h
|
||||
MMCTime.cpp
|
||||
|
||||
net/ByteArraySink.h
|
||||
net/ChecksumValidator.h
|
||||
net/Download.cpp
|
||||
net/Download.h
|
||||
net/FileSink.cpp
|
||||
net/FileSink.h
|
||||
net/HttpMetaCache.cpp
|
||||
net/HttpMetaCache.h
|
||||
net/Logging.h
|
||||
net/Logging.cpp
|
||||
net/NetAction.h
|
||||
net/NetRequest.cpp
|
||||
net/NetRequest.h
|
||||
net/NetJob.cpp
|
||||
net/NetJob.h
|
||||
net/NetUtils.h
|
||||
net/Sink.h
|
||||
net/Validator.h
|
||||
net/HeaderProxy.h
|
||||
net/RawHeaderProxy.h
|
||||
|
||||
ui/dialogs/ProgressDialog.cpp
|
||||
ui/dialogs/ProgressDialog.h
|
||||
ui/widgets/SubTaskProgressBar.h
|
||||
ui/widgets/SubTaskProgressBar.cpp
|
||||
|
||||
)
|
||||
|
||||
######## Logging categories ########
|
||||
|
||||
ecm_qt_declare_logging_category(CORE_SOURCES
|
||||
@ -680,6 +742,8 @@ set(LOGIC_SOURCES
|
||||
|
||||
if(APPLE AND Launcher_ENABLE_UPDATER)
|
||||
set (LOGIC_SOURCES ${LOGIC_SOURCES} ${MAC_UPDATE_SOURCES})
|
||||
else()
|
||||
set (LOGIC_SOURCES ${LOGIC_SOURCES} ${PRISM_UPDATE_SOURCES})
|
||||
endif()
|
||||
|
||||
SET(LAUNCHER_SOURCES
|
||||
@ -909,6 +973,9 @@ SET(LAUNCHER_SOURCES
|
||||
ui/pages/modplatform/ImportPage.cpp
|
||||
ui/pages/modplatform/ImportPage.h
|
||||
|
||||
ui/pages/modplatform/OptionalModDialog.cpp
|
||||
ui/pages/modplatform/OptionalModDialog.h
|
||||
|
||||
ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp
|
||||
ui/pages/modplatform/modrinth/ModrinthResourceModels.h
|
||||
ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp
|
||||
@ -1034,6 +1101,15 @@ SET(LAUNCHER_SOURCES
|
||||
ui/instanceview/VisualGroup.h
|
||||
)
|
||||
|
||||
if (NOT Apple)
|
||||
set(LAUNCHER_SOURCES
|
||||
${LAUNCHER_SOURCES}
|
||||
|
||||
ui/dialogs/UpdateAvailableDialog.h
|
||||
ui/dialogs/UpdateAvailableDialog.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
set(LAUNCHER_SOURCES
|
||||
WindowsConsole.cpp
|
||||
@ -1072,6 +1148,7 @@ qt_wrap_ui(LAUNCHER_UI
|
||||
ui/pages/modplatform/legacy_ftb/Page.ui
|
||||
ui/pages/modplatform/import_ftb/ImportFTBPage.ui
|
||||
ui/pages/modplatform/ImportPage.ui
|
||||
ui/pages/modplatform/OptionalModDialog.ui
|
||||
ui/pages/modplatform/modrinth/ModrinthPage.ui
|
||||
ui/pages/modplatform/technic/TechnicPage.ui
|
||||
ui/widgets/InstanceCardWidget.ui
|
||||
@ -1104,6 +1181,14 @@ qt_wrap_ui(LAUNCHER_UI
|
||||
ui/dialogs/skins/SkinManageDialog.ui
|
||||
)
|
||||
|
||||
qt_wrap_ui(PRISM_UPDATE_UI
|
||||
ui/dialogs/UpdateAvailableDialog.ui
|
||||
)
|
||||
|
||||
if (NOT Apple)
|
||||
set (LAUNCHER_UI ${LAUNCHER_UI} ${PRISM_UPDATE_UI})
|
||||
endif()
|
||||
|
||||
qt_add_resources(LAUNCHER_RESOURCES
|
||||
resources/backgrounds/backgrounds.qrc
|
||||
resources/multimc/multimc.qrc
|
||||
@ -1120,6 +1205,12 @@ qt_add_resources(LAUNCHER_RESOURCES
|
||||
../${Launcher_Branding_LogoQRC}
|
||||
)
|
||||
|
||||
qt_wrap_ui(PRISMUPDATER_UI
|
||||
updater/prismupdater/SelectReleaseDialog.ui
|
||||
ui/widgets/SubTaskProgressBar.ui
|
||||
ui/dialogs/ProgressDialog.ui
|
||||
)
|
||||
|
||||
######## Windows resource files ########
|
||||
if(WIN32)
|
||||
set(LAUNCHER_RCS ${CMAKE_CURRENT_BINARY_DIR}/../${Launcher_Branding_WindowsRC})
|
||||
@ -1138,6 +1229,7 @@ set_project_warnings(Launcher_logic
|
||||
"${Launcher_GCC_WARNINGS}")
|
||||
target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION)
|
||||
target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION)
|
||||
target_link_libraries(Launcher_logic
|
||||
systeminfo
|
||||
Launcher_murmur2
|
||||
@ -1219,7 +1311,45 @@ install(TARGETS ${Launcher_Name}
|
||||
FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR} COMPONENT Runtime
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
if(NOT APPLE OR (DEFINED Launcher_BUILD_UPDATER AND Launcher_BUILD_UPDATER))
|
||||
# Updater
|
||||
add_library(prism_updater_logic STATIC ${PRISMUPDATER_SOURCES} ${TASKS_SOURCES} ${PRISMUPDATER_UI})
|
||||
target_include_directories(prism_updater_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(prism_updater_logic
|
||||
QuaZip::QuaZip
|
||||
${ZLIB_LIBRARIES}
|
||||
systeminfo
|
||||
BuildConfig
|
||||
ghcFilesystem::ghc_filesystem
|
||||
Qt${QT_VERSION_MAJOR}::Widgets
|
||||
Qt${QT_VERSION_MAJOR}::Core
|
||||
Qt${QT_VERSION_MAJOR}::Network
|
||||
${Launcher_QT_LIBS}
|
||||
cmark::cmark
|
||||
Katabasis
|
||||
)
|
||||
|
||||
add_executable("${Launcher_Name}_updater" WIN32 updater/prismupdater/updater_main.cpp)
|
||||
target_sources("${Launcher_Name}_updater" PRIVATE updater/prismupdater/updater.exe.manifest)
|
||||
target_link_libraries("${Launcher_Name}_updater" prism_updater_logic)
|
||||
|
||||
if(DEFINED Launcher_APP_BINARY_NAME)
|
||||
set_target_properties("${Launcher_Name}_updater" PROPERTIES OUTPUT_NAME "${Launcher_APP_BINARY_NAME}_updater")
|
||||
endif()
|
||||
if(DEFINED Launcher_BINARY_RPATH)
|
||||
SET_TARGET_PROPERTIES("${Launcher_Name}_updater" PROPERTIES INSTALL_RPATH "${Launcher_BINARY_RPATH}")
|
||||
endif()
|
||||
|
||||
install(TARGETS "${Launcher_Name}_updater"
|
||||
BUNDLE DESTINATION "." COMPONENT Runtime
|
||||
LIBRARY DESTINATION ${LIBRARY_DEST_DIR} COMPONENT Runtime
|
||||
RUNTIME DESTINATION ${BINARY_DEST_DIR} COMPONENT Runtime
|
||||
FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR} COMPONENT Runtime
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WIN32 OR (DEFINED Launcher_BUILD_FILELINKER AND Launcher_BUILD_FILELINKER))
|
||||
# File link
|
||||
add_library(filelink_logic STATIC ${LINKEXE_SOURCES})
|
||||
set_project_warnings(filelink_logic
|
||||
"${Launcher_MSVC_WARNINGS}"
|
||||
@ -1238,7 +1368,7 @@ if(WIN32)
|
||||
${Launcher_QT_LIBS}
|
||||
)
|
||||
|
||||
add_executable("${Launcher_Name}_filelink" WIN32 filelink/main.cpp)
|
||||
add_executable("${Launcher_Name}_filelink" WIN32 filelink/filelink_main.cpp)
|
||||
|
||||
target_sources("${Launcher_Name}_filelink" PRIVATE filelink/filelink.exe.manifest)
|
||||
|
||||
|
@ -194,6 +194,40 @@ void write(const QString& filename, const QByteArray& data)
|
||||
}
|
||||
}
|
||||
|
||||
void appendSafe(const QString& filename, const QByteArray& data)
|
||||
{
|
||||
ensureExists(QFileInfo(filename).dir());
|
||||
QByteArray buffer;
|
||||
try {
|
||||
buffer = read(filename);
|
||||
} catch (FileSystemException&) {
|
||||
buffer = QByteArray();
|
||||
}
|
||||
buffer.append(data);
|
||||
QSaveFile file(filename);
|
||||
if (!file.open(QSaveFile::WriteOnly)) {
|
||||
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
|
||||
}
|
||||
if (buffer.size() != file.write(buffer)) {
|
||||
throw FileSystemException("Error writing data to " + filename + ": " + file.errorString());
|
||||
}
|
||||
if (!file.commit()) {
|
||||
throw FileSystemException("Error while committing data to " + filename + ": " + file.errorString());
|
||||
}
|
||||
}
|
||||
|
||||
void append(const QString& filename, const QByteArray& data)
|
||||
{
|
||||
ensureExists(QFileInfo(filename).dir());
|
||||
QFile file(filename);
|
||||
if (!file.open(QFile::Append)) {
|
||||
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
|
||||
}
|
||||
if (data.size() != file.write(data)) {
|
||||
throw FileSystemException("Error writing data to " + filename + ": " + file.errorString());
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray read(const QString& filename)
|
||||
{
|
||||
QFile file(filename);
|
||||
@ -238,6 +272,28 @@ bool ensureFolderPathExists(QString foldernamepath)
|
||||
return success;
|
||||
}
|
||||
|
||||
bool copyFileAttributes(QString src, QString dst)
|
||||
{
|
||||
#ifdef Q_OS_WIN32
|
||||
auto attrs = GetFileAttributesW(src.toStdWString().c_str());
|
||||
if (attrs == INVALID_FILE_ATTRIBUTES)
|
||||
return false;
|
||||
return SetFileAttributesW(dst.toStdWString().c_str(), attrs);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
// needs folders to exists
|
||||
void copyFolderAttributes(QString src, QString dst, QString relative)
|
||||
{
|
||||
auto path = PathCombine(src, relative);
|
||||
QDir dsrc(src);
|
||||
while ((path = QFileInfo(path).path()).length() >= src.length()) {
|
||||
auto dst_path = PathCombine(dst, dsrc.relativeFilePath(path));
|
||||
copyFileAttributes(path, dst_path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copies a directory and it's contents from src to dest
|
||||
* @param offset subdirectory form src to copy to dest
|
||||
@ -265,6 +321,9 @@ bool copy::operator()(const QString& offset, bool dryRun)
|
||||
if (!m_followSymlinks)
|
||||
opt |= copy_opts::copy_symlinks;
|
||||
|
||||
if (m_overwrite)
|
||||
opt |= copy_opts::overwrite_existing;
|
||||
|
||||
// Function that'll do the actual copying
|
||||
auto copy_file = [&](QString src_path, QString relative_dst_path) {
|
||||
if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))
|
||||
@ -273,6 +332,9 @@ bool copy::operator()(const QString& offset, bool dryRun)
|
||||
auto dst_path = PathCombine(dst, relative_dst_path);
|
||||
if (!dryRun) {
|
||||
ensureFilePathExists(dst_path);
|
||||
#ifdef Q_OS_WIN32
|
||||
copyFolderAttributes(src, dst, relative_dst_path);
|
||||
#endif
|
||||
fs::copy(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), opt, err);
|
||||
}
|
||||
if (err) {
|
||||
|
@ -61,6 +61,16 @@ class FileSystemException : public ::Exception {
|
||||
*/
|
||||
void write(const QString& filename, const QByteArray& data);
|
||||
|
||||
/**
|
||||
* append data to a file safely
|
||||
*/
|
||||
void appendSafe(const QString& filename, const QByteArray& data);
|
||||
|
||||
/**
|
||||
* append data to a file
|
||||
*/
|
||||
void append(const QString& filename, const QByteArray& data);
|
||||
|
||||
/**
|
||||
* read data from a file safely\
|
||||
*/
|
||||
@ -109,6 +119,11 @@ class copy : public QObject {
|
||||
m_whitelist = whitelist;
|
||||
return *this;
|
||||
}
|
||||
copy& overwrite(const bool overwrite)
|
||||
{
|
||||
m_overwrite = overwrite;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
|
||||
|
||||
@ -128,6 +143,7 @@ class copy : public QObject {
|
||||
bool m_followSymlinks = true;
|
||||
const IPathMatcher* m_matcher = nullptr;
|
||||
bool m_whitelist = false;
|
||||
bool m_overwrite = false;
|
||||
QDir m_src;
|
||||
QDir m_dst;
|
||||
qsizetype m_copied;
|
||||
|
@ -2,6 +2,7 @@
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* 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
|
||||
@ -237,8 +238,11 @@ GroupId InstanceList::getInstanceGroup(const InstanceId& id) const
|
||||
return GroupId();
|
||||
}
|
||||
|
||||
void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
|
||||
void InstanceList::setInstanceGroup(const InstanceId& id, GroupId name)
|
||||
{
|
||||
if (name.isEmpty() && !name.isNull())
|
||||
name = QString();
|
||||
|
||||
auto inst = getInstanceById(id);
|
||||
if (!inst) {
|
||||
qDebug() << "Attempt to set a null instance's group";
|
||||
@ -249,6 +253,7 @@ void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
|
||||
auto iter = m_instanceGroupIndex.find(inst->id());
|
||||
if (iter != m_instanceGroupIndex.end()) {
|
||||
if (*iter != name) {
|
||||
decreaseGroupCount(*iter);
|
||||
*iter = name;
|
||||
changed = true;
|
||||
}
|
||||
@ -258,7 +263,7 @@ void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
m_groupNameCache.insert(name);
|
||||
increaseGroupCount(name);
|
||||
auto idx = getInstIndex(inst.get());
|
||||
emit dataChanged(index(idx), index(idx), { GroupRole });
|
||||
saveGroupList();
|
||||
@ -267,29 +272,55 @@ void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
|
||||
|
||||
QStringList InstanceList::getGroups()
|
||||
{
|
||||
return m_groupNameCache.values();
|
||||
return m_groupNameCache.keys();
|
||||
}
|
||||
|
||||
void InstanceList::deleteGroup(const QString& name)
|
||||
void InstanceList::deleteGroup(const GroupId& name)
|
||||
{
|
||||
m_groupNameCache.remove(name);
|
||||
m_collapsedGroups.remove(name);
|
||||
|
||||
bool removed = false;
|
||||
qDebug() << "Delete group" << name;
|
||||
for (auto& instance : m_instances) {
|
||||
const auto& instID = instance->id();
|
||||
auto instGroupName = getInstanceGroup(instID);
|
||||
const QString& instID = instance->id();
|
||||
const QString instGroupName = getInstanceGroup(instID);
|
||||
if (instGroupName == name) {
|
||||
m_instanceGroupIndex.remove(instID);
|
||||
qDebug() << "Remove" << instID << "from group" << name;
|
||||
removed = true;
|
||||
auto idx = getInstIndex(instance.get());
|
||||
if (idx > 0) {
|
||||
if (idx > 0)
|
||||
emit dataChanged(index(idx), index(idx), { GroupRole });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (removed) {
|
||||
if (removed)
|
||||
saveGroupList();
|
||||
}
|
||||
|
||||
void InstanceList::renameGroup(const QString& src, const QString& dst)
|
||||
{
|
||||
m_groupNameCache.remove(src);
|
||||
if (m_collapsedGroups.remove(src))
|
||||
m_collapsedGroups.insert(dst);
|
||||
|
||||
bool modified = false;
|
||||
qDebug() << "Rename group" << src << "to" << dst;
|
||||
for (auto& instance : m_instances) {
|
||||
const QString& instID = instance->id();
|
||||
const QString instGroupName = getInstanceGroup(instID);
|
||||
if (instGroupName == src) {
|
||||
m_instanceGroupIndex[instID] = dst;
|
||||
increaseGroupCount(dst);
|
||||
qDebug() << "Set" << instID << "group to" << dst;
|
||||
modified = true;
|
||||
auto idx = getInstIndex(instance.get());
|
||||
if (idx > 0)
|
||||
emit dataChanged(index(idx), index(idx), { GroupRole });
|
||||
}
|
||||
}
|
||||
if (modified)
|
||||
saveGroupList();
|
||||
}
|
||||
|
||||
bool InstanceList::isGroupCollapsed(const QString& group)
|
||||
@ -305,12 +336,13 @@ bool InstanceList::trashInstance(const InstanceId& id)
|
||||
return false;
|
||||
}
|
||||
|
||||
auto cachedGroupId = m_instanceGroupIndex[id];
|
||||
QString cachedGroupId = m_instanceGroupIndex[id];
|
||||
|
||||
qDebug() << "Will trash instance" << id;
|
||||
QString trashedLoc;
|
||||
|
||||
if (m_instanceGroupIndex.remove(id)) {
|
||||
decreaseGroupCount(cachedGroupId);
|
||||
saveGroupList();
|
||||
}
|
||||
|
||||
@ -348,7 +380,7 @@ void InstanceList::undoTrashInstance()
|
||||
QFile(top.trashPath).rename(top.polyPath);
|
||||
|
||||
m_instanceGroupIndex[top.id] = top.groupName;
|
||||
m_groupNameCache.insert(top.groupName);
|
||||
increaseGroupCount(top.groupName);
|
||||
|
||||
saveGroupList();
|
||||
emit instancesChanged();
|
||||
@ -362,7 +394,10 @@ void InstanceList::deleteInstance(const InstanceId& id)
|
||||
return;
|
||||
}
|
||||
|
||||
QString cachedGroupId = m_instanceGroupIndex[id];
|
||||
|
||||
if (m_instanceGroupIndex.remove(id)) {
|
||||
decreaseGroupCount(cachedGroupId);
|
||||
saveGroupList();
|
||||
}
|
||||
|
||||
@ -610,6 +645,25 @@ InstancePtr InstanceList::loadInstance(const InstanceId& id)
|
||||
return inst;
|
||||
}
|
||||
|
||||
void InstanceList::increaseGroupCount(const QString& group)
|
||||
{
|
||||
if (group.isEmpty())
|
||||
return;
|
||||
|
||||
++m_groupNameCache[group];
|
||||
}
|
||||
|
||||
void InstanceList::decreaseGroupCount(const QString& group)
|
||||
{
|
||||
if (group.isEmpty())
|
||||
return;
|
||||
|
||||
if (--m_groupNameCache[group] < 1) {
|
||||
m_groupNameCache.remove(group);
|
||||
m_collapsedGroups.remove(group);
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceList::saveGroupList()
|
||||
{
|
||||
qDebug() << "Will save group list now.";
|
||||
@ -621,7 +675,7 @@ void InstanceList::saveGroupList()
|
||||
QString groupFileName = m_instDir + "/instgroups.json";
|
||||
QMap<QString, QSet<QString>> reverseGroupMap;
|
||||
for (auto iter = m_instanceGroupIndex.begin(); iter != m_instanceGroupIndex.end(); iter++) {
|
||||
QString id = iter.key();
|
||||
const QString& id = iter.key();
|
||||
QString group = iter.value();
|
||||
if (group.isEmpty())
|
||||
continue;
|
||||
@ -711,17 +765,22 @@ void InstanceList::loadGroupList()
|
||||
return;
|
||||
}
|
||||
|
||||
QSet<QString> groupSet;
|
||||
m_instanceGroupIndex.clear();
|
||||
m_groupNameCache.clear();
|
||||
|
||||
// Iterate through all the groups.
|
||||
QJsonObject groupMapping = rootObj.value("groups").toObject();
|
||||
for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++) {
|
||||
QString groupName = iter.key();
|
||||
|
||||
if (iter.key().isEmpty()) {
|
||||
qWarning() << "Redundant empty group found";
|
||||
continue;
|
||||
}
|
||||
|
||||
// If not an object, complain and skip to the next one.
|
||||
if (!iter.value().isObject()) {
|
||||
qWarning() << QString("Group '%1' in the group list should be an object.").arg(groupName).toUtf8();
|
||||
qWarning() << QString("Group '%1' in the group list should be an object").arg(groupName).toUtf8();
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -733,23 +792,19 @@ void InstanceList::loadGroupList()
|
||||
continue;
|
||||
}
|
||||
|
||||
// keep a list/set of groups for choosing
|
||||
groupSet.insert(groupName);
|
||||
|
||||
auto hidden = groupObj.value("hidden").toBool(false);
|
||||
if (hidden) {
|
||||
if (hidden)
|
||||
m_collapsedGroups.insert(groupName);
|
||||
}
|
||||
|
||||
// Iterate through the list of instances in the group.
|
||||
QJsonArray instancesArray = groupObj.value("instances").toArray();
|
||||
|
||||
for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end(); iter2++) {
|
||||
m_instanceGroupIndex[(*iter2).toString()] = groupName;
|
||||
for (auto value : instancesArray) {
|
||||
m_instanceGroupIndex[value.toString()] = groupName;
|
||||
increaseGroupCount(groupName);
|
||||
}
|
||||
}
|
||||
m_groupsLoaded = true;
|
||||
m_groupNameCache.unite(groupSet);
|
||||
qDebug() << "Group list loaded.";
|
||||
}
|
||||
|
||||
@ -925,7 +980,7 @@ bool InstanceList::commitStagedInstance(const QString& path,
|
||||
}
|
||||
|
||||
m_instanceGroupIndex[instID] = groupName;
|
||||
m_groupNameCache.insert(groupName);
|
||||
increaseGroupCount(groupName);
|
||||
}
|
||||
|
||||
instanceSet.insert(instID);
|
||||
|
@ -1,16 +1,36 @@
|
||||
/* Copyright 2013-2021 MultiMC Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* 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
|
||||
* 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.
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
@ -86,9 +106,10 @@ class InstanceList : public QAbstractListModel {
|
||||
bool isGroupCollapsed(const QString& groupName);
|
||||
|
||||
GroupId getInstanceGroup(const InstanceId& id) const;
|
||||
void setInstanceGroup(const InstanceId& id, const GroupId& name);
|
||||
void setInstanceGroup(const InstanceId& id, GroupId name);
|
||||
|
||||
void deleteGroup(const GroupId& name);
|
||||
void renameGroup(const GroupId& src, const GroupId& dst);
|
||||
bool trashInstance(const InstanceId& id);
|
||||
bool trashedSomething();
|
||||
void undoTrashInstance();
|
||||
@ -158,12 +179,16 @@ class InstanceList : public QAbstractListModel {
|
||||
QList<InstanceId> discoverInstances();
|
||||
InstancePtr loadInstance(const InstanceId& id);
|
||||
|
||||
void increaseGroupCount(const QString& group);
|
||||
void decreaseGroupCount(const QString& group);
|
||||
|
||||
private:
|
||||
int m_watchLevel = 0;
|
||||
int totalPlayTime = 0;
|
||||
bool m_dirty = false;
|
||||
QList<InstancePtr> m_instances;
|
||||
QSet<QString> m_groupNameCache;
|
||||
// id -> refs
|
||||
QMap<QString, int> m_groupNameCache;
|
||||
|
||||
SettingsObjectPtr m_globalSettings;
|
||||
QString m_instDir;
|
||||
|
@ -42,7 +42,11 @@
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QUrl>
|
||||
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
#include <QtConcurrentRun>
|
||||
#endif
|
||||
|
||||
namespace MMCZip {
|
||||
// ours
|
||||
@ -132,6 +136,7 @@ bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files,
|
||||
return result;
|
||||
}
|
||||
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
// ours
|
||||
bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods)
|
||||
{
|
||||
@ -217,6 +222,7 @@ bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<M
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
// ours
|
||||
QString findFolderOfFileInZip(QuaZip* zip, const QString& what, const QStringList& ignore_paths, const QString& root)
|
||||
@ -422,6 +428,7 @@ bool collectFileListRecursively(const QString& rootDir, const QString& subDir, Q
|
||||
return true;
|
||||
}
|
||||
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
void ExportToZipTask::executeTask()
|
||||
{
|
||||
setStatus("Adding files...");
|
||||
@ -500,5 +507,6 @@ bool ExportToZipTask::abort()
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace MMCZip
|
||||
} // namespace MMCZip
|
||||
|
@ -48,7 +48,10 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
#include "minecraft/mod/Mod.h"
|
||||
#endif
|
||||
#include "tasks/Task.h"
|
||||
|
||||
namespace MMCZip {
|
||||
@ -79,11 +82,12 @@ bool compressDirFiles(QuaZip* zip, QString dir, QFileInfoList files, bool follow
|
||||
*/
|
||||
bool compressDirFiles(QString fileCompressed, QString dir, QFileInfoList files, bool followSymlinks = false);
|
||||
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
/**
|
||||
* take a source jar, add mods to it, resulting in target jar
|
||||
*/
|
||||
bool createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod*>& mods);
|
||||
|
||||
#endif
|
||||
/**
|
||||
* Find a single file in archive by file name (not path)
|
||||
*
|
||||
@ -147,6 +151,7 @@ bool extractFile(QString fileCompressed, QString file, QString dir);
|
||||
*/
|
||||
bool collectFileListRecursively(const QString& rootDir, const QString& subDir, QFileInfoList* files, FilterFunction excludeFilter);
|
||||
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
class ExportToZipTask : public Task {
|
||||
public:
|
||||
ExportToZipTask(QString outputPath, QDir dir, QFileInfoList files, QString destinationPrefix = "", bool followSymlinks = false)
|
||||
@ -189,4 +194,5 @@ class ExportToZipTask : public Task {
|
||||
QFuture<ZipResult> m_build_zip_future;
|
||||
QFutureWatcher<ZipResult> m_build_zip_watcher;
|
||||
};
|
||||
#endif
|
||||
} // namespace MMCZip
|
||||
|
@ -35,6 +35,7 @@
|
||||
*/
|
||||
|
||||
#include "StringUtils.h"
|
||||
#include <qpair.h>
|
||||
|
||||
#include <QRegularExpression>
|
||||
#include <QUuid>
|
||||
@ -149,7 +150,7 @@ QString StringUtils::truncateUrlHumanFriendly(QUrl& url, int max_len, bool hard_
|
||||
}
|
||||
|
||||
if ((url_compact.length() >= max_len) && hard_limit) {
|
||||
// still too long, truncate normaly
|
||||
// still too long, truncate normally
|
||||
url_compact = QString(str_url);
|
||||
auto to_remove = url_compact.length() - max_len + 3;
|
||||
url_compact.remove(url_compact.length() - to_remove - 1, to_remove);
|
||||
@ -182,3 +183,32 @@ QString StringUtils::getRandomAlphaNumeric()
|
||||
{
|
||||
return QUuid::createUuid().toString(QUuid::Id128);
|
||||
}
|
||||
|
||||
QPair<QString, QString> StringUtils::splitFirst(const QString& s, const QString& sep, Qt::CaseSensitivity cs)
|
||||
{
|
||||
QString left, right;
|
||||
auto index = s.indexOf(sep, 0, cs);
|
||||
left = s.mid(0, index);
|
||||
right = s.mid(index + sep.length());
|
||||
return qMakePair(left, right);
|
||||
}
|
||||
|
||||
QPair<QString, QString> StringUtils::splitFirst(const QString& s, QChar sep, Qt::CaseSensitivity cs)
|
||||
{
|
||||
QString left, right;
|
||||
auto index = s.indexOf(sep, 0, cs);
|
||||
left = s.mid(0, index);
|
||||
right = s.mid(left.length() + 1);
|
||||
return qMakePair(left, right);
|
||||
}
|
||||
|
||||
QPair<QString, QString> StringUtils::splitFirst(const QString& s, const QRegularExpression& re)
|
||||
{
|
||||
QString left, right;
|
||||
QRegularExpressionMatch match;
|
||||
auto index = s.indexOf(re, 0, &match);
|
||||
left = s.mid(0, index);
|
||||
auto end = match.hasMatch() ? left.length() + match.capturedLength() : left.length() + 1;
|
||||
right = s.mid(end);
|
||||
return qMakePair(left, right);
|
||||
}
|
||||
|
@ -36,8 +36,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QPair>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <utility>
|
||||
|
||||
namespace StringUtils {
|
||||
|
||||
@ -70,12 +72,17 @@ int naturalCompare(const QString& s1, const QString& s2, Qt::CaseSensitivity cs)
|
||||
/**
|
||||
* @brief Truncate a url while keeping its readability py placing the `...` in the middle of the path
|
||||
* @param url Url to truncate
|
||||
* @param max_len max lenght of url in charaters
|
||||
* @param hard_limit if truncating the path can't get the url short enough, truncate it normaly.
|
||||
* @param max_len max length of url in characters
|
||||
* @param hard_limit if truncating the path can't get the url short enough, truncate it normally.
|
||||
*/
|
||||
QString truncateUrlHumanFriendly(QUrl& url, int max_len, bool hard_limit = false);
|
||||
|
||||
QString humanReadableFileSize(double bytes, bool use_si = false, int decimal_points = 1);
|
||||
|
||||
QString getRandomAlphaNumeric();
|
||||
|
||||
QPair<QString, QString> splitFirst(const QString& s, const QString& sep, Qt::CaseSensitivity cs = Qt::CaseSensitive);
|
||||
QPair<QString, QString> splitFirst(const QString& s, QChar sep, Qt::CaseSensitivity cs = Qt::CaseSensitive);
|
||||
QPair<QString, QString> splitFirst(const QString& s, const QRegularExpression& re);
|
||||
|
||||
} // namespace StringUtils
|
||||
|
@ -56,6 +56,7 @@ class Version {
|
||||
bool operator!=(const Version& other) const;
|
||||
|
||||
QString toString() const { return m_string; }
|
||||
bool isEmpty() const { return m_string.isEmpty(); }
|
||||
|
||||
friend QDebug operator<<(QDebug debug, const Version& v);
|
||||
|
||||
|
@ -93,6 +93,7 @@ FileLinkApp::FileLinkApp(int& argc, char** argv) : QCoreApplication(argc, argv),
|
||||
joinServer(serverToJoin);
|
||||
} else {
|
||||
qDebug() << "no server to join";
|
||||
m_status = Failed;
|
||||
exit();
|
||||
}
|
||||
}
|
||||
@ -108,6 +109,7 @@ void FileLinkApp::joinServer(QString server)
|
||||
connect(&socket, &QLocalSocket::readyRead, this, &FileLinkApp::readPathPairs);
|
||||
|
||||
connect(&socket, &QLocalSocket::errorOccurred, this, [&](QLocalSocket::LocalSocketError socketError) {
|
||||
m_status = Failed;
|
||||
switch (socketError) {
|
||||
case QLocalSocket::ServerNotFoundError:
|
||||
qDebug()
|
||||
@ -132,6 +134,7 @@ void FileLinkApp::joinServer(QString server)
|
||||
|
||||
connect(&socket, &QLocalSocket::disconnected, this, [&]() {
|
||||
qDebug() << "disconnected from server, should exit";
|
||||
m_status = Succeeded;
|
||||
exit();
|
||||
});
|
||||
|
||||
|
@ -41,8 +41,10 @@ class FileLinkApp : public QCoreApplication {
|
||||
// friends for the purpose of limiting access to deprecated stuff
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Status { Starting, Failed, Succeeded, Initialized };
|
||||
FileLinkApp(int& argc, char** argv);
|
||||
virtual ~FileLinkApp();
|
||||
Status status() const { return m_status; }
|
||||
|
||||
private:
|
||||
void joinServer(QString server);
|
||||
@ -50,6 +52,8 @@ class FileLinkApp : public QCoreApplication {
|
||||
void runLink();
|
||||
void sendResults();
|
||||
|
||||
Status m_status = Status::Starting;
|
||||
|
||||
bool m_useHardLinks = false;
|
||||
|
||||
QDateTime m_startTime;
|
||||
|
@ -26,5 +26,16 @@ int main(int argc, char* argv[])
|
||||
{
|
||||
FileLinkApp ldh(argc, argv);
|
||||
|
||||
return ldh.exec();
|
||||
switch (ldh.status()) {
|
||||
case FileLinkApp::Starting:
|
||||
case FileLinkApp::Initialized: {
|
||||
return ldh.exec();
|
||||
}
|
||||
case FileLinkApp::Failed:
|
||||
return 1;
|
||||
case FileLinkApp::Succeeded:
|
||||
return 0;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
@ -45,10 +45,12 @@ QString JavaVersion::toString() const
|
||||
|
||||
bool JavaVersion::requiresPermGen()
|
||||
{
|
||||
if (m_parseable) {
|
||||
return m_major < 8;
|
||||
}
|
||||
return true;
|
||||
return !m_parseable || m_major < 8;
|
||||
}
|
||||
|
||||
bool JavaVersion::isModular()
|
||||
{
|
||||
return m_parseable && m_major >= 9;
|
||||
}
|
||||
|
||||
bool JavaVersion::operator<(const JavaVersion& rhs)
|
||||
|
@ -25,6 +25,8 @@ class JavaVersion {
|
||||
|
||||
bool requiresPermGen();
|
||||
|
||||
bool isModular();
|
||||
|
||||
QString toString() const;
|
||||
|
||||
int major() { return m_major; }
|
||||
|
@ -184,6 +184,10 @@ void MinecraftInstance::loadSpecificSettings()
|
||||
m_settings->registerOverride(global_settings->getSetting("CloseAfterLaunch"), miscellaneousOverride);
|
||||
m_settings->registerOverride(global_settings->getSetting("QuitAfterGameStop"), miscellaneousOverride);
|
||||
|
||||
// Legacy-related options
|
||||
auto legacySettings = m_settings->registerSetting("OverrideLegacySettings", false);
|
||||
m_settings->registerOverride(global_settings->getSetting("OnlineFixes"), legacySettings);
|
||||
|
||||
m_settings->set("InstanceType", "OneSix");
|
||||
}
|
||||
|
||||
@ -513,20 +517,28 @@ QStringList MinecraftInstance::javaArguments()
|
||||
|
||||
args << "-Duser.language=en";
|
||||
|
||||
if (javaVersion.isModular() && shouldApplyOnlineFixes())
|
||||
// allow reflective access to java.net - required by the skin fix
|
||||
args << "--add-opens"
|
||||
<< "java.base/java.net=ALL-UNNAMED";
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
QString MinecraftInstance::getLauncher()
|
||||
{
|
||||
auto profile = m_components->getProfile();
|
||||
|
||||
// use legacy launcher if the traits are set
|
||||
if (profile->getTraits().contains("legacyLaunch") || profile->getTraits().contains("alphaLaunch"))
|
||||
if (traits().contains("legacyLaunch") || traits().contains("alphaLaunch"))
|
||||
return "legacy";
|
||||
|
||||
return "standard";
|
||||
}
|
||||
|
||||
bool MinecraftInstance::shouldApplyOnlineFixes()
|
||||
{
|
||||
return traits().contains("legacyServices") && settings()->get("OnlineFixes").toBool();
|
||||
}
|
||||
|
||||
QMap<QString, QString> MinecraftInstance::getVariables()
|
||||
{
|
||||
QMap<QString, QString> out;
|
||||
@ -716,6 +728,9 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS
|
||||
launchScript += "traits " + trait + "\n";
|
||||
}
|
||||
|
||||
if (shouldApplyOnlineFixes())
|
||||
launchScript += "onlineFixes true\n";
|
||||
|
||||
launchScript += "launcher " + getLauncher() + "\n";
|
||||
|
||||
// qDebug() << "Generated launch script:" << launchScript;
|
||||
|
@ -129,6 +129,7 @@ class MinecraftInstance : public BaseInstance {
|
||||
/// get arguments passed to java
|
||||
QStringList javaArguments();
|
||||
QString getLauncher();
|
||||
bool shouldApplyOnlineFixes();
|
||||
|
||||
/// get variables for launch command variable substitution/environment
|
||||
QMap<QString, QString> getVariables() override;
|
||||
|
@ -105,6 +105,17 @@ void LauncherPartLaunch::executeTask()
|
||||
auto instance = m_parent->instance();
|
||||
std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance);
|
||||
|
||||
QString legacyJarPath;
|
||||
if (minecraftInstance->getLauncher() == "legacy" || minecraftInstance->shouldApplyOnlineFixes()) {
|
||||
legacyJarPath = APPLICATION->getJarPath("NewLaunchLegacy.jar");
|
||||
if (legacyJarPath.isEmpty()) {
|
||||
const char* reason = QT_TR_NOOP("Legacy launcher library could not be found. Please check your installation.");
|
||||
emit logLine(tr(reason), MessageLevel::Fatal);
|
||||
emitFailed(tr(reason));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_launchScript = minecraftInstance->createLaunchScript(m_session, m_serverToJoin);
|
||||
QStringList args = minecraftInstance->javaArguments();
|
||||
QString allArgs = args.join(", ");
|
||||
@ -120,6 +131,9 @@ void LauncherPartLaunch::executeTask()
|
||||
auto classPath = minecraftInstance->getClassPath();
|
||||
classPath.prepend(jarPath);
|
||||
|
||||
if (!legacyJarPath.isEmpty())
|
||||
classPath.prepend(legacyJarPath);
|
||||
|
||||
auto natPath = minecraftInstance->getNativePath();
|
||||
#ifdef Q_OS_WIN
|
||||
if (!fitsInLocal8bit(natPath)) {
|
||||
|
@ -43,5 +43,5 @@ void ATLauncher::loadIndexedPack(ATLauncher::IndexedPack& m, QJsonObject& obj)
|
||||
m.system = Json::ensureBoolean(obj, QString("system"), false);
|
||||
m.description = Json::ensureString(obj, "description", "");
|
||||
|
||||
m.safeName = Json::requireString(obj, "name").replace(QRegularExpression("[^A-Za-z0-9]"), "");
|
||||
m.safeName = Json::requireString(obj, "name").replace(QRegularExpression("[^A-Za-z0-9]"), "").toLower() + ".png";
|
||||
}
|
||||
|
@ -37,6 +37,7 @@
|
||||
#include "ATLPackInstallTask.h"
|
||||
|
||||
#include <QtConcurrent>
|
||||
#include <algorithm>
|
||||
|
||||
#include <quazip/quazip.h>
|
||||
|
||||
@ -50,6 +51,7 @@
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/OneSixVersionFormat.h"
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "modplatform/atlauncher/ATLPackManifest.h"
|
||||
#include "net/ChecksumValidator.h"
|
||||
#include "settings/INISettingsObject.h"
|
||||
|
||||
@ -57,6 +59,7 @@
|
||||
|
||||
#include "Application.h"
|
||||
#include "BuildConfig.h"
|
||||
#include "ui/dialogs/BlockedModsDialog.h"
|
||||
|
||||
namespace ATLauncher {
|
||||
|
||||
@ -717,6 +720,8 @@ void PackInstallTask::downloadMods()
|
||||
|
||||
jarmods.clear();
|
||||
jobPtr.reset(new NetJob(tr("Mod download"), APPLICATION->network()));
|
||||
|
||||
QList<VersionMod> blocked_mods;
|
||||
for (const auto& mod : m_version.mods) {
|
||||
// skip non-client mods
|
||||
if (!mod.client)
|
||||
@ -731,9 +736,10 @@ void PackInstallTask::downloadMods()
|
||||
case DownloadType::Server:
|
||||
url = BuildConfig.ATL_DOWNLOAD_SERVER_URL + mod.url;
|
||||
break;
|
||||
case DownloadType::Browser:
|
||||
emitFailed(tr("Unsupported download type: %1").arg(mod.download_raw));
|
||||
return;
|
||||
case DownloadType::Browser: {
|
||||
blocked_mods.append(mod);
|
||||
continue;
|
||||
}
|
||||
case DownloadType::Direct:
|
||||
url = mod.url;
|
||||
break;
|
||||
@ -805,24 +811,86 @@ void PackInstallTask::downloadMods()
|
||||
modsToCopy[entry->getFullPath()] = path;
|
||||
}
|
||||
}
|
||||
if (!blocked_mods.isEmpty()) {
|
||||
QList<BlockedMod> mods;
|
||||
|
||||
for (auto mod : blocked_mods) {
|
||||
BlockedMod blocked_mod;
|
||||
blocked_mod.name = mod.file;
|
||||
blocked_mod.websiteUrl = mod.url;
|
||||
blocked_mod.hash = mod.md5;
|
||||
blocked_mod.matched = false;
|
||||
blocked_mod.localPath = "";
|
||||
|
||||
mods.append(blocked_mod);
|
||||
}
|
||||
|
||||
qWarning() << "Blocked mods found, displaying mod list";
|
||||
|
||||
BlockedModsDialog message_dialog(nullptr, tr("Blocked mods 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."),
|
||||
mods, "md5");
|
||||
|
||||
message_dialog.setModal(true);
|
||||
|
||||
if (message_dialog.exec()) {
|
||||
qDebug() << "Post dialog blocked mods list: " << mods;
|
||||
for (auto blocked : mods) {
|
||||
if (!blocked.matched) {
|
||||
qDebug() << blocked.name << "was not matched to a local file, skipping copy";
|
||||
continue;
|
||||
}
|
||||
auto modIter = std::find_if(blocked_mods.begin(), blocked_mods.end(),
|
||||
[blocked](const VersionMod& mod) { return mod.url == blocked.websiteUrl; });
|
||||
if (modIter == blocked_mods.end())
|
||||
continue;
|
||||
auto mod = *modIter;
|
||||
if (mod.type == ModType::Extract || mod.type == ModType::TexturePackExtract || mod.type == ModType::ResourcePackExtract) {
|
||||
modsToExtract.insert(blocked.localPath, mod);
|
||||
} else if (mod.type == ModType::Decomp) {
|
||||
modsToDecomp.insert(blocked.localPath, mod);
|
||||
} else {
|
||||
auto relpath = getDirForModType(mod.type, mod.type_raw);
|
||||
if (relpath == Q_NULLPTR)
|
||||
continue;
|
||||
|
||||
auto path = FS::PathCombine(m_stagingPath, "minecraft", relpath, mod.file);
|
||||
|
||||
if (mod.type == ModType::Forge) {
|
||||
auto ver = getComponentVersion("net.minecraftforge", mod.version);
|
||||
if (ver) {
|
||||
componentsToInstall.insert("net.minecraftforge", ver);
|
||||
continue;
|
||||
}
|
||||
|
||||
qDebug() << "Jarmod: " + path;
|
||||
jarmods.push_back(path);
|
||||
}
|
||||
|
||||
if (mod.type == ModType::Jar) {
|
||||
qDebug() << "Jarmod: " + path;
|
||||
jarmods.push_back(path);
|
||||
}
|
||||
|
||||
modsToCopy[blocked.localPath] = path;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
emitFailed(tr("Unknown download type: %1").arg("browser"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
connect(jobPtr.get(), &NetJob::succeeded, this, &PackInstallTask::onModsDownloaded);
|
||||
connect(jobPtr.get(), &NetJob::failed, [&](QString reason) {
|
||||
abortable = false;
|
||||
jobPtr.reset();
|
||||
emitFailed(reason);
|
||||
});
|
||||
connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total) {
|
||||
connect(jobPtr.get(), &NetJob::progress, [this](qint64 current, qint64 total) {
|
||||
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
|
||||
abortable = true;
|
||||
setProgress(current, total);
|
||||
});
|
||||
connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propagateStepProgress);
|
||||
connect(jobPtr.get(), &NetJob::aborted, [&] {
|
||||
abortable = false;
|
||||
jobPtr.reset();
|
||||
emitAborted();
|
||||
});
|
||||
connect(jobPtr.get(), &NetJob::aborted, &PackInstallTask::emitAborted);
|
||||
connect(jobPtr.get(), &NetJob::failed, &PackInstallTask::emitFailed);
|
||||
|
||||
jobPtr->start();
|
||||
}
|
||||
@ -843,7 +911,7 @@ void PackInstallTask::onModsDownloaded()
|
||||
QtConcurrent::run(QThreadPool::globalInstance(), this, &PackInstallTask::extractMods, modsToExtract, modsToDecomp, modsToCopy);
|
||||
#endif
|
||||
connect(&m_modExtractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &PackInstallTask::onModsExtracted);
|
||||
connect(&m_modExtractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, [&]() { emitAborted(); });
|
||||
connect(&m_modExtractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &PackInstallTask::emitAborted);
|
||||
m_modExtractFutureWatcher.setFuture(m_modExtractFuture);
|
||||
} else {
|
||||
install();
|
||||
|
@ -62,6 +62,7 @@
|
||||
#include "minecraft/World.h"
|
||||
#include "minecraft/mod/tasks/LocalResourceParse.h"
|
||||
#include "net/ApiDownload.h"
|
||||
#include "ui/pages/modplatform/OptionalModDialog.h"
|
||||
|
||||
static const FlameAPI api;
|
||||
|
||||
@ -509,13 +510,33 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
|
||||
void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
|
||||
{
|
||||
m_files_job.reset(new NetJob(tr("Mod Download Flame"), APPLICATION->network()));
|
||||
for (const auto& result : m_mod_id_resolver->getResults().files) {
|
||||
QString filename = result.fileName;
|
||||
auto results = m_mod_id_resolver->getResults().files;
|
||||
|
||||
QStringList optionalFiles;
|
||||
for (auto& result : results) {
|
||||
if (!result.required) {
|
||||
filename += ".disabled";
|
||||
optionalFiles << FS::PathCombine(result.targetFolder, result.fileName);
|
||||
}
|
||||
}
|
||||
|
||||
QStringList selectedOptionalMods;
|
||||
if (!optionalFiles.empty()) {
|
||||
OptionalModDialog optionalModDialog(m_parent, optionalFiles);
|
||||
if (optionalModDialog.exec() == QDialog::Rejected) {
|
||||
emitAborted();
|
||||
loop.quit();
|
||||
return;
|
||||
}
|
||||
|
||||
auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename);
|
||||
selectedOptionalMods = optionalModDialog.getResult();
|
||||
}
|
||||
for (const auto& result : results) {
|
||||
auto relpath = FS::PathCombine(result.targetFolder, result.fileName);
|
||||
if (!result.required && !selectedOptionalMods.contains(relpath)) {
|
||||
relpath += ".disabled";
|
||||
}
|
||||
|
||||
relpath = FS::PathCombine("minecraft", relpath);
|
||||
auto path = FS::PathCombine(m_stagingPath, relpath);
|
||||
|
||||
switch (result.type) {
|
||||
@ -547,7 +568,7 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
|
||||
m_mod_id_resolver.reset();
|
||||
connect(m_files_job.get(), &NetJob::succeeded, this, [&]() {
|
||||
m_files_job.reset();
|
||||
validateZIPResouces();
|
||||
validateZIPResources();
|
||||
});
|
||||
connect(m_files_job.get(), &NetJob::failed, [&](QString reason) {
|
||||
m_files_job.reset();
|
||||
@ -596,7 +617,7 @@ void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods)
|
||||
setAbortable(true);
|
||||
}
|
||||
|
||||
void FlameCreationTask::validateZIPResouces()
|
||||
void FlameCreationTask::validateZIPResources()
|
||||
{
|
||||
qDebug() << "Validating whether resources stored as .zip are in the right place";
|
||||
for (auto [fileName, targetFolder] : m_ZIP_resources) {
|
||||
@ -649,8 +670,8 @@ void FlameCreationTask::validateZIPResouces()
|
||||
validatePath(fileName, targetFolder, "datapacks");
|
||||
break;
|
||||
case PackedResourceType::ShaderPack:
|
||||
// in theroy flame API can't do this but who knows, that *may* change ?
|
||||
// better to handle it if it *does* occure in the future
|
||||
// in theory flame API can't do this but who knows, that *may* change ?
|
||||
// better to handle it if it *does* occur in the future
|
||||
validatePath(fileName, targetFolder, "shaderpacks");
|
||||
break;
|
||||
case PackedResourceType::WorldSave:
|
||||
|
@ -74,7 +74,7 @@ class FlameCreationTask final : public InstanceCreationTask {
|
||||
void idResolverSucceeded(QEventLoop&);
|
||||
void setupDownloadJob(QEventLoop&);
|
||||
void copyBlockedMods(QList<BlockedMod> const& blocked_mods);
|
||||
void validateZIPResouces();
|
||||
void validateZIPResources();
|
||||
QString getVersionForLoader(QString uid, QString loaderType, QString version, QString mcVersion);
|
||||
|
||||
private:
|
||||
|
@ -1,4 +1,6 @@
|
||||
#include "FlamePackIndex.h"
|
||||
#include <QFileInfo>
|
||||
#include <QUrl>
|
||||
|
||||
#include "Json.h"
|
||||
|
||||
@ -9,8 +11,8 @@ void Flame::loadIndexedPack(Flame::IndexedPack& pack, QJsonObject& obj)
|
||||
pack.description = Json::ensureString(obj, "summary", "");
|
||||
|
||||
auto logo = Json::requireObject(obj, "logo");
|
||||
pack.logoName = Json::requireString(logo, "title");
|
||||
pack.logoUrl = Json::requireString(logo, "thumbnailUrl");
|
||||
pack.logoName = Json::requireString(obj, "slug") + "." + QFileInfo(QUrl(pack.logoUrl).fileName()).suffix();
|
||||
|
||||
auto authors = Json::requireArray(obj, "authors");
|
||||
for (auto authorIter : authors) {
|
||||
|
@ -48,7 +48,7 @@ struct File {
|
||||
|
||||
int projectId = 0;
|
||||
int fileId = 0;
|
||||
// NOTE: the opposite to 'optional'. This is at the time of writing unused.
|
||||
// NOTE: the opposite to 'optional'
|
||||
bool required = true;
|
||||
QString hash;
|
||||
// NOTE: only set on blocked files ! Empty otherwise.
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
#include "modplatform/helpers/OverrideUtils.h"
|
||||
|
||||
#include "modplatform/modrinth/ModrinthPackManifest.h"
|
||||
#include "net/ChecksumValidator.h"
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
@ -16,8 +17,10 @@
|
||||
#include "settings/INISettingsObject.h"
|
||||
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
#include "ui/pages/modplatform/OptionalModDialog.h"
|
||||
|
||||
#include <QAbstractButton>
|
||||
#include <vector>
|
||||
|
||||
bool ModrinthCreationTask::abort()
|
||||
{
|
||||
@ -319,10 +322,10 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path,
|
||||
}
|
||||
|
||||
auto jsonFiles = Json::requireIsArrayOf<QJsonObject>(obj, "files", "modrinth.index.json");
|
||||
bool had_optional = false;
|
||||
std::vector<Modrinth::File> optionalFiles;
|
||||
for (const auto& modInfo : jsonFiles) {
|
||||
Modrinth::File file;
|
||||
file.path = Json::requireString(modInfo, "path");
|
||||
file.path = Json::requireString(modInfo, "path").replace("\\", "/");
|
||||
|
||||
auto env = Json::ensureObject(modInfo, "env");
|
||||
// 'env' field is optional
|
||||
@ -331,18 +334,7 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path,
|
||||
if (support == "unsupported") {
|
||||
continue;
|
||||
} else if (support == "optional") {
|
||||
// TODO: Make a review dialog for choosing which ones the user wants!
|
||||
if (!had_optional && show_optional_dialog) {
|
||||
had_optional = true;
|
||||
auto info = CustomMessageBox::selectable(
|
||||
m_parent, tr("Optional mod detected!"),
|
||||
tr("One or more mods from this modpack are optional. They will be downloaded, but disabled by default!"),
|
||||
QMessageBox::Information);
|
||||
info->exec();
|
||||
}
|
||||
|
||||
if (file.path.endsWith(".jar"))
|
||||
file.path += ".disabled";
|
||||
file.required = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -385,9 +377,29 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path,
|
||||
}
|
||||
}
|
||||
|
||||
files.push_back(file);
|
||||
(file.required ? files : optionalFiles).push_back(file);
|
||||
}
|
||||
|
||||
if (!optionalFiles.empty()) {
|
||||
QStringList oFiles;
|
||||
for (auto file : optionalFiles)
|
||||
oFiles.push_back(file.path);
|
||||
OptionalModDialog optionalModDialog(m_parent, oFiles);
|
||||
if (optionalModDialog.exec() == QDialog::Rejected) {
|
||||
emitAborted();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto selectedMods = optionalModDialog.getResult();
|
||||
for (auto file : optionalFiles) {
|
||||
if (selectedMods.contains(file.path)) {
|
||||
file.required = true;
|
||||
} else {
|
||||
file.path += ".disabled";
|
||||
}
|
||||
files.push_back(file);
|
||||
}
|
||||
}
|
||||
if (set_internal_data) {
|
||||
auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
|
||||
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
|
||||
|
@ -35,6 +35,7 @@
|
||||
*/
|
||||
|
||||
#include "ModrinthPackManifest.h"
|
||||
#include <QFileInfo>
|
||||
#include "Json.h"
|
||||
|
||||
#include "modplatform/modrinth/ModrinthAPI.h"
|
||||
@ -56,8 +57,8 @@ void loadIndexedPack(Modpack& pack, QJsonObject& obj)
|
||||
pack.description = Json::ensureString(obj, "description");
|
||||
auto temp_author_name = Json::ensureString(obj, "author");
|
||||
pack.author = std::make_tuple(temp_author_name, api.getAuthorURL(temp_author_name));
|
||||
pack.iconName = QString("modrinth_%1").arg(Json::ensureString(obj, "slug"));
|
||||
pack.iconUrl = Json::ensureString(obj, "icon_url");
|
||||
pack.iconName = QString("modrinth_%1.%2").arg(Json::ensureString(obj, "slug"), QFileInfo(pack.iconUrl.fileName()).suffix());
|
||||
}
|
||||
|
||||
void loadIndexedInfo(Modpack& pack, QJsonObject& obj)
|
||||
@ -111,9 +112,8 @@ void loadIndexedVersions(Modpack& pack, QJsonDocument& doc)
|
||||
unsortedVersions.append(file);
|
||||
}
|
||||
auto orderSortPredicate = [](const ModpackVersion& a, const ModpackVersion& b) -> bool {
|
||||
bool a_better_release = a.version_type <= b.version_type;
|
||||
// dates are in RFC 3339 format
|
||||
return a.date > b.date && a_better_release;
|
||||
return a.date > b.date;
|
||||
};
|
||||
|
||||
std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate);
|
||||
|
@ -57,6 +57,7 @@ struct File {
|
||||
QCryptographicHash::Algorithm hashAlgorithm;
|
||||
QByteArray hash;
|
||||
QQueue<QUrl> downloads;
|
||||
bool required = true;
|
||||
};
|
||||
|
||||
struct DonationData {
|
||||
|
@ -89,4 +89,4 @@ QNetworkReply* Download::getReply(QNetworkRequest& request)
|
||||
{
|
||||
return m_network->get(request);
|
||||
}
|
||||
} // namespace Net
|
||||
} // namespace Net
|
||||
|
@ -36,14 +36,20 @@
|
||||
*/
|
||||
|
||||
#include "NetJob.h"
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
#include "Application.h"
|
||||
#endif
|
||||
|
||||
NetJob::NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> network, int max_concurrent)
|
||||
: ConcurrentTask(nullptr,
|
||||
job_name,
|
||||
max_concurrent < 0 ? APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt() : max_concurrent)
|
||||
, m_network(network)
|
||||
{}
|
||||
: ConcurrentTask(nullptr, job_name), m_network(network)
|
||||
{
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
if (max_concurrent < 0)
|
||||
max_concurrent = APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt();
|
||||
#endif
|
||||
if (max_concurrent > 0)
|
||||
setMaxConcurrent(max_concurrent);
|
||||
}
|
||||
|
||||
auto NetJob::addNetAction(NetAction::Ptr action) -> bool
|
||||
{
|
||||
|
@ -47,6 +47,7 @@
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
#include "Application.h"
|
||||
#endif
|
||||
#include "BuildConfig.h"
|
||||
|
||||
#include "net/NetAction.h"
|
||||
|
||||
|
@ -206,7 +206,7 @@ void TranslationsModel::indexReceived()
|
||||
reloadLocalFiles();
|
||||
|
||||
auto language = d->m_system_locale;
|
||||
if (!findLanguage(language)) {
|
||||
if (!findLanguageAsOptional(language).has_value()) {
|
||||
language = d->m_system_language;
|
||||
}
|
||||
selectLanguage(language);
|
||||
@ -417,14 +417,17 @@ int TranslationsModel::columnCount([[maybe_unused]] const QModelIndex& parent) c
|
||||
return 2;
|
||||
}
|
||||
|
||||
Language* TranslationsModel::findLanguage(const QString& key)
|
||||
QVector<Language>::Iterator TranslationsModel::findLanguage(const QString& key)
|
||||
{
|
||||
auto found = std::find_if(d->m_languages.begin(), d->m_languages.end(), [&](Language& lang) { return lang.key == key; });
|
||||
if (found == d->m_languages.end()) {
|
||||
return nullptr;
|
||||
} else {
|
||||
return found;
|
||||
}
|
||||
return std::find_if(d->m_languages.begin(), d->m_languages.end(), [&](Language& lang) { return lang.key == key; });
|
||||
}
|
||||
|
||||
std::optional<Language> TranslationsModel::findLanguageAsOptional(const QString& key)
|
||||
{
|
||||
auto found = findLanguage(key);
|
||||
if (found != d->m_languages.end())
|
||||
return *found;
|
||||
return {};
|
||||
}
|
||||
|
||||
void TranslationsModel::setUseSystemLocale(bool useSystemLocale)
|
||||
@ -436,13 +439,13 @@ void TranslationsModel::setUseSystemLocale(bool useSystemLocale)
|
||||
bool TranslationsModel::selectLanguage(QString key)
|
||||
{
|
||||
QString& langCode = key;
|
||||
auto langPtr = findLanguage(key);
|
||||
auto langPtr = findLanguageAsOptional(key);
|
||||
|
||||
if (langCode.isEmpty()) {
|
||||
d->no_language_set = true;
|
||||
}
|
||||
|
||||
if (!langPtr) {
|
||||
if (!langPtr.has_value()) {
|
||||
qWarning() << "Selected invalid language" << key << ", defaulting to" << defaultLangCode;
|
||||
langCode = defaultLangCode;
|
||||
} else {
|
||||
@ -527,9 +530,8 @@ bool TranslationsModel::selectLanguage(QString key)
|
||||
QModelIndex TranslationsModel::selectedIndex()
|
||||
{
|
||||
auto found = findLanguage(d->m_selectedLanguage);
|
||||
if (found) {
|
||||
// QVector iterator freely converts to pointer to contained type
|
||||
return index(found - d->m_languages.begin(), 0, QModelIndex());
|
||||
if (found != d->m_languages.end()) {
|
||||
return index(std::distance(d->m_languages.begin(), found), 0, QModelIndex());
|
||||
}
|
||||
return QModelIndex();
|
||||
}
|
||||
@ -562,8 +564,8 @@ void TranslationsModel::updateLanguage(QString key)
|
||||
qWarning() << "Cannot update builtin language" << key;
|
||||
return;
|
||||
}
|
||||
auto found = findLanguage(key);
|
||||
if (!found) {
|
||||
auto found = findLanguageAsOptional(key);
|
||||
if (!found.has_value()) {
|
||||
qWarning() << "Cannot update invalid language" << key;
|
||||
return;
|
||||
}
|
||||
@ -578,8 +580,8 @@ void TranslationsModel::downloadTranslation(QString key)
|
||||
d->m_nextDownload = key;
|
||||
return;
|
||||
}
|
||||
auto lang = findLanguage(key);
|
||||
if (!lang) {
|
||||
auto lang = findLanguageAsOptional(key);
|
||||
if (!lang.has_value()) {
|
||||
qWarning() << "Will not download an unknown translation" << key;
|
||||
return;
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
struct Language;
|
||||
|
||||
@ -40,7 +41,8 @@ class TranslationsModel : public QAbstractListModel {
|
||||
void setUseSystemLocale(bool useSystemLocale);
|
||||
|
||||
private:
|
||||
Language* findLanguage(const QString& key);
|
||||
QVector<Language>::Iterator findLanguage(const QString& key);
|
||||
std::optional<Language> findLanguageAsOptional(const QString& key);
|
||||
void reloadLocalFiles();
|
||||
void downloadTranslation(QString key);
|
||||
void downloadNext();
|
||||
|
@ -218,7 +218,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
|
||||
ui->actionDISCORD->setVisible(!BuildConfig.DISCORD_URL.isEmpty());
|
||||
ui->actionREDDIT->setVisible(!BuildConfig.SUBREDDIT_URL.isEmpty());
|
||||
|
||||
ui->actionCheckUpdate->setVisible(BuildConfig.UPDATER_ENABLED);
|
||||
ui->actionCheckUpdate->setVisible(APPLICATION->updaterEnabled());
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
ui->actionAddToPATH->setVisible(false);
|
||||
@ -376,7 +376,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
|
||||
updateNewsLabel();
|
||||
}
|
||||
|
||||
if (BuildConfig.UPDATER_ENABLED) {
|
||||
if (APPLICATION->updaterEnabled()) {
|
||||
bool updatesAllowed = APPLICATION->updatesAreAllowed();
|
||||
updatesAllowedChanged(updatesAllowed);
|
||||
|
||||
@ -513,10 +513,10 @@ void MainWindow::showInstanceContextMenu(const QPoint& pos)
|
||||
} else {
|
||||
auto group = view->groupNameAt(pos);
|
||||
|
||||
QAction* actionVoid = new QAction(BuildConfig.LAUNCHER_DISPLAYNAME, this);
|
||||
QAction* actionVoid = new QAction(group.isNull() ? BuildConfig.LAUNCHER_DISPLAYNAME : group, this);
|
||||
actionVoid->setEnabled(false);
|
||||
|
||||
QAction* actionCreateInstance = new QAction(tr("Create instance"), this);
|
||||
QAction* actionCreateInstance = new QAction(tr("&Create instance"), this);
|
||||
actionCreateInstance->setToolTip(ui->actionAddInstance->toolTip());
|
||||
if (!group.isNull()) {
|
||||
QVariantMap instance_action_data;
|
||||
@ -530,12 +530,13 @@ void MainWindow::showInstanceContextMenu(const QPoint& pos)
|
||||
actions.prepend(actionVoid);
|
||||
actions.append(actionCreateInstance);
|
||||
if (!group.isNull()) {
|
||||
QAction* actionDeleteGroup = new QAction(tr("Delete group '%1'").arg(group), this);
|
||||
QVariantMap delete_group_action_data;
|
||||
delete_group_action_data["group"] = group;
|
||||
actionDeleteGroup->setData(delete_group_action_data);
|
||||
connect(actionDeleteGroup, SIGNAL(triggered(bool)), SLOT(deleteGroup()));
|
||||
QAction* actionDeleteGroup = new QAction(tr("&Delete group"), this);
|
||||
connect(actionDeleteGroup, &QAction::triggered, this, [this, group] { deleteGroup(group); });
|
||||
actions.append(actionDeleteGroup);
|
||||
|
||||
QAction* actionRenameGroup = new QAction(tr("&Rename group"), this);
|
||||
connect(actionRenameGroup, &QAction::triggered, this, [this, group] { renameGroup(group); });
|
||||
actions.append(actionRenameGroup);
|
||||
}
|
||||
}
|
||||
QMenu myMenu;
|
||||
@ -675,7 +676,7 @@ void MainWindow::repopulateAccountsMenu()
|
||||
|
||||
void MainWindow::updatesAllowedChanged(bool allowed)
|
||||
{
|
||||
if (!BuildConfig.UPDATER_ENABLED) {
|
||||
if (!APPLICATION->updaterEnabled()) {
|
||||
return;
|
||||
}
|
||||
ui->actionCheckUpdate->setEnabled(allowed);
|
||||
@ -1127,40 +1128,49 @@ void MainWindow::on_actionChangeInstGroup_triggered()
|
||||
if (!m_selectedInstance)
|
||||
return;
|
||||
|
||||
bool ok = false;
|
||||
InstanceId instId = m_selectedInstance->id();
|
||||
QString name(APPLICATION->instances()->getInstanceGroup(instId));
|
||||
auto groups = APPLICATION->instances()->getGroups();
|
||||
groups.insert(0, "");
|
||||
groups.sort(Qt::CaseInsensitive);
|
||||
int foo = groups.indexOf(name);
|
||||
QString src(APPLICATION->instances()->getInstanceGroup(instId));
|
||||
|
||||
QStringList groups = APPLICATION->instances()->getGroups();
|
||||
groups.prepend("");
|
||||
int index = groups.indexOf(src);
|
||||
bool ok = false;
|
||||
QString dst = QInputDialog::getItem(this, tr("Group name"), tr("Enter a new group name."), groups, index, true, &ok);
|
||||
dst = dst.simplified();
|
||||
|
||||
name = QInputDialog::getItem(this, tr("Group name"), tr("Enter a new group name."), groups, foo, true, &ok);
|
||||
name = name.simplified();
|
||||
if (ok) {
|
||||
APPLICATION->instances()->setInstanceGroup(instId, name);
|
||||
APPLICATION->instances()->setInstanceGroup(instId, dst);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::deleteGroup()
|
||||
void MainWindow::deleteGroup(QString group)
|
||||
{
|
||||
QObject* obj = sender();
|
||||
if (!obj)
|
||||
Q_ASSERT(!group.isEmpty());
|
||||
|
||||
const int reply = QMessageBox::question(this, tr("Delete group"), tr("Are you sure you want to delete the group '%1'?").arg(group),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
if (reply == QMessageBox::Yes)
|
||||
APPLICATION->instances()->deleteGroup(group);
|
||||
}
|
||||
|
||||
void MainWindow::renameGroup(QString group)
|
||||
{
|
||||
Q_ASSERT(!group.isEmpty());
|
||||
|
||||
QString name = QInputDialog::getText(this, tr("Rename group"), tr("Enter a new group name."), QLineEdit::Normal, group);
|
||||
name = name.simplified();
|
||||
if (name.isNull() || name == group)
|
||||
return;
|
||||
QAction* action = qobject_cast<QAction*>(obj);
|
||||
if (!action)
|
||||
|
||||
const bool empty = name.isEmpty();
|
||||
const bool duplicate = APPLICATION->instances()->getGroups().contains(name, Qt::CaseInsensitive) && group.toLower() != name.toLower();
|
||||
|
||||
if (empty || duplicate) {
|
||||
QMessageBox::warning(this, tr("Cannot rename group"), empty ? tr("Cannot set empty name.") : tr("Group already exists. :/"));
|
||||
return;
|
||||
auto map = action->data().toMap();
|
||||
if (!map.contains("group"))
|
||||
return;
|
||||
QString groupName = map["group"].toString();
|
||||
if (!groupName.isEmpty()) {
|
||||
auto reply = QMessageBox::question(this, tr("Delete group"), tr("Are you sure you want to delete the group %1?").arg(groupName),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
if (reply == QMessageBox::Yes) {
|
||||
APPLICATION->instances()->deleteGroup(groupName);
|
||||
}
|
||||
}
|
||||
|
||||
APPLICATION->instances()->renameGroup(group, name);
|
||||
}
|
||||
|
||||
void MainWindow::undoTrashInstance()
|
||||
@ -1212,7 +1222,7 @@ void MainWindow::refreshInstances()
|
||||
|
||||
void MainWindow::checkForUpdates()
|
||||
{
|
||||
if (BuildConfig.UPDATER_ENABLED) {
|
||||
if (APPLICATION->updaterEnabled()) {
|
||||
APPLICATION->triggerUpdateCheck();
|
||||
} else {
|
||||
qWarning() << "Updater not set up. Cannot check for updates.";
|
||||
|
@ -150,7 +150,8 @@ class MainWindow : public QMainWindow {
|
||||
|
||||
void on_actionDeleteInstance_triggered();
|
||||
|
||||
void deleteGroup();
|
||||
void deleteGroup(QString group);
|
||||
void renameGroup(QString group);
|
||||
void undoTrashInstance();
|
||||
|
||||
inline void on_actionExportInstance_triggered() { on_actionExportInstanceZip_triggered(); }
|
||||
|
@ -41,8 +41,8 @@
|
||||
#include <QPushButton>
|
||||
#include <QStandardPaths>
|
||||
|
||||
BlockedModsDialog::BlockedModsDialog(QWidget* parent, const QString& title, const QString& text, QList<BlockedMod>& mods)
|
||||
: QDialog(parent), ui(new Ui::BlockedModsDialog), m_mods(mods)
|
||||
BlockedModsDialog::BlockedModsDialog(QWidget* parent, const QString& title, const QString& text, QList<BlockedMod>& mods, QString hash_type)
|
||||
: QDialog(parent), ui(new Ui::BlockedModsDialog), m_mods(mods), m_hash_type(hash_type)
|
||||
{
|
||||
m_hashing_task = shared_qobject_ptr<ConcurrentTask>(
|
||||
new ConcurrentTask(this, "MakeHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()));
|
||||
@ -255,7 +255,7 @@ void BlockedModsDialog::addHashTask(QString path)
|
||||
/// @param path the path to the local file being hashed
|
||||
void BlockedModsDialog::buildHashTask(QString path)
|
||||
{
|
||||
auto hash_task = Hashing::createBlockedModHasher(path, ModPlatform::ResourceProvider::FLAME, "sha1");
|
||||
auto hash_task = Hashing::createBlockedModHasher(path, ModPlatform::ResourceProvider::FLAME, m_hash_type);
|
||||
|
||||
qDebug() << "[Blocked Mods Dialog] Creating Hash task for path: " << path;
|
||||
|
||||
@ -335,6 +335,13 @@ bool BlockedModsDialog::checkValidPath(QString path)
|
||||
|
||||
for (auto& mod : m_mods) {
|
||||
if (compare(filename, mod.name)) {
|
||||
// if the mod is not yet matched and doesn't have a hash then
|
||||
// just match it with the file that has the exact same name
|
||||
if (!mod.matched && mod.hash.isEmpty()) {
|
||||
mod.matched = true;
|
||||
mod.localPath = path;
|
||||
return false;
|
||||
}
|
||||
qDebug() << "[Blocked Mods Dialog] Name match found:" << mod.name << "| From path:" << path;
|
||||
return true;
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ class BlockedModsDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
BlockedModsDialog(QWidget* parent, const QString& title, const QString& text, QList<BlockedMod>& mods);
|
||||
BlockedModsDialog(QWidget* parent, const QString& title, const QString& text, QList<BlockedMod>& mods, QString hash_type = "sha1");
|
||||
|
||||
~BlockedModsDialog() override;
|
||||
|
||||
@ -73,6 +73,7 @@ class BlockedModsDialog : public QDialog {
|
||||
QSet<QString> m_pending_hash_paths;
|
||||
bool m_rehash_pending;
|
||||
QPushButton* m_openMissingButton;
|
||||
QString m_hash_type;
|
||||
|
||||
void openAll(bool missingOnly);
|
||||
void addDownloadFolder();
|
||||
|
@ -2,6 +2,7 @@
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* 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
|
||||
@ -61,22 +62,14 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget* parent)
|
||||
ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey));
|
||||
ui->instNameTextBox->setText(original->name());
|
||||
ui->instNameTextBox->setFocus();
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
auto groupList = APPLICATION->instances()->getGroups();
|
||||
QSet<QString> groups(groupList.begin(), groupList.end());
|
||||
groupList = QStringList(groups.values());
|
||||
#else
|
||||
auto groups = APPLICATION->instances()->getGroups().toSet();
|
||||
auto groupList = QStringList(groups.toList());
|
||||
#endif
|
||||
groupList.sort(Qt::CaseInsensitive);
|
||||
groupList.removeOne("");
|
||||
groupList.push_front("");
|
||||
ui->groupBox->addItems(groupList);
|
||||
int index = groupList.indexOf(APPLICATION->instances()->getInstanceGroup(m_original->id()));
|
||||
if (index == -1) {
|
||||
|
||||
QStringList groups = APPLICATION->instances()->getGroups();
|
||||
groups.prepend("");
|
||||
ui->groupBox->addItems(groups);
|
||||
int index = groups.indexOf(APPLICATION->instances()->getInstanceGroup(m_original->id()));
|
||||
if (index == -1)
|
||||
index = 0;
|
||||
}
|
||||
|
||||
ui->groupBox->setCurrentIndex(index);
|
||||
ui->groupBox->lineEdit()->setPlaceholderText(tr("No group"));
|
||||
ui->copySavesCheckbox->setChecked(m_selectedOptions.isCopySavesEnabled());
|
||||
|
@ -2,6 +2,7 @@
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* 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
|
||||
@ -75,23 +76,14 @@ NewInstanceDialog::NewInstanceDialog(const QString& initialGroup,
|
||||
InstIconKey = "default";
|
||||
ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey));
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
auto groupList = APPLICATION->instances()->getGroups();
|
||||
auto groups = QSet<QString>(groupList.begin(), groupList.end());
|
||||
groupList = groups.values();
|
||||
#else
|
||||
auto groups = APPLICATION->instances()->getGroups().toSet();
|
||||
auto groupList = QStringList(groups.toList());
|
||||
#endif
|
||||
groupList.sort(Qt::CaseInsensitive);
|
||||
groupList.removeOne("");
|
||||
groupList.push_front(initialGroup);
|
||||
groupList.push_front("");
|
||||
ui->groupBox->addItems(groupList);
|
||||
int index = groupList.indexOf(initialGroup);
|
||||
QStringList groups = APPLICATION->instances()->getGroups();
|
||||
groups.prepend("");
|
||||
int index = groups.indexOf(initialGroup);
|
||||
if (index == -1) {
|
||||
index = 0;
|
||||
index = 1;
|
||||
groups.insert(index, initialGroup);
|
||||
}
|
||||
ui->groupBox->addItems(groups);
|
||||
ui->groupBox->setCurrentIndex(index);
|
||||
ui->groupBox->lineEdit()->setPlaceholderText(tr("No group"));
|
||||
|
||||
@ -237,8 +229,7 @@ void NewInstanceDialog::setSuggestedIcon(const QString& key)
|
||||
|
||||
InstanceTask* NewInstanceDialog::extractTask()
|
||||
{
|
||||
InstanceTask* extracted = creationTask.get();
|
||||
creationTask.release();
|
||||
InstanceTask* extracted = creationTask.release();
|
||||
|
||||
InstanceName inst_name(ui->instNameTextBox->placeholderText().trimmed(), importVersion);
|
||||
inst_name.setName(ui->instNameTextBox->text().trimmed());
|
||||
|
@ -48,6 +48,9 @@
|
||||
<property name="text">
|
||||
<string>Global Task Status...</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
@ -109,8 +112,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>464</width>
|
||||
<height>96</height>
|
||||
<width>460</width>
|
||||
<height>108</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="taskProgressLayout">
|
||||
|
63
launcher/ui/dialogs/UpdateAvailableDialog.cpp
Normal file
63
launcher/ui/dialogs/UpdateAvailableDialog.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
// SPDX-FileCopyrightText: 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "UpdateAvailableDialog.h"
|
||||
#include <QPushButton>
|
||||
#include "Application.h"
|
||||
#include "BuildConfig.h"
|
||||
#include "Markdown.h"
|
||||
#include "ui_UpdateAvailableDialog.h"
|
||||
|
||||
UpdateAvailableDialog::UpdateAvailableDialog(const QString& currentVersion,
|
||||
const QString& availableVersion,
|
||||
const QString& releaseNotes,
|
||||
QWidget* parent)
|
||||
: QDialog(parent), ui(new Ui::UpdateAvailableDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
QString launcherName = BuildConfig.LAUNCHER_DISPLAYNAME;
|
||||
|
||||
ui->headerLabel->setText(tr("A new version of %1 is available!").arg(launcherName));
|
||||
ui->versionAvailableLabel->setText(
|
||||
tr("Version %1 is now available - you have %2 . Would you like to download it now?").arg(availableVersion).arg(currentVersion));
|
||||
ui->icon->setPixmap(APPLICATION->getThemedIcon("checkupdate").pixmap(64));
|
||||
|
||||
auto releaseNotesHtml = markdownToHTML(releaseNotes);
|
||||
ui->releaseNotes->setHtml(releaseNotesHtml);
|
||||
ui->releaseNotes->setOpenExternalLinks(true);
|
||||
|
||||
connect(ui->skipButton, &QPushButton::clicked, this, [this]() {
|
||||
setResult(ResultCode::Skip);
|
||||
done(ResultCode::Skip);
|
||||
});
|
||||
|
||||
connect(ui->delayButton, &QPushButton::clicked, this, [this]() {
|
||||
setResult(ResultCode::DontInstall);
|
||||
done(ResultCode::DontInstall);
|
||||
});
|
||||
|
||||
connect(ui->installButton, &QPushButton::clicked, this, [this]() {
|
||||
setResult(ResultCode::Install);
|
||||
done(ResultCode::Install);
|
||||
});
|
||||
}
|
48
launcher/ui/dialogs/UpdateAvailableDialog.h
Normal file
48
launcher/ui/dialogs/UpdateAvailableDialog.h
Normal file
@ -0,0 +1,48 @@
|
||||
// SPDX-FileCopyrightText: 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
namespace Ui {
|
||||
class UpdateAvailableDialog;
|
||||
}
|
||||
|
||||
class UpdateAvailableDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum ResultCode {
|
||||
Install = 10,
|
||||
DontInstall = 11,
|
||||
Skip = 12,
|
||||
};
|
||||
|
||||
explicit UpdateAvailableDialog(const QString& currentVersion,
|
||||
const QString& availableVersion,
|
||||
const QString& releaseNotes,
|
||||
QWidget* parent = 0);
|
||||
~UpdateAvailableDialog() = default;
|
||||
|
||||
private:
|
||||
Ui::UpdateAvailableDialog* ui;
|
||||
};
|
155
launcher/ui/dialogs/UpdateAvailableDialog.ui
Normal file
155
launcher/ui/dialogs/UpdateAvailableDialog.ui
Normal file
@ -0,0 +1,155 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>UpdateAvailableDialog</class>
|
||||
<widget class="QDialog" name="UpdateAvailableDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>636</width>
|
||||
<height>352</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Update Available</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="leftsideLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="icon">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="mainLayout">
|
||||
<property name="leftMargin">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="headerLabel">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>11</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>A new version is available!</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="versionAvailableLabel">
|
||||
<property name="text">
|
||||
<string>Version %1 is now available - you have %2 . Would you like to download it now?</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="releaseNotesLabel">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Release Notes:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTextBrowser" name="releaseNotes"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="skipButton">
|
||||
<property name="text">
|
||||
<string>Skip This Version</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="delayButton">
|
||||
<property name="text">
|
||||
<string>Remind Me Later</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="installButton">
|
||||
<property name="text">
|
||||
<string>Install Update</string>
|
||||
</property>
|
||||
<property name="default">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -119,6 +119,9 @@ void MinecraftPage::applySettings()
|
||||
// Miscellaneous
|
||||
s->set("CloseAfterLaunch", ui->closeAfterLaunchCheck->isChecked());
|
||||
s->set("QuitAfterGameStop", ui->quitAfterGameStopCheck->isChecked());
|
||||
|
||||
// Legacy settings
|
||||
s->set("OnlineFixes", ui->onlineFixes->isChecked());
|
||||
}
|
||||
|
||||
void MinecraftPage::loadSettings()
|
||||
@ -170,6 +173,8 @@ void MinecraftPage::loadSettings()
|
||||
|
||||
ui->closeAfterLaunchCheck->setChecked(s->get("CloseAfterLaunch").toBool());
|
||||
ui->quitAfterGameStopCheck->setChecked(s->get("QuitAfterGameStop").toBool());
|
||||
|
||||
ui->onlineFixes->setChecked(s->get("OnlineFixes").toBool());
|
||||
}
|
||||
|
||||
void MinecraftPage::retranslate()
|
||||
|
@ -138,7 +138,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showGameTimeWithoutDays">
|
||||
<property name="text">
|
||||
<string>Show time spent playing in hours</string>
|
||||
@ -197,6 +197,25 @@
|
||||
<string>Tweaks</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_12">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="legacySettingsGroupBox">
|
||||
<property name="title">
|
||||
<string>Legacy settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="onlineFixes">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Emulates usages of old online services which are no longer operating.</p><p>This currently allows modern skins to be used.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enable online fixes (experimental)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="nativeLibWorkaroundGroupBox">
|
||||
<property name="title">
|
||||
|
@ -3,6 +3,7 @@
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* 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
|
||||
@ -253,6 +254,14 @@ void InstanceSettingsPage::applySettings()
|
||||
m_settings->reset("InstanceAccountId");
|
||||
}
|
||||
|
||||
bool overrideLegacySettings = ui->legacySettingsGroupBox->isChecked();
|
||||
m_settings->set("OverrideLegacySettings", overrideLegacySettings);
|
||||
if (overrideLegacySettings) {
|
||||
m_settings->set("OnlineFixes", ui->onlineFixes->isChecked());
|
||||
} else {
|
||||
m_settings->reset("OnlineFixes");
|
||||
}
|
||||
|
||||
// FIXME: This should probably be called by a signal instead
|
||||
m_instance->updateRuntimeContext();
|
||||
}
|
||||
@ -356,6 +365,9 @@ void InstanceSettingsPage::loadSettings()
|
||||
|
||||
ui->instanceAccountGroupBox->setChecked(m_settings->get("UseAccountForInstance").toBool());
|
||||
updateAccountsMenu();
|
||||
|
||||
ui->legacySettingsGroupBox->setChecked(m_settings->get("OverrideLegacySettings").toBool());
|
||||
ui->onlineFixes->setChecked(m_settings->get("OnlineFixes").toBool());
|
||||
}
|
||||
|
||||
void InstanceSettingsPage::on_javaDetectBtn_clicked()
|
||||
|
@ -583,6 +583,31 @@
|
||||
<string>Miscellaneous</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_9">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="legacySettingsGroupBox">
|
||||
<property name="title">
|
||||
<string>Legacy settings</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_17">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="onlineFixes">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Emulates usages of old online services which are no longer operating.</p><p>This currently allows modern skins to be used.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enable online fixes (experimental)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gameTimeGroupBox">
|
||||
<property name="enabled">
|
||||
|
63
launcher/ui/pages/modplatform/OptionalModDialog.cpp
Normal file
63
launcher/ui/pages/modplatform/OptionalModDialog.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#include "OptionalModDialog.h"
|
||||
#include "ui_OptionalModDialog.h"
|
||||
|
||||
OptionalModDialog::OptionalModDialog(QWidget* parent, const QStringList& mods) : QDialog(parent), ui(new Ui::OptionalModDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
for (const QString& mod : mods) {
|
||||
auto item = new QListWidgetItem(mod, ui->list);
|
||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
item->setCheckState(Qt::Unchecked);
|
||||
item->setData(Qt::UserRole, mod);
|
||||
}
|
||||
|
||||
connect(ui->selectAllButton, &QPushButton::clicked, ui->list, [this] {
|
||||
for (int i = 0; i < ui->list->count(); i++)
|
||||
ui->list->item(i)->setCheckState(Qt::Checked);
|
||||
});
|
||||
connect(ui->clearAllButton, &QPushButton::clicked, ui->list, [this] {
|
||||
for (int i = 0; i < ui->list->count(); i++)
|
||||
ui->list->item(i)->setCheckState(Qt::Unchecked);
|
||||
});
|
||||
connect(ui->list, &QListWidget::itemActivated, [](QListWidgetItem* item) {
|
||||
if (item->checkState() == Qt::Checked)
|
||||
item->setCheckState(Qt::Unchecked);
|
||||
else
|
||||
item->setCheckState(Qt::Checked);
|
||||
});
|
||||
}
|
||||
|
||||
OptionalModDialog::~OptionalModDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
QStringList OptionalModDialog::getResult()
|
||||
{
|
||||
QStringList result;
|
||||
result.reserve(ui->list->count());
|
||||
for (int i = 0; i < ui->list->count(); i++) {
|
||||
auto item = ui->list->item(i);
|
||||
if (item->checkState() == Qt::Checked)
|
||||
result.append(item->data(Qt::UserRole).toString());
|
||||
}
|
||||
return result;
|
||||
}
|
39
launcher/ui/pages/modplatform/OptionalModDialog.h
Normal file
39
launcher/ui/pages/modplatform/OptionalModDialog.h
Normal file
@ -0,0 +1,39 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QDialog>
|
||||
|
||||
namespace Ui {
|
||||
class OptionalModDialog;
|
||||
}
|
||||
|
||||
class OptionalModDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
OptionalModDialog(QWidget* parent, const QStringList& mods);
|
||||
~OptionalModDialog() override;
|
||||
|
||||
QStringList getResult();
|
||||
|
||||
private:
|
||||
Ui::OptionalModDialog* ui;
|
||||
};
|
113
launcher/ui/pages/modplatform/OptionalModDialog.ui
Normal file
113
launcher/ui/pages/modplatform/OptionalModDialog.ui
Normal file
@ -0,0 +1,113 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>OptionalModDialog</class>
|
||||
<widget class="QDialog" name="OptionalModDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>550</width>
|
||||
<height>310</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Select Optional Mods</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="1" column="0">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QListWidget" name="list">
|
||||
<property name="defaultDropAction">
|
||||
<enum>Qt::IgnoreAction</enum>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="selectAllButton">
|
||||
<property name="text">
|
||||
<string>Select All</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="clearAllButton">
|
||||
<property name="text">
|
||||
<string>Deselect All</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Unchecked mods will be disabled.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>OptionalModDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>274</x>
|
||||
<y>284</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>274</x>
|
||||
<y>154</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>OptionalModDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>274</x>
|
||||
<y>284</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>274</x>
|
||||
<y>154</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
@ -63,7 +63,7 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
|
||||
}
|
||||
auto icon = APPLICATION->getThemedIcon("atlauncher-placeholder");
|
||||
|
||||
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(pack.safeName.toLower());
|
||||
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1").arg(pack.safeName);
|
||||
((ListModel*)this)->requestLogo(pack.safeName, url);
|
||||
|
||||
return icon;
|
||||
|
@ -38,7 +38,7 @@
|
||||
#include <QAbstractListModel>
|
||||
#include <QDialog>
|
||||
|
||||
#include "modplatform/atlauncher/ATLPackIndex.h"
|
||||
#include "modplatform/atlauncher/ATLPackManifest.h"
|
||||
#include "net/NetJob.h"
|
||||
|
||||
namespace Ui {
|
||||
|
@ -114,8 +114,8 @@ void AtlPage::suggestCurrent()
|
||||
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());
|
||||
auto editedLogoName = "atl_" + selected.safeName;
|
||||
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1").arg(selected.safeName);
|
||||
listModel->getLogo(selected.safeName, url,
|
||||
[this, editedLogoName](QString logo) { dialog->setSuggestedIconFromFile(logo, editedLogoName); });
|
||||
}
|
||||
|
@ -228,8 +228,7 @@ void FlamePage::suggestCurrent()
|
||||
extra_info.insert("pack_version_id", QString::number(version.fileId));
|
||||
|
||||
dialog->setSuggestedPack(current.name, new InstanceImportTask(version.downloadUrl, this, std::move(extra_info)));
|
||||
QString editedLogoName;
|
||||
editedLogoName = "curseforge_" + current.logoName;
|
||||
QString editedLogoName = "curseforge_" + current.logoName;
|
||||
listModel->getLogo(current.logoName, current.logoUrl,
|
||||
[this, editedLogoName](QString logo) { dialog->setSuggestedIconFromFile(logo, editedLogoName); });
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ void ImportFTBPage::suggestCurrent()
|
||||
}
|
||||
|
||||
dialog->setSuggestedPack(selected.name, new PackInstallTask(selected));
|
||||
QString editedLogoName = QString("ftb_%1").arg(selected.id);
|
||||
QString editedLogoName = QString("ftb_%1_%2,jpg").arg(selected.name, selected.id);
|
||||
dialog->setSuggestedIconFromFile(FS::PathCombine(selected.path, "folder.jpg"), editedLogoName);
|
||||
}
|
||||
|
||||
|
@ -179,15 +179,11 @@ void Page::suggestCurrent()
|
||||
}
|
||||
|
||||
dialog->setSuggestedPack(selected.name, selectedVersion, new PackInstallTask(APPLICATION->network(), selected, selectedVersion));
|
||||
QString editedLogoName;
|
||||
if (selected.logo.toLower().startsWith("ftb")) {
|
||||
editedLogoName = selected.logo;
|
||||
} else {
|
||||
editedLogoName = "ftb_" + selected.logo;
|
||||
QString editedLogoName = selected.logo;
|
||||
if (!selected.logo.toLower().startsWith("ftb")) {
|
||||
editedLogoName = "ftb_" + editedLogoName;
|
||||
}
|
||||
|
||||
editedLogoName = editedLogoName.left(editedLogoName.lastIndexOf(".png"));
|
||||
|
||||
if (selected.type == PackType::Public) {
|
||||
publicListModel->getLogo(selected.logo,
|
||||
[this, editedLogoName](QString logo) { dialog->setSuggestedIconFromFile(logo, editedLogoName); });
|
||||
|
@ -41,7 +41,9 @@
|
||||
#include "net/ApiDownload.h"
|
||||
#include "ui/widgets/ProjectItem.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QIcon>
|
||||
#include <QUrl>
|
||||
|
||||
Technic::ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {}
|
||||
|
||||
@ -193,7 +195,7 @@ void Technic::ListModel::searchRequestFinished()
|
||||
pack.logoName = "null";
|
||||
} else {
|
||||
pack.logoUrl = rawURL;
|
||||
pack.logoName = rawURL.section(QLatin1Char('/'), -1);
|
||||
pack.logoName = pack.slug + "." + QFileInfo(QUrl(rawURL).fileName()).suffix();
|
||||
}
|
||||
pack.broken = false;
|
||||
newList.append(pack);
|
||||
@ -215,7 +217,7 @@ void Technic::ListModel::searchRequestFinished()
|
||||
auto iconUrl = Json::requireString(iconObj, "url");
|
||||
|
||||
pack.logoUrl = iconUrl;
|
||||
pack.logoName = iconUrl.section(QLatin1Char('/'), -1);
|
||||
pack.logoName = pack.slug + "." + QFileInfo(QUrl(iconUrl).fileName()).suffix();
|
||||
} else {
|
||||
pack.logoUrl = "null";
|
||||
pack.logoName = "null";
|
||||
|
354
launcher/updater/PrismExternalUpdater.cpp
Normal file
354
launcher/updater/PrismExternalUpdater.cpp
Normal file
@ -0,0 +1,354 @@
|
||||
// SPDX-FileCopyrightText: 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "PrismExternalUpdater.h"
|
||||
#include <QCoreApplication>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QMessageBox>
|
||||
#include <QProcess>
|
||||
#include <QProgressDialog>
|
||||
#include <QSettings>
|
||||
#include <QTimer>
|
||||
#include <memory>
|
||||
|
||||
#include "StringUtils.h"
|
||||
|
||||
#include "BuildConfig.h"
|
||||
|
||||
#include "ui/dialogs/UpdateAvailableDialog.h"
|
||||
|
||||
class PrismExternalUpdater::Private {
|
||||
public:
|
||||
QDir appDir;
|
||||
QDir dataDir;
|
||||
QTimer updateTimer;
|
||||
bool allowBeta;
|
||||
bool autoCheck;
|
||||
double updateInterval;
|
||||
QDateTime lastCheck;
|
||||
std::unique_ptr<QSettings> settings;
|
||||
|
||||
QWidget* parent;
|
||||
};
|
||||
|
||||
PrismExternalUpdater::PrismExternalUpdater(QWidget* parent, const QString& appDir, const QString& dataDir)
|
||||
{
|
||||
priv = new PrismExternalUpdater::Private();
|
||||
priv->appDir = QDir(appDir);
|
||||
priv->dataDir = QDir(dataDir);
|
||||
auto settings_file = priv->dataDir.absoluteFilePath("prismlauncher_update.cfg");
|
||||
priv->settings = std::make_unique<QSettings>(settings_file, QSettings::Format::IniFormat);
|
||||
priv->allowBeta = priv->settings->value("allow_beta", false).toBool();
|
||||
priv->autoCheck = priv->settings->value("auto_check", false).toBool();
|
||||
bool interval_ok;
|
||||
// default once per day
|
||||
priv->updateInterval = priv->settings->value("update_interval", 86400).toInt(&interval_ok);
|
||||
if (!interval_ok)
|
||||
priv->updateInterval = 86400;
|
||||
auto last_check = priv->settings->value("last_check");
|
||||
if (!last_check.isNull() && last_check.isValid()) {
|
||||
priv->lastCheck = QDateTime::fromString(last_check.toString(), Qt::ISODate);
|
||||
}
|
||||
priv->parent = parent;
|
||||
connectTimer();
|
||||
resetAutoCheckTimer();
|
||||
}
|
||||
|
||||
PrismExternalUpdater::~PrismExternalUpdater()
|
||||
{
|
||||
if (priv->updateTimer.isActive())
|
||||
priv->updateTimer.stop();
|
||||
disconnectTimer();
|
||||
priv->settings->sync();
|
||||
delete priv;
|
||||
}
|
||||
|
||||
void PrismExternalUpdater::checkForUpdates()
|
||||
{
|
||||
QProgressDialog progress(tr("Checking for updates..."), "", 0, 0, priv->parent);
|
||||
progress.setCancelButton(nullptr);
|
||||
progress.adjustSize();
|
||||
progress.show();
|
||||
QCoreApplication::processEvents();
|
||||
|
||||
QProcess proc;
|
||||
auto exe_name = QStringLiteral("%1_updater").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME);
|
||||
#if defined Q_OS_WIN32
|
||||
exe_name.append(".exe");
|
||||
|
||||
auto env = QProcessEnvironment::systemEnvironment();
|
||||
env.insert("__COMPAT_LAYER", "RUNASINVOKER");
|
||||
proc.setProcessEnvironment(env);
|
||||
#else
|
||||
exe_name = QString("bin/%1").arg(exe_name);
|
||||
#endif
|
||||
|
||||
QStringList args = { "--check-only", "--dir", priv->dataDir.absolutePath(), "--debug" };
|
||||
if (priv->allowBeta)
|
||||
args.append("--pre-release");
|
||||
|
||||
proc.start(priv->appDir.absoluteFilePath(exe_name), args);
|
||||
auto result_start = proc.waitForStarted(5000);
|
||||
if (!result_start) {
|
||||
auto err = proc.error();
|
||||
qDebug() << "Failed to start updater after 5 seconds."
|
||||
<< "reason:" << err << proc.errorString();
|
||||
auto msgBox =
|
||||
QMessageBox(QMessageBox::Information, tr("Update Check Failed"),
|
||||
tr("Failed to start after 5 seconds\nReason: %1.").arg(proc.errorString()), QMessageBox::Ok, priv->parent);
|
||||
msgBox.setMinimumWidth(460);
|
||||
msgBox.adjustSize();
|
||||
msgBox.exec();
|
||||
priv->lastCheck = QDateTime::currentDateTime();
|
||||
priv->settings->setValue("last_check", priv->lastCheck.toString(Qt::ISODate));
|
||||
priv->settings->sync();
|
||||
resetAutoCheckTimer();
|
||||
return;
|
||||
}
|
||||
QCoreApplication::processEvents();
|
||||
|
||||
auto result_finished = proc.waitForFinished(60000);
|
||||
if (!result_finished) {
|
||||
proc.kill();
|
||||
auto err = proc.error();
|
||||
auto output = proc.readAll();
|
||||
qDebug() << "Updater failed to close after 60 seconds."
|
||||
<< "reason:" << err << proc.errorString();
|
||||
auto msgBox =
|
||||
QMessageBox(QMessageBox::Information, tr("Update Check Failed"),
|
||||
tr("Updater failed to close 60 seconds\nReason: %1.").arg(proc.errorString()), QMessageBox::Ok, priv->parent);
|
||||
msgBox.setDetailedText(output);
|
||||
msgBox.setMinimumWidth(460);
|
||||
msgBox.adjustSize();
|
||||
msgBox.exec();
|
||||
priv->lastCheck = QDateTime::currentDateTime();
|
||||
priv->settings->setValue("last_check", priv->lastCheck.toString(Qt::ISODate));
|
||||
priv->settings->sync();
|
||||
resetAutoCheckTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
auto exit_code = proc.exitCode();
|
||||
|
||||
auto std_output = proc.readAllStandardOutput();
|
||||
auto std_error = proc.readAllStandardError();
|
||||
|
||||
progress.hide();
|
||||
QCoreApplication::processEvents();
|
||||
|
||||
switch (exit_code) {
|
||||
case 0:
|
||||
// no update available
|
||||
{
|
||||
qDebug() << "No update available";
|
||||
auto msgBox = QMessageBox(QMessageBox::Information, tr("No Update Available"), tr("You are running the latest version."),
|
||||
QMessageBox::Ok, priv->parent);
|
||||
msgBox.setMinimumWidth(460);
|
||||
msgBox.adjustSize();
|
||||
msgBox.exec();
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
// there was an error
|
||||
{
|
||||
qDebug() << "Updater subprocess error" << qPrintable(std_error);
|
||||
auto msgBox = QMessageBox(QMessageBox::Warning, tr("Update Check Error"),
|
||||
tr("There was an error running the update check."), QMessageBox::Ok, priv->parent);
|
||||
msgBox.setDetailedText(QString(std_error));
|
||||
msgBox.setMinimumWidth(460);
|
||||
msgBox.adjustSize();
|
||||
msgBox.exec();
|
||||
}
|
||||
break;
|
||||
case 100:
|
||||
// update available
|
||||
{
|
||||
auto [first_line, remainder1] = StringUtils::splitFirst(std_output, '\n');
|
||||
auto [second_line, remainder2] = StringUtils::splitFirst(remainder1, '\n');
|
||||
auto [third_line, release_notes] = StringUtils::splitFirst(remainder2, '\n');
|
||||
auto version_name = StringUtils::splitFirst(first_line, ": ").second.trimmed();
|
||||
auto version_tag = StringUtils::splitFirst(second_line, ": ").second.trimmed();
|
||||
auto release_timestamp = QDateTime::fromString(StringUtils::splitFirst(third_line, ": ").second.trimmed(), Qt::ISODate);
|
||||
qDebug() << "Update available:" << version_name << version_tag << release_timestamp;
|
||||
qDebug() << "Update release notes:" << release_notes;
|
||||
|
||||
offerUpdate(version_name, version_tag, release_notes);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// unknown error code
|
||||
{
|
||||
qDebug() << "Updater exited with unknown code" << exit_code;
|
||||
auto msgBox =
|
||||
QMessageBox(QMessageBox::Information, tr("Unknown Update Error"),
|
||||
tr("The updater exited with an unknown condition.\nExit Code: %1").arg(QString::number(exit_code)),
|
||||
QMessageBox::Ok, priv->parent);
|
||||
auto detail_txt = tr("StdOut: %1\nStdErr: %2").arg(QString(std_output)).arg(QString(std_error));
|
||||
msgBox.setDetailedText(detail_txt);
|
||||
msgBox.setMinimumWidth(460);
|
||||
msgBox.adjustSize();
|
||||
msgBox.exec();
|
||||
}
|
||||
}
|
||||
priv->lastCheck = QDateTime::currentDateTime();
|
||||
priv->settings->setValue("last_check", priv->lastCheck.toString(Qt::ISODate));
|
||||
priv->settings->sync();
|
||||
resetAutoCheckTimer();
|
||||
}
|
||||
|
||||
bool PrismExternalUpdater::getAutomaticallyChecksForUpdates()
|
||||
{
|
||||
return priv->autoCheck;
|
||||
}
|
||||
|
||||
double PrismExternalUpdater::getUpdateCheckInterval()
|
||||
{
|
||||
return priv->updateInterval;
|
||||
}
|
||||
|
||||
bool PrismExternalUpdater::getBetaAllowed()
|
||||
{
|
||||
return priv->allowBeta;
|
||||
}
|
||||
|
||||
void PrismExternalUpdater::setAutomaticallyChecksForUpdates(bool check)
|
||||
{
|
||||
priv->autoCheck = check;
|
||||
priv->settings->setValue("auto_check", check);
|
||||
priv->settings->sync();
|
||||
resetAutoCheckTimer();
|
||||
}
|
||||
|
||||
void PrismExternalUpdater::setUpdateCheckInterval(double seconds)
|
||||
{
|
||||
priv->updateInterval = seconds;
|
||||
priv->settings->setValue("update_interval", seconds);
|
||||
priv->settings->sync();
|
||||
resetAutoCheckTimer();
|
||||
}
|
||||
|
||||
void PrismExternalUpdater::setBetaAllowed(bool allowed)
|
||||
{
|
||||
priv->allowBeta = allowed;
|
||||
priv->settings->setValue("auto_beta", allowed);
|
||||
priv->settings->sync();
|
||||
}
|
||||
|
||||
void PrismExternalUpdater::resetAutoCheckTimer()
|
||||
{
|
||||
if (priv->autoCheck) {
|
||||
int timeoutDuration = 0;
|
||||
auto now = QDateTime::currentDateTime();
|
||||
if (priv->lastCheck.isValid()) {
|
||||
auto diff = priv->lastCheck.secsTo(now);
|
||||
auto secs_left = priv->updateInterval - diff;
|
||||
if (secs_left < 0)
|
||||
secs_left = 0;
|
||||
timeoutDuration = secs_left * 1000; // to msec
|
||||
}
|
||||
qDebug() << "Auto update timer starting," << timeoutDuration / 1000 << "seconds left";
|
||||
priv->updateTimer.start(timeoutDuration);
|
||||
} else {
|
||||
if (priv->updateTimer.isActive())
|
||||
priv->updateTimer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
void PrismExternalUpdater::connectTimer()
|
||||
{
|
||||
connect(&priv->updateTimer, &QTimer::timeout, this, &PrismExternalUpdater::autoCheckTimerFired);
|
||||
}
|
||||
|
||||
void PrismExternalUpdater::disconnectTimer()
|
||||
{
|
||||
disconnect(&priv->updateTimer, &QTimer::timeout, this, &PrismExternalUpdater::autoCheckTimerFired);
|
||||
}
|
||||
|
||||
void PrismExternalUpdater::autoCheckTimerFired()
|
||||
{
|
||||
qDebug() << "Auto update Timer fired";
|
||||
checkForUpdates();
|
||||
}
|
||||
|
||||
void PrismExternalUpdater::offerUpdate(const QString& version_name, const QString& version_tag, const QString& release_notes)
|
||||
{
|
||||
priv->settings->beginGroup("skip");
|
||||
auto should_skip = priv->settings->value(version_tag, false).toBool();
|
||||
priv->settings->endGroup();
|
||||
|
||||
if (should_skip) {
|
||||
auto msgBox = QMessageBox(QMessageBox::Information, tr("No Update Available"), tr("There are no new updates available."),
|
||||
QMessageBox::Ok, priv->parent);
|
||||
msgBox.setMinimumWidth(460);
|
||||
msgBox.adjustSize();
|
||||
msgBox.exec();
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateAvailableDialog dlg(BuildConfig.printableVersionString(), version_name, release_notes);
|
||||
|
||||
auto result = dlg.exec();
|
||||
qDebug() << "offer dlg result" << result;
|
||||
switch (result) {
|
||||
case UpdateAvailableDialog::Install: {
|
||||
performUpdate(version_tag);
|
||||
return;
|
||||
}
|
||||
case UpdateAvailableDialog::Skip: {
|
||||
priv->settings->beginGroup("skip");
|
||||
priv->settings->setValue(version_tag, true);
|
||||
priv->settings->endGroup();
|
||||
priv->settings->sync();
|
||||
return;
|
||||
}
|
||||
case UpdateAvailableDialog::DontInstall: {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PrismExternalUpdater::performUpdate(const QString& version_tag)
|
||||
{
|
||||
QProcess proc;
|
||||
auto exe_name = QStringLiteral("%1_updater").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME);
|
||||
#if defined Q_OS_WIN32
|
||||
exe_name.append(".exe");
|
||||
|
||||
auto env = QProcessEnvironment::systemEnvironment();
|
||||
env.insert("__COMPAT_LAYER", "RUNASINVOKER");
|
||||
proc.setProcessEnvironment(env);
|
||||
#else
|
||||
exe_name = QString("bin/%1").arg(exe_name);
|
||||
#endif
|
||||
|
||||
QStringList args = { "--dir", priv->dataDir.absolutePath(), "--install-version", version_tag };
|
||||
if (priv->allowBeta)
|
||||
args.append("--pre-release");
|
||||
|
||||
auto result = proc.startDetached(priv->appDir.absoluteFilePath(exe_name), args);
|
||||
if (!result) {
|
||||
qDebug() << "Failed to start updater:" << proc.error() << proc.errorString();
|
||||
}
|
||||
QCoreApplication::exit();
|
||||
}
|
95
launcher/updater/PrismExternalUpdater.h
Normal file
95
launcher/updater/PrismExternalUpdater.h
Normal file
@ -0,0 +1,95 @@
|
||||
// SPDX-FileCopyrightText: 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "ExternalUpdater.h"
|
||||
|
||||
/*!
|
||||
* An implementation for the updater on windows and linux that uses out external updater.
|
||||
*/
|
||||
|
||||
class PrismExternalUpdater : public ExternalUpdater {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PrismExternalUpdater(QWidget* parent, const QString& appDir, const QString& dataDir);
|
||||
~PrismExternalUpdater() override;
|
||||
|
||||
/*!
|
||||
* Check for updates manually, showing the user a progress bar and an alert if no updates are found.
|
||||
*/
|
||||
void checkForUpdates() override;
|
||||
|
||||
/*!
|
||||
* Indicates whether or not to check for updates automatically.
|
||||
*/
|
||||
bool getAutomaticallyChecksForUpdates() override;
|
||||
|
||||
/*!
|
||||
* Indicates the current automatic update check interval in seconds.
|
||||
*/
|
||||
double getUpdateCheckInterval() override;
|
||||
|
||||
/*!
|
||||
* Indicates whether or not beta updates should be checked for in addition to regular releases.
|
||||
*/
|
||||
bool getBetaAllowed() override;
|
||||
|
||||
/*!
|
||||
* Set whether or not to check for updates automatically.
|
||||
*
|
||||
* The update schedule cycle will be reset in a short delay after the property’s new value is set. This is to allow
|
||||
* reverting this property without kicking off a schedule change immediately."
|
||||
*/
|
||||
void setAutomaticallyChecksForUpdates(bool check) override;
|
||||
|
||||
/*!
|
||||
* Set the current automatic update check interval in seconds.
|
||||
*
|
||||
* The update schedule cycle will be reset in a short delay after the property’s new value is set. This is to allow
|
||||
* reverting this property without kicking off a schedule change immediately."
|
||||
*/
|
||||
void setUpdateCheckInterval(double seconds) override;
|
||||
|
||||
/*!
|
||||
* Set whether or not beta updates should be checked for in addition to regular releases.
|
||||
*/
|
||||
void setBetaAllowed(bool allowed) override;
|
||||
|
||||
void resetAutoCheckTimer();
|
||||
void disconnectTimer();
|
||||
void connectTimer();
|
||||
|
||||
void offerUpdate(const QString& version_name, const QString& version_tag, const QString& release_notes);
|
||||
void performUpdate(const QString& version_tag);
|
||||
|
||||
public slots:
|
||||
void autoCheckTimerFired();
|
||||
|
||||
private:
|
||||
class Private;
|
||||
|
||||
Private* priv;
|
||||
};
|
93
launcher/updater/prismupdater/GitHubRelease.cpp
Normal file
93
launcher/updater/prismupdater/GitHubRelease.cpp
Normal file
@ -0,0 +1,93 @@
|
||||
// SPDX-FileCopyrightText: 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "GitHubRelease.h"
|
||||
|
||||
QDebug operator<<(QDebug debug, const GitHubReleaseAsset& asset)
|
||||
{
|
||||
QDebugStateSaver saver(debug);
|
||||
debug.nospace() << "GitHubReleaseAsset( "
|
||||
"id: "
|
||||
<< asset.id
|
||||
<< ", "
|
||||
"name "
|
||||
<< asset.name
|
||||
<< ", "
|
||||
"label: "
|
||||
<< asset.label
|
||||
<< ", "
|
||||
"content_type: "
|
||||
<< asset.content_type
|
||||
<< ", "
|
||||
"size: "
|
||||
<< asset.size
|
||||
<< ", "
|
||||
"created_at: "
|
||||
<< asset.created_at
|
||||
<< ", "
|
||||
"updated_at: "
|
||||
<< asset.updated_at
|
||||
<< ", "
|
||||
"browser_download_url: "
|
||||
<< asset.browser_download_url
|
||||
<< " "
|
||||
")";
|
||||
return debug;
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug debug, const GitHubRelease& rls)
|
||||
{
|
||||
QDebugStateSaver saver(debug);
|
||||
debug.nospace() << "GitHubRelease( "
|
||||
"id: "
|
||||
<< rls.id
|
||||
<< ", "
|
||||
"name "
|
||||
<< rls.name
|
||||
<< ", "
|
||||
"tag_name: "
|
||||
<< rls.tag_name
|
||||
<< ", "
|
||||
"created_at: "
|
||||
<< rls.created_at
|
||||
<< ", "
|
||||
"published_at: "
|
||||
<< rls.published_at
|
||||
<< ", "
|
||||
"prerelease: "
|
||||
<< rls.prerelease
|
||||
<< ", "
|
||||
"draft: "
|
||||
<< rls.draft
|
||||
<< ", "
|
||||
"version"
|
||||
<< rls.version
|
||||
<< ", "
|
||||
"body: "
|
||||
<< rls.body
|
||||
<< ", "
|
||||
"assets: "
|
||||
<< rls.assets
|
||||
<< " "
|
||||
")";
|
||||
return debug;
|
||||
}
|
61
launcher/updater/prismupdater/GitHubRelease.h
Normal file
61
launcher/updater/prismupdater/GitHubRelease.h
Normal file
@ -0,0 +1,61 @@
|
||||
// SPDX-FileCopyrightText: 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <QDateTime>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "Version.h"
|
||||
|
||||
struct GitHubReleaseAsset {
|
||||
int id = -1;
|
||||
QString name;
|
||||
QString label;
|
||||
QString content_type;
|
||||
int size;
|
||||
QDateTime created_at;
|
||||
QDateTime updated_at;
|
||||
QString browser_download_url;
|
||||
|
||||
bool isValid() { return id > 0; }
|
||||
};
|
||||
|
||||
struct GitHubRelease {
|
||||
int id = -1;
|
||||
QString name;
|
||||
QString tag_name;
|
||||
QDateTime created_at;
|
||||
QDateTime published_at;
|
||||
bool prerelease;
|
||||
bool draft;
|
||||
QString body;
|
||||
QList<GitHubReleaseAsset> assets;
|
||||
Version version;
|
||||
|
||||
bool isValid() const { return id > 0; }
|
||||
};
|
||||
|
||||
QDebug operator<<(QDebug debug, const GitHubReleaseAsset& rls);
|
||||
QDebug operator<<(QDebug debug, const GitHubRelease& rls);
|
1401
launcher/updater/prismupdater/PrismUpdater.cpp
Normal file
1401
launcher/updater/prismupdater/PrismUpdater.cpp
Normal file
File diff suppressed because it is too large
Load Diff
143
launcher/updater/prismupdater/PrismUpdater.h
Normal file
143
launcher/updater/prismupdater/PrismUpdater.h
Normal file
@ -0,0 +1,143 @@
|
||||
// SPDX-FileCopyrightText: 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QtCore>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDataStream>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QFlag>
|
||||
#include <QIcon>
|
||||
#include <QLocalSocket>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QUrl>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#include "QObjectPtr.h"
|
||||
#include "net/Download.h"
|
||||
|
||||
#define PRISM_EXTERNAL_EXE
|
||||
#include "FileSystem.h"
|
||||
|
||||
#include "GitHubRelease.h"
|
||||
|
||||
class PrismUpdaterApp : public QApplication {
|
||||
// friends for the purpose of limiting access to deprecated stuff
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Status { Starting, Failed, Succeeded, Initialized, Aborted };
|
||||
PrismUpdaterApp(int& argc, char** argv);
|
||||
virtual ~PrismUpdaterApp();
|
||||
void loadReleaseList();
|
||||
void run();
|
||||
Status status() const { return m_status; }
|
||||
|
||||
private:
|
||||
void fail(const QString& reason);
|
||||
void abort(const QString& reason);
|
||||
void showFatalErrorMessage(const QString& title, const QString& content);
|
||||
|
||||
bool loadPrismVersionFromExe(const QString& exe_path);
|
||||
|
||||
void downloadReleasePage(const QString& api_url, int page);
|
||||
int parseReleasePage(const QByteArray* response);
|
||||
|
||||
bool needUpdate(const GitHubRelease& release);
|
||||
|
||||
GitHubRelease getLatestRelease();
|
||||
GitHubRelease selectRelease();
|
||||
QList<GitHubRelease> newerReleases();
|
||||
QList<GitHubRelease> nonDraftReleases();
|
||||
|
||||
void printReleases();
|
||||
|
||||
QList<GitHubReleaseAsset> validReleaseArtifacts(const GitHubRelease& release);
|
||||
GitHubReleaseAsset selectAsset(const QList<GitHubReleaseAsset>& assets);
|
||||
void performUpdate(const GitHubRelease& release);
|
||||
void performInstall(QFileInfo file);
|
||||
void unpackAndInstall(QFileInfo file);
|
||||
void backupAppDir();
|
||||
std::optional<QDir> unpackArchive(QFileInfo file);
|
||||
|
||||
QFileInfo downloadAsset(const GitHubReleaseAsset& asset);
|
||||
bool callAppImageUpdate();
|
||||
|
||||
void moveAndFinishUpdate(QDir target);
|
||||
|
||||
public slots:
|
||||
void downloadError(QString reason);
|
||||
|
||||
private:
|
||||
const QString& root() { return m_rootPath; }
|
||||
|
||||
bool isPortable() { return m_isPortable; }
|
||||
|
||||
void clearUpdateLog();
|
||||
void logUpdate(const QString& msg);
|
||||
|
||||
QString m_rootPath;
|
||||
QString m_dataPath;
|
||||
bool m_isPortable = false;
|
||||
bool m_isAppimage = false;
|
||||
bool m_isFlatpak = false;
|
||||
QString m_appimagePath;
|
||||
QString m_prismExecutable;
|
||||
QUrl m_prismRepoUrl;
|
||||
Version m_userSelectedVersion;
|
||||
bool m_checkOnly;
|
||||
bool m_forceUpdate;
|
||||
bool m_printOnly;
|
||||
bool m_selectUI;
|
||||
bool m_allowDowngrade;
|
||||
bool m_allowPreRelease;
|
||||
|
||||
QString m_updateLogPath;
|
||||
|
||||
QString m_prismBinaryName;
|
||||
QString m_prismVersion;
|
||||
int m_prismVersionMajor = -1;
|
||||
int m_prismVersionMinor = -1;
|
||||
QString m_prsimVersionChannel;
|
||||
QString m_prismGitCommit;
|
||||
|
||||
GitHubRelease m_install_release;
|
||||
|
||||
Status m_status = Status::Starting;
|
||||
shared_qobject_ptr<QNetworkAccessManager> m_network;
|
||||
QString m_current_url;
|
||||
Task::Ptr m_current_task;
|
||||
QList<GitHubRelease> m_releases;
|
||||
|
||||
public:
|
||||
std::unique_ptr<QFile> logFile;
|
||||
bool logToConsole = false;
|
||||
|
||||
#if defined Q_OS_WIN32
|
||||
// used on Windows to attach the standard IO streams
|
||||
bool consoleAttached = false;
|
||||
#endif
|
||||
};
|
89
launcher/updater/prismupdater/SelectReleaseDialog.ui
Normal file
89
launcher/updater/prismupdater/SelectReleaseDialog.ui
Normal file
@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SelectReleaseDialog</class>
|
||||
<widget class="QDialog" name="SelectReleaseDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>468</width>
|
||||
<height>385</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Select Release to Install</string>
|
||||
</property>
|
||||
<property name="sizeGripEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,1,0">
|
||||
<item>
|
||||
<widget class="QLabel" name="eplainLabel">
|
||||
<property name="text">
|
||||
<string>Please select the release you wish to update to.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTreeWidget" name="versionsTree">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">1</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTextBrowser" name="changelogTextBrowser"/>
|
||||
</item>
|
||||
<item>
|
||||
<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>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>SelectReleaseDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>SelectReleaseDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
168
launcher/updater/prismupdater/UpdaterDialogs.cpp
Normal file
168
launcher/updater/prismupdater/UpdaterDialogs.cpp
Normal file
@ -0,0 +1,168 @@
|
||||
// SPDX-FileCopyrightText: 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "UpdaterDialogs.h"
|
||||
|
||||
#include "ui_SelectReleaseDialog.h"
|
||||
|
||||
#include <QTextBrowser>
|
||||
#include "Markdown.h"
|
||||
|
||||
SelectReleaseDialog::SelectReleaseDialog(const Version& current_version, const QList<GitHubRelease>& releases, QWidget* parent)
|
||||
: QDialog(parent), m_releases(releases), m_currentVersion(current_version), ui(new Ui::SelectReleaseDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
ui->changelogTextBrowser->setOpenExternalLinks(true);
|
||||
ui->changelogTextBrowser->setLineWrapMode(QTextBrowser::LineWrapMode::WidgetWidth);
|
||||
ui->changelogTextBrowser->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded);
|
||||
|
||||
ui->versionsTree->setColumnCount(2);
|
||||
|
||||
ui->versionsTree->header()->setSectionResizeMode(0, QHeaderView::Stretch);
|
||||
ui->versionsTree->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
|
||||
ui->versionsTree->setHeaderLabels({ tr("Version"), tr("Published Date") });
|
||||
ui->versionsTree->header()->setStretchLastSection(false);
|
||||
|
||||
ui->eplainLabel->setText(tr("Select a version to install.\n"
|
||||
"\n"
|
||||
"Currently installed version: %1")
|
||||
.arg(m_currentVersion.toString()));
|
||||
|
||||
loadReleases();
|
||||
|
||||
connect(ui->versionsTree, &QTreeWidget::currentItemChanged, this, &SelectReleaseDialog::selectionChanged);
|
||||
|
||||
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &SelectReleaseDialog::accept);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &SelectReleaseDialog::reject);
|
||||
}
|
||||
|
||||
SelectReleaseDialog::~SelectReleaseDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void SelectReleaseDialog::loadReleases()
|
||||
{
|
||||
for (auto rls : m_releases) {
|
||||
appendRelease(rls);
|
||||
}
|
||||
}
|
||||
|
||||
void SelectReleaseDialog::appendRelease(GitHubRelease const& release)
|
||||
{
|
||||
auto rls_item = new QTreeWidgetItem(ui->versionsTree);
|
||||
rls_item->setText(0, release.tag_name);
|
||||
rls_item->setExpanded(true);
|
||||
rls_item->setText(1, release.published_at.toString());
|
||||
rls_item->setData(0, Qt::UserRole, QVariant(release.id));
|
||||
|
||||
ui->versionsTree->addTopLevelItem(rls_item);
|
||||
}
|
||||
|
||||
GitHubRelease SelectReleaseDialog::getRelease(QTreeWidgetItem* item)
|
||||
{
|
||||
int id = item->data(0, Qt::UserRole).toInt();
|
||||
GitHubRelease release;
|
||||
for (auto rls : m_releases) {
|
||||
if (rls.id == id)
|
||||
release = rls;
|
||||
}
|
||||
return release;
|
||||
}
|
||||
|
||||
void SelectReleaseDialog::selectionChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous)
|
||||
{
|
||||
GitHubRelease release = getRelease(current);
|
||||
QString body = markdownToHTML(release.body.toUtf8());
|
||||
m_selectedRelease = release;
|
||||
|
||||
ui->changelogTextBrowser->setHtml(body);
|
||||
}
|
||||
|
||||
SelectReleaseAssetDialog::SelectReleaseAssetDialog(const QList<GitHubReleaseAsset>& assets, QWidget* parent)
|
||||
: QDialog(parent), m_assets(assets), ui(new Ui::SelectReleaseDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
ui->changelogTextBrowser->setOpenExternalLinks(true);
|
||||
ui->changelogTextBrowser->setLineWrapMode(QTextBrowser::LineWrapMode::WidgetWidth);
|
||||
ui->changelogTextBrowser->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded);
|
||||
|
||||
ui->versionsTree->setColumnCount(2);
|
||||
|
||||
ui->versionsTree->header()->setSectionResizeMode(0, QHeaderView::Stretch);
|
||||
ui->versionsTree->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
|
||||
ui->versionsTree->setHeaderLabels({ tr("Version"), tr("Published Date") });
|
||||
ui->versionsTree->header()->setStretchLastSection(false);
|
||||
|
||||
ui->eplainLabel->setText(tr("Select a version to install."));
|
||||
|
||||
ui->changelogTextBrowser->setHidden(true);
|
||||
|
||||
loadAssets();
|
||||
|
||||
connect(ui->versionsTree, &QTreeWidget::currentItemChanged, this, &SelectReleaseAssetDialog::selectionChanged);
|
||||
|
||||
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &SelectReleaseAssetDialog::accept);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &SelectReleaseAssetDialog::reject);
|
||||
}
|
||||
|
||||
SelectReleaseAssetDialog::~SelectReleaseAssetDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void SelectReleaseAssetDialog::loadAssets()
|
||||
{
|
||||
for (auto rls : m_assets) {
|
||||
appendAsset(rls);
|
||||
}
|
||||
}
|
||||
|
||||
void SelectReleaseAssetDialog::appendAsset(GitHubReleaseAsset const& asset)
|
||||
{
|
||||
auto rls_item = new QTreeWidgetItem(ui->versionsTree);
|
||||
rls_item->setText(0, asset.name);
|
||||
rls_item->setExpanded(true);
|
||||
rls_item->setText(1, asset.updated_at.toString());
|
||||
rls_item->setData(0, Qt::UserRole, QVariant(asset.id));
|
||||
|
||||
ui->versionsTree->addTopLevelItem(rls_item);
|
||||
}
|
||||
|
||||
GitHubReleaseAsset SelectReleaseAssetDialog::getAsset(QTreeWidgetItem* item)
|
||||
{
|
||||
int id = item->data(0, Qt::UserRole).toInt();
|
||||
GitHubReleaseAsset selected_asset;
|
||||
for (auto asset : m_assets) {
|
||||
if (asset.id == id)
|
||||
selected_asset = asset;
|
||||
}
|
||||
return selected_asset;
|
||||
}
|
||||
|
||||
void SelectReleaseAssetDialog::selectionChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous)
|
||||
{
|
||||
GitHubReleaseAsset asset = getAsset(current);
|
||||
m_selectedAsset = asset;
|
||||
}
|
75
launcher/updater/prismupdater/UpdaterDialogs.h
Normal file
75
launcher/updater/prismupdater/UpdaterDialogs.h
Normal file
@ -0,0 +1,75 @@
|
||||
// SPDX-FileCopyrightText: 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
#include <QTreeWidgetItem>
|
||||
|
||||
#include "GitHubRelease.h"
|
||||
#include "Version.h"
|
||||
|
||||
namespace Ui {
|
||||
class SelectReleaseDialog;
|
||||
}
|
||||
|
||||
class SelectReleaseDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SelectReleaseDialog(const Version& cur_version, const QList<GitHubRelease>& releases, QWidget* parent = 0);
|
||||
~SelectReleaseDialog();
|
||||
|
||||
void loadReleases();
|
||||
void appendRelease(GitHubRelease const& release);
|
||||
GitHubRelease selectedRelease() { return m_selectedRelease; }
|
||||
private slots:
|
||||
GitHubRelease getRelease(QTreeWidgetItem* item);
|
||||
void selectionChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous);
|
||||
|
||||
protected:
|
||||
QList<GitHubRelease> m_releases;
|
||||
GitHubRelease m_selectedRelease;
|
||||
Version m_currentVersion;
|
||||
|
||||
Ui::SelectReleaseDialog* ui;
|
||||
};
|
||||
|
||||
class SelectReleaseAssetDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SelectReleaseAssetDialog(const QList<GitHubReleaseAsset>& assets, QWidget* parent = 0);
|
||||
~SelectReleaseAssetDialog();
|
||||
|
||||
void loadAssets();
|
||||
void appendAsset(GitHubReleaseAsset const& asset);
|
||||
GitHubReleaseAsset selectedAsset() { return m_selectedAsset; }
|
||||
private slots:
|
||||
GitHubReleaseAsset getAsset(QTreeWidgetItem* item);
|
||||
void selectionChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous);
|
||||
|
||||
protected:
|
||||
QList<GitHubReleaseAsset> m_assets;
|
||||
GitHubReleaseAsset m_selectedAsset;
|
||||
|
||||
Ui::SelectReleaseDialog* ui;
|
||||
};
|
26
launcher/updater/prismupdater/updater.exe.manifest
Normal file
26
launcher/updater/prismupdater/updater.exe.manifest
Normal file
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- Windows 10, Windows 11 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
<!-- Windows 8.1 -->
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||
<!-- Windows 8 -->
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||
<!-- Windows 7 -->
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
</assembly>
|
40
launcher/updater/prismupdater/updater_main.cpp
Normal file
40
launcher/updater/prismupdater/updater_main.cpp
Normal file
@ -0,0 +1,40 @@
|
||||
// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "PrismUpdater.h"
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
PrismUpdaterApp wUpApp(argc, argv);
|
||||
|
||||
switch (wUpApp.status()) {
|
||||
case PrismUpdaterApp::Starting:
|
||||
case PrismUpdaterApp::Initialized: {
|
||||
return wUpApp.exec();
|
||||
}
|
||||
case PrismUpdaterApp::Failed:
|
||||
return 1;
|
||||
case PrismUpdaterApp::Succeeded:
|
||||
return 0;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user