diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 661c6c5be..aa702aa7e 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -44,6 +44,7 @@ #include "BuildConfig.h" #include "DataMigrationTask.h" +#include "java/JavaInstallList.h" #include "net/PasteUpload.h" #include "pathmatcher/MultiMatcher.h" #include "pathmatcher/SimplePrefixMatcher.h" @@ -125,6 +126,7 @@ #include #include +#include "SysInfo.h" #ifdef Q_OS_LINUX #include @@ -607,7 +609,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) // Memory m_settings->registerSetting({ "MinMemAlloc", "MinMemoryAlloc" }, 512); - m_settings->registerSetting({ "MaxMemAlloc", "MaxMemoryAlloc" }, suitableMaxMem()); + m_settings->registerSetting({ "MaxMemAlloc", "MaxMemoryAlloc" }, SysInfo::suitableMaxMem()); m_settings->registerSetting("PermGen", 128); // Java Settings @@ -1667,20 +1669,6 @@ QString Application::getUserAgentUncached() return BuildConfig.USER_AGENT_UNCACHED; } -int Application::suitableMaxMem() -{ - float totalRAM = (float)Sys::getSystemRam() / (float)Sys::mebibyte; - int maxMemoryAlloc; - - // If totalRAM < 6GB, use (totalRAM / 1.5), else 4GB - if (totalRAM < (4096 * 1.5)) - maxMemoryAlloc = (int)(totalRAM / 1.5); - else - maxMemoryAlloc = 4096; - - return maxMemoryAlloc; -} - bool Application::handleDataMigration(const QString& currentData, const QString& oldData, const QString& name, diff --git a/launcher/Application.h b/launcher/Application.h index 7669e08ec..85bf2dff4 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -180,8 +180,6 @@ class Application : public QApplication { void ShowGlobalSettings(class QWidget* parent, QString open_page = QString()); - int suitableMaxMem(); - bool updaterEnabled(); QString updaterBinaryName(); diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 48ca8f085..e735f2081 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -422,8 +422,6 @@ set(SETTINGS_SOURCES set(JAVA_SOURCES java/JavaChecker.h java/JavaChecker.cpp - java/JavaCheckerJob.h - java/JavaCheckerJob.cpp java/JavaInstall.h java/JavaInstall.cpp java/JavaInstallList.h diff --git a/launcher/JavaCommon.cpp b/launcher/JavaCommon.cpp index e16ac9255..cfc3cfe42 100644 --- a/launcher/JavaCommon.cpp +++ b/launcher/JavaCommon.cpp @@ -63,7 +63,7 @@ bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget* parent) return true; } -void JavaCommon::javaWasOk(QWidget* parent, const JavaCheckResult& result) +void JavaCommon::javaWasOk(QWidget* parent, const JavaChecker::Result& result) { QString text; text += QObject::tr( @@ -79,7 +79,7 @@ void JavaCommon::javaWasOk(QWidget* parent, const JavaCheckResult& result) CustomMessageBox::selectable(parent, QObject::tr("Java test success"), text, QMessageBox::Information)->show(); } -void JavaCommon::javaArgsWereBad(QWidget* parent, const JavaCheckResult& result) +void JavaCommon::javaArgsWereBad(QWidget* parent, const JavaChecker::Result& result) { auto htmlError = result.errorLog; QString text; @@ -89,7 +89,7 @@ void JavaCommon::javaArgsWereBad(QWidget* parent, const JavaCheckResult& result) CustomMessageBox::selectable(parent, QObject::tr("Java test failure"), text, QMessageBox::Warning)->show(); } -void JavaCommon::javaBinaryWasBad(QWidget* parent, const JavaCheckResult& result) +void JavaCommon::javaBinaryWasBad(QWidget* parent, const JavaChecker::Result& result) { QString text; text += QObject::tr( @@ -116,34 +116,26 @@ void JavaCommon::TestCheck::run() emit finished(); return; } - checker.reset(new JavaChecker()); + checker.reset(new JavaChecker(m_path, "", 0, 0, 0, 0, this)); connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinished); - checker->m_path = m_path; - checker->performCheck(); + checker->start(); } -void JavaCommon::TestCheck::checkFinished(JavaCheckResult result) +void JavaCommon::TestCheck::checkFinished(JavaChecker::Result result) { - if (result.validity != JavaCheckResult::Validity::Valid) { + if (result.validity != JavaChecker::Result::Validity::Valid) { javaBinaryWasBad(m_parent, result); emit finished(); return; } - checker.reset(new JavaChecker()); + checker.reset(new JavaChecker(m_path, m_args, m_maxMem, m_maxMem, result.javaVersion.requiresPermGen() ? m_permGen : 0, 0, this)); connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinishedWithArgs); - checker->m_path = m_path; - checker->m_args = m_args; - checker->m_minMem = m_minMem; - checker->m_maxMem = m_maxMem; - if (result.javaVersion.requiresPermGen()) { - checker->m_permGen = m_permGen; - } - checker->performCheck(); + checker->start(); } -void JavaCommon::TestCheck::checkFinishedWithArgs(JavaCheckResult result) +void JavaCommon::TestCheck::checkFinishedWithArgs(JavaChecker::Result result) { - if (result.validity == JavaCheckResult::Validity::Valid) { + if (result.validity == JavaChecker::Result::Validity::Valid) { javaWasOk(m_parent, result); emit finished(); return; diff --git a/launcher/JavaCommon.h b/launcher/JavaCommon.h index c96f7a985..7c5510efc 100644 --- a/launcher/JavaCommon.h +++ b/launcher/JavaCommon.h @@ -10,11 +10,11 @@ namespace JavaCommon { bool checkJVMArgs(QString args, QWidget* parent); // Show a dialog saying that the Java binary was usable -void javaWasOk(QWidget* parent, const JavaCheckResult& result); +void javaWasOk(QWidget* parent, const JavaChecker::Result& result); // Show a dialog saying that the Java binary was not usable because of bad options -void javaArgsWereBad(QWidget* parent, const JavaCheckResult& result); +void javaArgsWereBad(QWidget* parent, const JavaChecker::Result& result); // Show a dialog saying that the Java binary was not usable -void javaBinaryWasBad(QWidget* parent, const JavaCheckResult& result); +void javaBinaryWasBad(QWidget* parent, const JavaChecker::Result& result); // Show a dialog if we couldn't find Java Checker void javaCheckNotFound(QWidget* parent); @@ -32,11 +32,11 @@ class TestCheck : public QObject { void finished(); private slots: - void checkFinished(JavaCheckResult result); - void checkFinishedWithArgs(JavaCheckResult result); + void checkFinished(JavaChecker::Result result); + void checkFinishedWithArgs(JavaChecker::Result result); private: - std::shared_ptr checker; + JavaChecker::Ptr checker; QWidget* m_parent = nullptr; QString m_path; QString m_args; diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index 3bfe16ab5..78a3b290d 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -507,6 +507,112 @@ bool ExportToZipTask::abort() } return false; } + +void ExtractZipTask::executeTask() +{ + m_zip_future = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { return extractZip(); }); + connect(&m_zip_watcher, &QFutureWatcher::finished, this, &ExtractZipTask::finish); + m_zip_watcher.setFuture(m_zip_future); +} + +auto ExtractZipTask::extractZip() -> ZipResult +{ + auto target = m_output_dir.absolutePath(); + auto target_top_dir = QUrl::fromLocalFile(target); + + QStringList extracted; + + qDebug() << "Extracting subdir" << m_subdirectory << "from" << m_input->getZipName() << "to" << target; + auto numEntries = m_input->getEntriesCount(); + if (numEntries < 0) { + return ZipResult(tr("Failed to enumerate files in archive")); + } + if (numEntries == 0) { + logWarning(tr("Extracting empty archives seems odd...")); + return ZipResult(); + } + if (!m_input->goToFirstFile()) { + return ZipResult(tr("Failed to seek to first file in zip")); + } + + setStatus("Extracting files..."); + setProgress(0, numEntries); + do { + if (m_zip_future.isCanceled()) + return ZipResult(); + setProgress(m_progress + 1, m_progressTotal); + QString file_name = m_input->getCurrentFileName(); + if (!file_name.startsWith(m_subdirectory)) + continue; + + auto relative_file_name = QDir::fromNativeSeparators(file_name.remove(0, m_subdirectory.size())); + auto original_name = relative_file_name; + setStatus("Unziping: " + relative_file_name); + + // Fix subdirs/files ending with a / getting transformed into absolute paths + if (relative_file_name.startsWith('/')) + relative_file_name = relative_file_name.mid(1); + + // Fix weird "folders with a single file get squashed" thing + QString sub_path; + if (relative_file_name.contains('/') && !relative_file_name.endsWith('/')) { + sub_path = relative_file_name.section('/', 0, -2) + '/'; + FS::ensureFolderPathExists(FS::PathCombine(target, sub_path)); + + relative_file_name = relative_file_name.split('/').last(); + } + + QString target_file_path; + if (relative_file_name.isEmpty()) { + target_file_path = target + '/'; + } else { + target_file_path = FS::PathCombine(target_top_dir.toLocalFile(), sub_path, relative_file_name); + if (relative_file_name.endsWith('/') && !target_file_path.endsWith('/')) + target_file_path += '/'; + } + + if (!target_top_dir.isParentOf(QUrl::fromLocalFile(target_file_path))) { + return ZipResult(tr("Extracting %1 was cancelled, because it was effectively outside of the target path %2") + .arg(relative_file_name, target)); + } + + if (!JlCompress::extractFile(m_input.get(), "", target_file_path)) { + JlCompress::removeFile(extracted); + return ZipResult(tr("Failed to extract file %1 to %2").arg(original_name, target_file_path)); + } + + extracted.append(target_file_path); + QFile::setPermissions(target_file_path, + QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser); + + qDebug() << "Extracted file" << relative_file_name << "to" << target_file_path; + } while (m_input->goToNextFile()); + + return ZipResult(); +} + +void ExtractZipTask::finish() +{ + if (m_zip_future.isCanceled()) { + emitAborted(); + } else if (auto result = m_zip_future.result(); result.has_value()) { + emitFailed(result.value()); + } else { + emitSucceeded(); + } +} + +bool ExtractZipTask::abort() +{ + if (m_zip_future.isRunning()) { + m_zip_future.cancel(); + // NOTE: Here we don't do `emitAborted()` because it will be done when `m_build_zip_future` actually cancels, which may not occur + // immediately. + return true; + } + return false; +} + #endif } // namespace MMCZip diff --git a/launcher/MMCZip.h b/launcher/MMCZip.h index 45b1df0b3..2b396eb9c 100644 --- a/launcher/MMCZip.h +++ b/launcher/MMCZip.h @@ -194,5 +194,33 @@ class ExportToZipTask : public Task { QFuture m_build_zip_future; QFutureWatcher m_build_zip_watcher; }; + +class ExtractZipTask : public Task { + public: + ExtractZipTask(QString input, QDir outputDir, QString subdirectory = "") + : ExtractZipTask(std::make_shared(input), outputDir, subdirectory) + {} + ExtractZipTask(std::shared_ptr input, QDir outputDir, QString subdirectory = "") + : m_input(input), m_output_dir(outputDir), m_subdirectory(subdirectory) + {} + virtual ~ExtractZipTask() = default; + + typedef std::optional ZipResult; + + protected: + virtual void executeTask() override; + bool abort() override; + + ZipResult extractZip(); + void finish(); + + private: + std::shared_ptr m_input; + QDir m_output_dir; + QString m_subdirectory; + + QFuture m_zip_future; + QFutureWatcher m_zip_watcher; +}; #endif } // namespace MMCZip diff --git a/launcher/SysInfo.cpp b/launcher/SysInfo.cpp index ad251e29f..f15dde0e4 100644 --- a/launcher/SysInfo.cpp +++ b/launcher/SysInfo.cpp @@ -1,5 +1,6 @@ #include #include +#include "sys.h" #ifdef Q_OS_MACOS #include #endif @@ -7,9 +8,6 @@ #include #include #include -#include "Application.h" -#include "Commandline.h" -#include "java/JavaUtils.h" #ifdef Q_OS_MACOS bool rosettaDetect() @@ -59,4 +57,18 @@ QString useQTForArch() #endif return qtArch; } + +int suitableMaxMem() +{ + float totalRAM = (float)Sys::getSystemRam() / (float)Sys::mebibyte; + int maxMemoryAlloc; + + // If totalRAM < 6GB, use (totalRAM / 1.5), else 4GB + if (totalRAM < (4096 * 1.5)) + maxMemoryAlloc = (int)(totalRAM / 1.5); + else + maxMemoryAlloc = 4096; + + return maxMemoryAlloc; +} } // namespace SysInfo diff --git a/launcher/SysInfo.h b/launcher/SysInfo.h index 3650bf008..499c3b1dd 100644 --- a/launcher/SysInfo.h +++ b/launcher/SysInfo.h @@ -3,4 +3,5 @@ namespace SysInfo { QString currentSystem(); QString useQTForArch(); +int suitableMaxMem(); } // namespace SysInfo diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp index 20caba189..7271c0d09 100644 --- a/launcher/java/JavaChecker.cpp +++ b/launcher/java/JavaChecker.cpp @@ -40,14 +40,13 @@ #include #include -#include "Application.h" #include "Commandline.h" -#include "FileSystem.h" -#include "JavaUtils.h" +#include "java/JavaUtils.h" -JavaChecker::JavaChecker(QObject* parent) : QObject(parent) {} +JavaChecker::JavaChecker(QString path, QString args, int minMem, int maxMem, int permGen, int id, QObject* parent) + : Task(parent), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen), m_id(id){}; -void JavaChecker::performCheck() +void JavaChecker::executeTask() { QString checkerJar = JavaUtils::getJavaCheckPath(); @@ -69,7 +68,7 @@ void JavaChecker::performCheck() if (m_maxMem != 0) { args << QString("-Xmx%1m").arg(m_maxMem); } - if (m_permGen != 64) { + if (m_permGen != 64 && m_permGen != 0) { args << QString("-XX:PermSize=%1m").arg(m_permGen); } @@ -112,11 +111,10 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) QProcessPtr _process = process; process.reset(); - JavaCheckResult result; - { - result.path = m_path; - result.id = m_id; - } + Result result = { + m_path, + m_id, + }; result.errorLog = m_stderr; result.outLog = m_stdout; qDebug() << "STDOUT" << m_stdout; @@ -124,8 +122,9 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) qDebug() << "Java checker finished with status" << status << "exit code" << exitcode; if (status == QProcess::CrashExit || exitcode == 1) { - result.validity = JavaCheckResult::Validity::Errored; + result.validity = Result::Validity::Errored; emit checkFinished(result); + emitSucceeded(); return; } @@ -158,8 +157,9 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) } if (!results.contains("os.arch") || !results.contains("java.version") || !results.contains("java.vendor") || !success) { - result.validity = JavaCheckResult::Validity::ReturnedInvalidData; + result.validity = Result::Validity::ReturnedInvalidData; emit checkFinished(result); + emitSucceeded(); return; } @@ -168,7 +168,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) auto java_vendor = results["java.vendor"]; bool is_64 = os_arch == "x86_64" || os_arch == "amd64" || os_arch == "aarch64" || os_arch == "arm64"; - result.validity = JavaCheckResult::Validity::Valid; + result.validity = Result::Validity::Valid; result.is_64bit = is_64; result.mojangPlatform = is_64 ? "64" : "32"; result.realPlatform = os_arch; @@ -176,6 +176,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) result.javaVendor = java_vendor; qDebug() << "Java checker succeeded."; emit checkFinished(result); + emitSucceeded(); } void JavaChecker::error(QProcess::ProcessError err) @@ -187,15 +188,9 @@ void JavaChecker::error(QProcess::ProcessError err) qDebug() << "Native environment:"; qDebug() << QProcessEnvironment::systemEnvironment().toStringList(); killTimer.stop(); - JavaCheckResult result; - { - result.path = m_path; - result.id = m_id; - } - - emit checkFinished(result); - return; + emit checkFinished({ m_path, m_id }); } + emitSucceeded(); } void JavaChecker::timeout() diff --git a/launcher/java/JavaChecker.h b/launcher/java/JavaChecker.h index 7111f8522..0c6191c21 100644 --- a/launcher/java/JavaChecker.h +++ b/launcher/java/JavaChecker.h @@ -3,49 +3,51 @@ #include #include -#include "QObjectPtr.h" - #include "JavaVersion.h" +#include "QObjectPtr.h" +#include "tasks/Task.h" -class JavaChecker; - -struct JavaCheckResult { - QString path; - QString mojangPlatform; - QString realPlatform; - JavaVersion javaVersion; - QString javaVendor; - QString outLog; - QString errorLog; - bool is_64bit = false; - int id; - enum class Validity { Errored, ReturnedInvalidData, Valid } validity = Validity::Errored; -}; - -using QProcessPtr = shared_qobject_ptr; -using JavaCheckerPtr = shared_qobject_ptr; -class JavaChecker : public QObject { +class JavaChecker : public Task { Q_OBJECT public: - explicit JavaChecker(QObject* parent = 0); - void performCheck(); + using QProcessPtr = shared_qobject_ptr; + using Ptr = shared_qobject_ptr; - QString m_path; - QString m_args; - int m_id = 0; - int m_minMem = 0; - int m_maxMem = 0; - int m_permGen = 64; + struct Result { + QString path; + int id; + QString mojangPlatform; + QString realPlatform; + JavaVersion javaVersion; + QString javaVendor; + QString outLog; + QString errorLog; + bool is_64bit = false; + enum class Validity { Errored, ReturnedInvalidData, Valid } validity = Validity::Errored; + }; + + explicit JavaChecker(QString path, QString args, int minMem = 0, int maxMem = 0, int permGen = 0, int id = 0, QObject* parent = 0); signals: - void checkFinished(JavaCheckResult result); + void checkFinished(Result result); + + protected: + virtual void executeTask() override; private: QProcessPtr process; QTimer killTimer; QString m_stdout; QString m_stderr; - public slots: + + QString m_path; + QString m_args; + int m_minMem = 0; + int m_maxMem = 0; + int m_permGen = 64; + int m_id = 0; + + private slots: void timeout(); void finished(int exitcode, QProcess::ExitStatus); void error(QProcess::ProcessError); diff --git a/launcher/java/JavaCheckerJob.cpp b/launcher/java/JavaCheckerJob.cpp deleted file mode 100644 index 870e2a09a..000000000 --- a/launcher/java/JavaCheckerJob.cpp +++ /dev/null @@ -1,41 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "JavaCheckerJob.h" - -#include - -void JavaCheckerJob::partFinished(JavaCheckResult result) -{ - num_finished++; - qDebug() << m_job_name.toLocal8Bit() << "progress:" << num_finished << "/" << javacheckers.size(); - setProgress(num_finished, javacheckers.size()); - - javaresults.replace(result.id, result); - - if (num_finished == javacheckers.size()) { - emitSucceeded(); - } -} - -void JavaCheckerJob::executeTask() -{ - qDebug() << m_job_name.toLocal8Bit() << " started."; - for (auto iter : javacheckers) { - javaresults.append(JavaCheckResult()); - connect(iter.get(), &JavaChecker::checkFinished, this, &JavaCheckerJob::partFinished); - iter->performCheck(); - } -} diff --git a/launcher/java/JavaCheckerJob.h b/launcher/java/JavaCheckerJob.h deleted file mode 100644 index ddf827968..000000000 --- a/launcher/java/JavaCheckerJob.h +++ /dev/null @@ -1,56 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include "JavaChecker.h" -#include "tasks/Task.h" - -class JavaCheckerJob; -using JavaCheckerJobPtr = shared_qobject_ptr; - -// FIXME: this just seems horribly redundant -class JavaCheckerJob : public Task { - Q_OBJECT - public: - explicit JavaCheckerJob(QString job_name) : Task(), m_job_name(job_name){}; - virtual ~JavaCheckerJob(){}; - - bool addJavaCheckerAction(JavaCheckerPtr base) - { - javacheckers.append(base); - // if this is already running, the action needs to be started right away! - if (isRunning()) { - setProgress(num_finished, javacheckers.size()); - connect(base.get(), &JavaChecker::checkFinished, this, &JavaCheckerJob::partFinished); - base->performCheck(); - } - return true; - } - QList getResults() { return javaresults; } - - private slots: - void partFinished(JavaCheckResult result); - - protected: - virtual void executeTask() override; - - private: - QString m_job_name; - QList javacheckers; - QList javaresults; - int num_finished = 0; -}; diff --git a/launcher/java/JavaInstallList.cpp b/launcher/java/JavaInstallList.cpp index d8be4963f..ef99d6853 100644 --- a/launcher/java/JavaInstallList.cpp +++ b/launcher/java/JavaInstallList.cpp @@ -38,11 +38,13 @@ #include #include +#include -#include "java/JavaCheckerJob.h" +#include "Application.h" +#include "java/JavaChecker.h" #include "java/JavaInstallList.h" #include "java/JavaUtils.h" -#include "minecraft/VersionFilterData.h" +#include "tasks/ConcurrentTask.h" JavaInstallList::JavaInstallList(QObject* parent) : BaseVersionList(parent) {} @@ -55,7 +57,7 @@ Task::Ptr JavaInstallList::getLoadTask() Task::Ptr JavaInstallList::getCurrentTask() { if (m_status == Status::InProgress) { - return m_loadTask; + return m_load_task; } return nullptr; } @@ -64,8 +66,8 @@ void JavaInstallList::load() { if (m_status != Status::InProgress) { m_status = Status::InProgress; - m_loadTask.reset(new JavaListLoadTask(this)); - m_loadTask->start(); + m_load_task.reset(new JavaListLoadTask(this)); + m_load_task->start(); } } @@ -129,7 +131,7 @@ void JavaInstallList::updateListData(QList versions) } endResetModel(); m_status = Status::Done; - m_loadTask.reset(); + m_load_task.reset(); } bool sortJavas(BaseVersion::Ptr left, BaseVersion::Ptr right) @@ -149,11 +151,9 @@ void JavaInstallList::sortVersions() JavaListLoadTask::JavaListLoadTask(JavaInstallList* vlist) : Task() { m_list = vlist; - m_currentRecommended = NULL; + m_current_recommended = NULL; } -JavaListLoadTask::~JavaListLoadTask() {} - void JavaListLoadTask::executeTask() { setStatus(tr("Detecting Java installations...")); @@ -161,20 +161,17 @@ void JavaListLoadTask::executeTask() JavaUtils ju; QList candidate_paths = ju.FindJavaPaths(); - m_job.reset(new JavaCheckerJob("Java detection")); + ConcurrentTask::Ptr job(new ConcurrentTask(this, "Java detection", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())); + m_job.reset(job); connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished); connect(m_job.get(), &Task::progress, this, &Task::setProgress); qDebug() << "Probing the following Java paths: "; int id = 0; for (QString candidate : candidate_paths) { - qDebug() << " " << candidate; - - auto candidate_checker = new JavaChecker(); - candidate_checker->m_path = candidate; - candidate_checker->m_id = id; - m_job->addJavaCheckerAction(JavaCheckerPtr(candidate_checker)); - + auto checker = new JavaChecker(candidate, "", 0, 0, 0, id, this); + connect(checker, &JavaChecker::checkFinished, [this](JavaChecker::Result result) { m_results << result; }); + job->addTask(Task::Ptr(checker)); id++; } @@ -184,11 +181,11 @@ void JavaListLoadTask::executeTask() void JavaListLoadTask::javaCheckerFinished() { QList candidates; - auto results = m_job->getResults(); + std::sort(m_results.begin(), m_results.end(), [](JavaChecker::Result a, JavaChecker::Result b) { return a.id < b.id; }); qDebug() << "Found the following valid Java installations:"; - for (JavaCheckResult result : results) { - if (result.validity == JavaCheckResult::Validity::Valid) { + for (auto result : m_results) { + if (result.validity == JavaChecker::Result::Validity::Valid) { JavaInstallPtr javaVersion(new JavaInstall()); javaVersion->id = result.javaVersion; diff --git a/launcher/java/JavaInstallList.h b/launcher/java/JavaInstallList.h index 1eebadf23..08f0b310d 100644 --- a/launcher/java/JavaInstallList.h +++ b/launcher/java/JavaInstallList.h @@ -19,9 +19,9 @@ #include #include "BaseVersionList.h" +#include "java/JavaChecker.h" #include "tasks/Task.h" -#include "JavaCheckerJob.h" #include "JavaInstall.h" #include "QObjectPtr.h" @@ -53,7 +53,7 @@ class JavaInstallList : public BaseVersionList { protected: Status m_status = Status::NotDone; - shared_qobject_ptr m_loadTask; + shared_qobject_ptr m_load_task; QList m_vlist; }; @@ -62,14 +62,16 @@ class JavaListLoadTask : public Task { public: explicit JavaListLoadTask(JavaInstallList* vlist); - virtual ~JavaListLoadTask(); + virtual ~JavaListLoadTask() = default; + protected: void executeTask() override; public slots: void javaCheckerFinished(); protected: - shared_qobject_ptr m_job; + Task::Ptr m_job; JavaInstallList* m_list; - JavaInstall* m_currentRecommended; + JavaInstall* m_current_recommended; + QList m_results; }; diff --git a/launcher/java/JavaUtils.h b/launcher/java/JavaUtils.h index 616179706..c052ca325 100644 --- a/launcher/java/JavaUtils.h +++ b/launcher/java/JavaUtils.h @@ -15,10 +15,9 @@ #pragma once +#include #include - -#include "JavaChecker.h" -#include "JavaInstallList.h" +#include "java/JavaInstall.h" #ifdef Q_OS_WIN #include diff --git a/launcher/java/providers/AdoptiumJavaDownloader.cpp b/launcher/java/providers/AdoptiumJavaDownloader.cpp new file mode 100644 index 000000000..4f8499d4a --- /dev/null +++ b/launcher/java/providers/AdoptiumJavaDownloader.cpp @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * 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 . + */ +#include "java/providers/AdoptiumJavaDownloader.h" +#include +#include +#include "MMCZip.h" + +#include "Application.h" +#include "net/NetJob.h" +#include "tasks/Task.h" + +void AdoptiumJavaDownloader::executeTask() +{ + downloadJava(); +}; + +QString AdoptiumJavaDownloader::getArch() const +{ + if (m_os_arch == "arm64") + return "aarch64"; + if (m_os_arch.isEmpty()) + return "x86"; + return m_os_arch; +} + +void AdoptiumJavaDownloader::downloadJava() +{ + // JRE found ! download the zip + setStatus(tr("Downloading Java from Adoptium")); + + auto javaVersion = m_is_legacy ? QString("8") : QString("17"); + auto azulOS = m_os_name == "osx" ? "mac" : m_os_name; + auto arch = getArch(); + MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("java", "adoptiumJRE.zip"); + + auto download = makeShared(QString("JRE::DownloadJava"), APPLICATION->network()); + download->addNetAction(Net::Download::makeCached( + QString("https://api.adoptium.net/v3/binary/latest/%1/ga/%2/%3/jre/hotspot/normal/eclipse").arg(javaVersion, azulOS, arch), entry)); + auto fullPath = entry->getFullPath(); + + connect(download.get(), &NetJob::finished, [download, this] { disconnect(this, &Task::aborted, download.get(), &NetJob::abort); }); + // connect(download.get(), &NetJob::aborted, [path] { APPLICATION->instances()->destroyStagingPath(path); }); + connect(download.get(), &NetJob::progress, this, &AdoptiumJavaDownloader::progress); + connect(download.get(), &NetJob::failed, this, &AdoptiumJavaDownloader::emitFailed); + connect(this, &Task::aborted, download.get(), &NetJob::abort); + connect(download.get(), &NetJob::succeeded, [this, fullPath] { + // This should do all of the extracting and creating folders + extractJava(fullPath); + }); + download->start(); +}; + +void AdoptiumJavaDownloader::extractJava(QString input) +{ + setStatus(tr("Extracting java")); + auto zip = std::make_shared(input); + auto files = zip->getFileNameList(); + if (files.isEmpty()) { + emitFailed("Empty archive"); + return; + } + auto zipTask = makeShared(input, m_final_path, files[0]); + + auto progressStep = std::make_shared(); + connect(zipTask.get(), &Task::finished, this, [this, progressStep] { + progressStep->state = TaskStepState::Succeeded; + stepProgress(*progressStep); + }); + + connect(this, &Task::aborted, zipTask.get(), &Task::abort); + connect(zipTask.get(), &Task::finished, [zipTask, this] { disconnect(this, &Task::aborted, zipTask.get(), &Task::abort); }); + + connect(zipTask.get(), &Task::succeeded, this, &AdoptiumJavaDownloader::emitSucceeded); + connect(zipTask.get(), &Task::aborted, this, &AdoptiumJavaDownloader::emitAborted); + connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) { + progressStep->state = TaskStepState::Failed; + stepProgress(*progressStep); + emitFailed(reason); + }); + connect(zipTask.get(), &Task::stepProgress, this, &AdoptiumJavaDownloader::propagateStepProgress); + + connect(zipTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) { + progressStep->update(current, total); + stepProgress(*progressStep); + }); + connect(zipTask.get(), &Task::status, this, [this, progressStep](QString status) { + progressStep->status = status; + stepProgress(*progressStep); + }); + zipTask->start(); +}; + +static const QStringList supportedOs = { + "linux", "windows", "mac", "solaris", "aix", "alpine-linux", +}; + +static const QStringList supportedArch = { + "x64", "x86", "x32", "ppc64", "ppc64le", "s390x", "aarch64", "arm", "sparcv9", "riscv64", +}; + +bool AdoptiumJavaDownloader::isSupported() const +{ + return supportedOs.contains(m_os_name == "osx" ? "mac" : m_os_name) && supportedArch.contains(getArch()); +}; \ No newline at end of file diff --git a/launcher/java/providers/AdoptiumJavaDownloader.h b/launcher/java/providers/AdoptiumJavaDownloader.h new file mode 100644 index 000000000..f0ae6239b --- /dev/null +++ b/launcher/java/providers/AdoptiumJavaDownloader.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * 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 . + */ + +#pragma once + +#include "java/providers/BasicJavaDownloader.h" + +class AdoptiumJavaDownloader : public BasicJavaDownloader { + Q_OBJECT + public: + void executeTask() override; + + virtual QString name() const override { return "Adoptium"; }; + virtual bool isSupported() const override; + private slots: + void downloadJava(); + void extractJava(QString input); + + private: + QString getArch() const; +}; \ No newline at end of file diff --git a/launcher/java/providers/AzulJavaDownloader.cpp b/launcher/java/providers/AzulJavaDownloader.cpp new file mode 100644 index 000000000..674da592e --- /dev/null +++ b/launcher/java/providers/AzulJavaDownloader.cpp @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * 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 . + */ +#include "java/providers/AzulJavaDownloader.h" +#include +#include "MMCZip.h" + +#include "Application.h" +#include "Json.h" +#include "net/NetJob.h" +#include "tasks/Task.h" + +void AzulJavaDownloader::executeTask() +{ + downloadJavaList(); +}; + +void AzulJavaDownloader::downloadJavaList() +{ + setStatus(tr("Querying Azul meta")); + + auto javaVersion = m_is_legacy ? QString("8.0") : QString("17.0"); + auto azulOS = m_os_name == "osx" ? "macos" : m_os_name; + auto arch = getArch(); + auto metaResponse = std::make_shared(); + auto downloadJob = makeShared(QString("JRE::QueryAzulMeta"), APPLICATION->network()); + downloadJob->addNetAction(Net::Download::makeByteArray(QString("https://api.azul.com/metadata/v1/zulu/packages/?" + "java_version=%1" + "&os=%2" + "&arch=%3" + "&archive_type=zip" + "&java_package_type=jre" + "&support_term=lts" + "&latest=true" + "status=ga" + "&availability_types=CA" + "&page=1" + "&page_size=1") + .arg(javaVersion, azulOS, arch), + metaResponse)); + connect(downloadJob.get(), &NetJob::finished, + [downloadJob, metaResponse, this] { disconnect(this, &Task::aborted, downloadJob.get(), &NetJob::abort); }); + connect(this, &Task::aborted, downloadJob.get(), &NetJob::abort); + connect(downloadJob.get(), &NetJob::failed, this, &AzulJavaDownloader::emitFailed); + connect(downloadJob.get(), &NetJob::progress, this, &AzulJavaDownloader::progress); + connect(downloadJob.get(), &NetJob::succeeded, [metaResponse, this] { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*metaResponse, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response at " << parse_error.offset << " reason: " << parse_error.errorString(); + qWarning() << *metaResponse; + return; + } + auto array = Json::ensureArray(doc.array()); + if (!array.empty()) { + downloadJava(array); + } else { + emitFailed(tr("No suitable JRE found")); + } + }); + downloadJob->start(); +}; + +QString AzulJavaDownloader::getArch() const +{ + if (m_os_arch == "arm64") + return "aarch64"; + if (m_os_arch == "arm") + return "aarch32"; + if (m_os_arch.isEmpty()) + return "x86"; + return m_os_arch; +} + +void AzulJavaDownloader::downloadJava(const QJsonArray& array) +{ + // JRE found ! download the zip + setStatus(tr("Downloading Java from Azul")); + + MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("java", "azulJRE.zip"); + + auto downloadURL = QUrl(array[0].toObject()["url"].toString()); + auto download = makeShared(QString("JRE::DownloadJava"), APPLICATION->network()); + download->addNetAction(Net::Download::makeCached(downloadURL, entry)); + auto fullPath = entry->getFullPath(); + + connect(download.get(), &NetJob::finished, [download, this] { disconnect(this, &Task::aborted, download.get(), &NetJob::abort); }); + // connect(download.get(), &NetJob::aborted, [path] { APPLICATION->instances()->destroyStagingPath(path); }); + connect(download.get(), &NetJob::progress, this, &AzulJavaDownloader::progress); + connect(download.get(), &NetJob::failed, this, &AzulJavaDownloader::emitFailed); + connect(this, &Task::aborted, download.get(), &NetJob::abort); + connect(download.get(), &NetJob::succeeded, [downloadURL, this, fullPath] { + // This should do all of the extracting and creating folders + extractJava(fullPath, downloadURL.fileName().chopped(4)); + }); + download->start(); +}; + +void AzulJavaDownloader::extractJava(QString input, QString subdirectory) +{ + setStatus(tr("Extracting java")); + auto zipTask = makeShared(input, m_final_path, subdirectory); + + auto progressStep = std::make_shared(); + connect(zipTask.get(), &Task::finished, this, [this, progressStep] { + progressStep->state = TaskStepState::Succeeded; + stepProgress(*progressStep); + }); + + connect(this, &Task::aborted, zipTask.get(), &Task::abort); + connect(zipTask.get(), &Task::finished, [zipTask, this] { disconnect(this, &Task::aborted, zipTask.get(), &Task::abort); }); + + connect(zipTask.get(), &Task::succeeded, this, &AzulJavaDownloader::emitSucceeded); + connect(zipTask.get(), &Task::aborted, this, &AzulJavaDownloader::emitAborted); + connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) { + progressStep->state = TaskStepState::Failed; + stepProgress(*progressStep); + emitFailed(reason); + }); + connect(zipTask.get(), &Task::stepProgress, this, &AzulJavaDownloader::propagateStepProgress); + + connect(zipTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) { + progressStep->update(current, total); + stepProgress(*progressStep); + }); + connect(zipTask.get(), &Task::status, this, [this, progressStep](QString status) { + progressStep->status = status; + stepProgress(*progressStep); + }); + zipTask->start(); +}; + +static const QStringList supportedOs = { + "macos", "linux", "windows", "linux-musl", "linux-glibc", "qnx", "solaris", "aix", +}; + +static const QStringList supportedArch = { + "x86", "x64", "amd64", "i686", "arm", "aarch64", "aarch32", "aarch32sf", "aarch32hf", "ppc", + "ppc64", "ppc32", "ppc32hf", "ppc32spe", "sparc", "sparc64", "sparc32", "sparcv9", "sparcv9-64", "sparcv9-32", +}; + +bool AzulJavaDownloader::isSupported() const +{ + return supportedOs.contains(m_os_name == "osx" ? "macos" : m_os_name) && supportedArch.contains(getArch()); +}; \ No newline at end of file diff --git a/launcher/java/providers/AzulJavaDownloader.h b/launcher/java/providers/AzulJavaDownloader.h new file mode 100644 index 000000000..c4bc8c761 --- /dev/null +++ b/launcher/java/providers/AzulJavaDownloader.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * 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 . + */ + +#pragma once + +#include "java/providers/BasicJavaDownloader.h" + +class AzulJavaDownloader : public BasicJavaDownloader { + Q_OBJECT + public: + void executeTask() override; + + virtual QString name() const override { return "Azul"; }; + virtual bool isSupported() const override; + private slots: + void downloadJavaList(); + void downloadJava(const QJsonArray& doc); + void extractJava(QString input, QString subdirectory); + + private: + QString getArch() const; +}; \ No newline at end of file diff --git a/launcher/java/providers/BasicJavaDownloader.cpp b/launcher/java/providers/BasicJavaDownloader.cpp new file mode 100644 index 000000000..9f353d9c1 --- /dev/null +++ b/launcher/java/providers/BasicJavaDownloader.cpp @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * 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 . + */ +#include "java/providers/BasicJavaDownloader.h" + +#include "SysInfo.h" +#include "tasks/Task.h" + +BasicJavaDownloader::BasicJavaDownloader(QString final_path, bool m_is_legacy, QObject* parent) + : Task(parent) + , m_os_name(SysInfo::currentSystem()) + , m_os_arch(SysInfo::useQTForArch()) + , m_final_path(final_path) + , m_is_legacy(m_is_legacy) +{} diff --git a/launcher/java/providers/BasicJavaDownloader.h b/launcher/java/providers/BasicJavaDownloader.h new file mode 100644 index 000000000..34c944ec8 --- /dev/null +++ b/launcher/java/providers/BasicJavaDownloader.h @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * 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 . + */ + +#pragma once + +#include "tasks/Task.h" + +class BasicJavaDownloader : public Task { + Q_OBJECT + public: + BasicJavaDownloader(QString final_path, bool m_is_legacy = false, QObject* parent = nullptr); + virtual ~BasicJavaDownloader() = default; + + [[nodiscard]] bool canAbort() const override { return true; } + + virtual QString name() const = 0; + virtual bool isSupported() const = 0; + + protected: + QString m_os_name; + QString m_os_arch; + QString m_final_path; + bool m_is_legacy; + + Task::Ptr m_current_task; +}; \ No newline at end of file diff --git a/launcher/java/providers/MojangJavaDownloader.cpp b/launcher/java/providers/MojangJavaDownloader.cpp new file mode 100644 index 000000000..fc2a17e94 --- /dev/null +++ b/launcher/java/providers/MojangJavaDownloader.cpp @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * 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 . + */ +#include "java/providers/MojanglJavaDownloader.h" + +#include "Application.h" +#include "FileSystem.h" +#include "Json.h" +#include "net/ChecksumValidator.h" +#include "net/NetJob.h" + +struct File { + QString path; + QString url; + QByteArray hash; + bool isExec; +}; + +void MojangJavaDownloader::executeTask() +{ + downloadJavaList(); +}; + +void MojangJavaDownloader::downloadJavaList() +{ + auto netJob = makeShared(QString("JRE::QueryVersions"), APPLICATION->network()); + auto response = std::make_shared(); + setStatus(tr("Querying mojang meta")); + netJob->addNetAction(Net::Download::makeByteArray( + QUrl("https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"), response)); + + connect(netJob.get(), &NetJob::finished, [netJob, this] { + // delete so that it's not called on a deleted job + // FIXME: is this needed? qt should handle this + disconnect(this, &Task::aborted, netJob.get(), &NetJob::abort); + }); + connect(this, &Task::aborted, netJob.get(), &NetJob::abort); + + connect(netJob.get(), &NetJob::progress, this, &MojangJavaDownloader::progress); + connect(netJob.get(), &NetJob::failed, this, &MojangJavaDownloader::emitFailed); + connect(netJob.get(), &NetJob::succeeded, [response, this, netJob] { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response at " << parse_error.offset << " reason: " << parse_error.errorString(); + qWarning() << *response; + emitFailed(parse_error.errorString()); + return; + } + auto versionArray = Json::ensureArray(Json::ensureObject(doc.object(), getOS()), m_is_legacy ? "jre-legacy" : "java-runtime-gamma"); + if (!versionArray.empty()) { + parseManifest(versionArray); + + } else { + // mojang does not have a JRE for us, so fail + emitFailed("No suitable JRE found"); + } + }); + + netJob->start(); +}; + +QString MojangJavaDownloader::getOS() const +{ + if (m_os_name == "windows") { + if (m_os_arch == "x86_64") { + return "windows-x64"; + } + if (m_os_arch == "i386") { + return "windows-x86"; + } + // Unknown, maybe arm, appending arch for downloader + return "windows-" + m_os_arch; + } + if (m_os_name == "osx") { + if (m_os_arch == "arm64") { + return "mac-os-arm64"; + } + return "mac-os"; + } + if (m_os_name == "linux") { + if (m_os_arch == "x86_64") { + return "linux"; + } + // will work for i386, and arm(64) + return "linux-" + m_os_arch; + } + return {}; +} +void MojangJavaDownloader::parseManifest(const QJsonArray& versionArray) +{ + setStatus(tr("Downloading Java from Mojang")); + auto url = Json::ensureString(Json::ensureObject(Json::ensureObject(versionArray[0]), "manifest"), "url"); + auto download = makeShared(QString("JRE::DownloadJava"), APPLICATION->network()); + auto files = std::make_shared(); + + download->addNetAction(Net::Download::makeByteArray(QUrl(url), files)); + + connect(download.get(), &NetJob::finished, [download, this] { disconnect(this, &Task::aborted, download.get(), &NetJob::abort); }); + connect(download.get(), &NetJob::progress, this, &MojangJavaDownloader::progress); + connect(download.get(), &NetJob::failed, this, &MojangJavaDownloader::emitFailed); + connect(this, &Task::aborted, download.get(), &NetJob::abort); + + connect(download.get(), &NetJob::succeeded, [files, this] { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*files, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response at " << parse_error.offset << " reason: " << parse_error.errorString(); + qWarning() << *files; + emitFailed(parse_error.errorString()); + return; + } + downloadJava(doc); + }); + download->start(); +}; + +void MojangJavaDownloader::downloadJava(const QJsonDocument& doc) +{ + // valid json doc, begin making jre spot + FS::ensureFolderPathExists(m_final_path); + std::vector toDownload; + auto list = Json::ensureObject(Json::ensureObject(doc.object()), "files"); + for (const auto& paths : list.keys()) { + auto file = FS::PathCombine(m_final_path, paths); + + const QJsonObject& meta = Json::ensureObject(list, paths); + auto type = Json::ensureString(meta, "type"); + if (type == "directory") { + FS::ensureFolderPathExists(file); + } else if (type == "link") { + // this is linux only ! + auto path = Json::ensureString(meta, "target"); + if (!path.isEmpty()) { + auto target = FS::PathCombine(file, "../" + path); + QFile(target).link(file); + } + } else if (type == "file") { + // TODO download compressed version if it exists ? + auto raw = Json::ensureObject(Json::ensureObject(meta, "downloads"), "raw"); + auto isExec = Json::ensureBoolean(meta, "executable", false); + auto url = Json::ensureString(raw, "url"); + if (!url.isEmpty() && QUrl(url).isValid()) { + auto f = File{ file, url, QByteArray::fromHex(Json::ensureString(raw, "sha1").toLatin1()), isExec }; + toDownload.push_back(f); + } + } + } + auto elementDownload = new NetJob("JRE::FileDownload", APPLICATION->network()); + for (const auto& file : toDownload) { + auto dl = Net::Download::makeFile(file.url, file.path); + if (!file.hash.isEmpty()) { + dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, file.hash)); + } + if (file.isExec) { + connect(dl.get(), &Net::Download::succeeded, + [file] { QFile(file.path).setPermissions(QFile(file.path).permissions() | QFileDevice::Permissions(0x1111)); }); + } + elementDownload->addNetAction(dl); + } + connect(elementDownload, &NetJob::finished, [elementDownload, this] { + disconnect(this, &Task::aborted, elementDownload, &NetJob::abort); + elementDownload->deleteLater(); + }); + connect(elementDownload, &NetJob::progress, this, &MojangJavaDownloader::progress); + connect(elementDownload, &NetJob::failed, this, &MojangJavaDownloader::emitFailed); + + connect(this, &Task::aborted, elementDownload, &NetJob::abort); + connect(elementDownload, &NetJob::succeeded, [this] { emitSucceeded(); }); + elementDownload->start(); +}; diff --git a/launcher/java/providers/MojanglJavaDownloader.h b/launcher/java/providers/MojanglJavaDownloader.h new file mode 100644 index 000000000..a1b5b1765 --- /dev/null +++ b/launcher/java/providers/MojanglJavaDownloader.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2023 Trial97 + * + * 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 . + */ + +#pragma once + +#include "java/providers/BasicJavaDownloader.h" + +class MojangJavaDownloader : public BasicJavaDownloader { + Q_OBJECT + public: + void executeTask() override; + + virtual QString name() const override { return "Mojang"; }; + virtual bool isSupported() const override { return !getOS().isEmpty(); }; + private slots: + void downloadJavaList(); + void parseManifest(const QJsonArray& versionArray); + void downloadJava(const QJsonDocument& doc); + + private: + QString getOS() const; +}; \ No newline at end of file diff --git a/launcher/launch/steps/CheckJava.cpp b/launcher/launch/steps/CheckJava.cpp index 81337a88e..c776092fe 100644 --- a/launcher/launch/steps/CheckJava.cpp +++ b/launcher/launch/steps/CheckJava.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include "java/JavaUtils.h" @@ -90,11 +91,10 @@ void CheckJava::executeTask() // if timestamps are not the same, or something is missing, check! if (m_javaSignature != storedSignature || storedVersion.size() == 0 || storedArchitecture.size() == 0 || storedRealArchitecture.size() == 0 || storedVendor.size() == 0) { - m_JavaChecker.reset(new JavaChecker); + m_JavaChecker.reset(new JavaChecker(realJavaPath, "", 0, 0, 0, 0, this)); emit logLine(QString("Checking Java version..."), MessageLevel::Launcher); connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished); - m_JavaChecker->m_path = realJavaPath; - m_JavaChecker->performCheck(); + m_JavaChecker->start(); return; } else { auto verString = instance->settings()->get("JavaVersion").toString(); @@ -106,10 +106,10 @@ void CheckJava::executeTask() emitSucceeded(); } -void CheckJava::checkJavaFinished(JavaCheckResult result) +void CheckJava::checkJavaFinished(JavaChecker::Result result) { switch (result.validity) { - case JavaCheckResult::Validity::Errored: { + case JavaChecker::Result::Validity::Errored: { // Error message displayed if java can't start emit logLine(QString("Could not start java:"), MessageLevel::Error); emit logLines(result.errorLog.split('\n'), MessageLevel::Error); @@ -117,14 +117,14 @@ void CheckJava::checkJavaFinished(JavaCheckResult result) emitFailed(QString("Could not start java!")); return; } - case JavaCheckResult::Validity::ReturnedInvalidData: { + case JavaChecker::Result::Validity::ReturnedInvalidData: { emit logLine(QString("Java checker returned some invalid data we don't understand:"), MessageLevel::Error); emit logLines(result.outLog.split('\n'), MessageLevel::Warning); emit logLine("\nMinecraft might not start properly.", MessageLevel::Launcher); emitSucceeded(); return; } - case JavaCheckResult::Validity::Valid: { + case JavaChecker::Result::Validity::Valid: { auto instance = m_parent->instance(); printJavaInfo(result.javaVersion.toString(), result.mojangPlatform, result.realPlatform, result.javaVendor); instance->settings()->set("JavaVersion", result.javaVersion.toString()); diff --git a/launcher/launch/steps/CheckJava.h b/launcher/launch/steps/CheckJava.h index 4436e2a55..5ba9e4018 100644 --- a/launcher/launch/steps/CheckJava.h +++ b/launcher/launch/steps/CheckJava.h @@ -28,7 +28,7 @@ class CheckJava : public LaunchStep { virtual void executeTask(); virtual bool canAbort() const { return false; } private slots: - void checkJavaFinished(JavaCheckResult result); + void checkJavaFinished(JavaChecker::Result result); private: void printJavaInfo(const QString& version, const QString& architecture, const QString& realArchitecture, const QString& vendor); @@ -37,5 +37,5 @@ class CheckJava : public LaunchStep { private: QString m_javaPath; QString m_javaSignature; - JavaCheckerPtr m_JavaChecker; + JavaChecker::Ptr m_JavaChecker; }; diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp index 63d7aa57c..be6b195dc 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.cpp +++ b/launcher/ui/widgets/JavaSettingsWidget.cpp @@ -14,7 +14,9 @@ #include "FileSystem.h" #include "JavaCommon.h" #include "JavaDownloader.h" +#include "java/JavaChecker.h" #include "java/JavaInstall.h" +#include "java/JavaInstallList.h" #include "java/JavaUtils.h" #include "ui/dialogs/CustomMessageBox.h" @@ -368,30 +370,25 @@ void JavaSettingsWidget::checkJavaPath(const QString& path) return; } setJavaStatus(JavaStatus::Pending); - m_checker.reset(new JavaChecker()); - m_checker->m_path = path; - m_checker->m_minMem = minHeapSize(); - m_checker->m_maxMem = maxHeapSize(); - if (m_permGenSpinBox->isVisible()) { - m_checker->m_permGen = m_permGenSpinBox->value(); - } + m_checker.reset( + new JavaChecker(path, "", minHeapSize(), maxHeapSize(), m_permGenSpinBox->isVisible() ? m_permGenSpinBox->value() : 0, 0, this)); connect(m_checker.get(), &JavaChecker::checkFinished, this, &JavaSettingsWidget::checkFinished); - m_checker->performCheck(); + m_checker->start(); } -void JavaSettingsWidget::checkFinished(JavaCheckResult result) +void JavaSettingsWidget::checkFinished(JavaChecker::Result result) { m_result = result; switch (result.validity) { - case JavaCheckResult::Validity::Valid: { + case JavaChecker::Result::Validity::Valid: { setJavaStatus(JavaStatus::Good); break; } - case JavaCheckResult::Validity::ReturnedInvalidData: { + case JavaChecker::Result::Validity::ReturnedInvalidData: { setJavaStatus(JavaStatus::ReturnedInvalidData); break; } - case JavaCheckResult::Validity::Errored: { + case JavaChecker::Result::Validity::Errored: { setJavaStatus(JavaStatus::DoesNotStart); break; } diff --git a/launcher/ui/widgets/JavaSettingsWidget.h b/launcher/ui/widgets/JavaSettingsWidget.h index 0e05bf341..d3cd2c5a7 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.h +++ b/launcher/ui/widgets/JavaSettingsWidget.h @@ -51,7 +51,7 @@ class JavaSettingsWidget : public QWidget { void on_javaBrowseBtn_clicked(); void on_javaStatusBtn_clicked(); void on_javaDownloadBtn_clicked(); - void checkFinished(JavaCheckResult result); + void checkFinished(JavaChecker::Result result); protected: /* methods */ void checkJavaPathOnEdit(const QString& path); @@ -89,5 +89,5 @@ class JavaSettingsWidget : public QWidget { QString queuedCheck; uint64_t m_availableMemory = 0ull; shared_qobject_ptr m_checker; - JavaCheckResult m_result; + JavaChecker::Result m_result; };