diff options
Diffstat (limited to 'meshmc/launcher/ui/dialogs/JavaDownloadDialog.cpp')
| -rw-r--r-- | meshmc/launcher/ui/dialogs/JavaDownloadDialog.cpp | 399 |
1 files changed, 399 insertions, 0 deletions
diff --git a/meshmc/launcher/ui/dialogs/JavaDownloadDialog.cpp b/meshmc/launcher/ui/dialogs/JavaDownloadDialog.cpp new file mode 100644 index 0000000000..c83542543c --- /dev/null +++ b/meshmc/launcher/ui/dialogs/JavaDownloadDialog.cpp @@ -0,0 +1,399 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * 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 "JavaDownloadDialog.h" + +#include <QVBoxLayout> +#include <QHBoxLayout> +#include <QGroupBox> +#include <QMessageBox> +#include <QJsonDocument> +#include <QJsonObject> +#include <QJsonArray> +#include <QDebug> +#include <QDir> +#include <QHeaderView> +#include <QSplitter> + +#include "Application.h" +#include "BuildConfig.h" +#include "FileSystem.h" +#include "Json.h" +#include "java/JavaUtils.h" +#include "net/Download.h" + +JavaDownloadDialog::JavaDownloadDialog(QWidget* parent) : QDialog(parent) +{ + setWindowTitle(tr("Download Java")); + setMinimumSize(700, 450); + resize(800, 500); + m_providers = JavaDownload::JavaProviderInfo::availableProviders(); + setupUi(); +} + +void JavaDownloadDialog::setupUi() +{ + auto* mainLayout = new QVBoxLayout(this); + + // --- Three-column selection area --- + auto* columnsLayout = new QHBoxLayout(); + + // Left: Provider list + auto* providerGroup = new QGroupBox(tr("Provider"), this); + auto* providerLayout = new QVBoxLayout(providerGroup); + m_providerList = new QListWidget(this); + m_providerList->setIconSize(QSize(24, 24)); + for (const auto& provider : m_providers) { + auto* item = new QListWidgetItem( + APPLICATION->getThemedIcon(provider.iconName), provider.name); + m_providerList->addItem(item); + } + providerLayout->addWidget(m_providerList); + columnsLayout->addWidget(providerGroup, 1); + + // Center: Major version list + auto* versionGroup = new QGroupBox(tr("Version"), this); + auto* versionLayout = new QVBoxLayout(versionGroup); + m_versionList = new QListWidget(this); + versionLayout->addWidget(m_versionList); + columnsLayout->addWidget(versionGroup, 1); + + // Right: Sub-version / build list + auto* subVersionGroup = new QGroupBox(tr("Build"), this); + auto* subVersionLayout = new QVBoxLayout(subVersionGroup); + m_subVersionList = new QListWidget(this); + subVersionLayout->addWidget(m_subVersionList); + columnsLayout->addWidget(subVersionGroup, 1); + + mainLayout->addLayout(columnsLayout, 1); + + // Info label + m_infoLabel = new QLabel(this); + m_infoLabel->setWordWrap(true); + m_infoLabel->setText( + tr("Select a Java provider, version, and build to download.")); + mainLayout->addWidget(m_infoLabel); + + // Progress bar + m_progressBar = new QProgressBar(this); + m_progressBar->setVisible(false); + m_progressBar->setRange(0, 100); + mainLayout->addWidget(m_progressBar); + + // Status label + m_statusLabel = new QLabel(this); + m_statusLabel->setVisible(false); + mainLayout->addWidget(m_statusLabel); + + // Buttons + auto* buttonLayout = new QHBoxLayout(); + buttonLayout->addStretch(); + + m_downloadBtn = new QPushButton(tr("Download"), this); + m_downloadBtn->setEnabled(false); + buttonLayout->addWidget(m_downloadBtn); + + m_cancelBtn = new QPushButton(tr("Cancel"), this); + buttonLayout->addWidget(m_cancelBtn); + + mainLayout->addLayout(buttonLayout); + + // Connections + connect(m_providerList, &QListWidget::currentRowChanged, this, + &JavaDownloadDialog::providerChanged); + connect(m_versionList, &QListWidget::currentRowChanged, this, + &JavaDownloadDialog::majorVersionChanged); + connect(m_subVersionList, &QListWidget::currentRowChanged, this, + &JavaDownloadDialog::subVersionChanged); + connect(m_downloadBtn, &QPushButton::clicked, this, + &JavaDownloadDialog::onDownloadClicked); + connect(m_cancelBtn, &QPushButton::clicked, this, + &JavaDownloadDialog::onCancelClicked); + + // Select first provider + if (m_providerList->count() > 0) { + m_providerList->setCurrentRow(0); + } +} + +void JavaDownloadDialog::providerChanged(int index) +{ + if (index < 0 || index >= m_providers.size()) + return; + + m_versionList->clear(); + m_subVersionList->clear(); + m_downloadBtn->setEnabled(false); + m_versions.clear(); + m_runtimes.clear(); + + const auto& provider = m_providers[index]; + m_infoLabel->setText(tr("Loading versions for %1...").arg(provider.name)); + + fetchVersionList(provider.uid); +} + +void JavaDownloadDialog::majorVersionChanged(int index) +{ + if (index < 0 || index >= m_versions.size()) + return; + + m_subVersionList->clear(); + m_downloadBtn->setEnabled(false); + m_runtimes.clear(); + + const auto& version = m_versions[index]; + m_infoLabel->setText(tr("Loading builds for %1...").arg(version.versionId)); + + fetchRuntimes(version.uid, version.versionId); +} + +void JavaDownloadDialog::subVersionChanged(int index) +{ + if (index < 0 || index >= m_runtimes.size()) { + m_downloadBtn->setEnabled(false); + return; + } + + const auto& rt = m_runtimes[index]; + m_infoLabel->setText(tr("Ready to download: %1\n" + "Version: %2\n" + "Platform: %3\n" + "Checksum: %4") + .arg(rt.name, rt.version.toString(), rt.runtimeOS, + rt.checksumHash.isEmpty() + ? tr("None") + : tr("Yes (%1)").arg(rt.checksumType))); + m_downloadBtn->setEnabled(true); +} + +void JavaDownloadDialog::fetchVersionList(const QString& uid) +{ + m_fetchJob.reset(); + m_fetchData.clear(); + + QString url = QString("%1%2/index.json").arg(BuildConfig.META_URL, uid); + m_fetchJob = new NetJob(tr("Fetch Java versions"), APPLICATION->network()); + auto dl = Net::Download::makeByteArray(QUrl(url), &m_fetchData); + m_fetchJob->addNetAction(dl); + + connect(m_fetchJob.get(), &NetJob::succeeded, this, [this, uid]() { + m_fetchJob.reset(); + + QJsonDocument doc; + try { + doc = Json::requireDocument(m_fetchData); + } catch (const Exception& e) { + m_infoLabel->setText( + tr("Failed to parse version list: %1").arg(e.cause())); + return; + } + if (!doc.isObject()) { + m_infoLabel->setText(tr("Failed to parse version list.")); + return; + } + + m_versions = JavaDownload::parseVersionIndex(doc.object(), uid); + m_versionList->clear(); + + for (const auto& ver : m_versions) { + QString displayName = ver.versionId; + if (displayName.startsWith("java")) { + displayName = "Java " + displayName.mid(4); + } + m_versionList->addItem(displayName); + } + + if (m_versions.size() > 0) { + m_infoLabel->setText(tr("Select a version.")); + } else { + m_infoLabel->setText( + tr("No versions available for this provider.")); + } + }); + + connect(m_fetchJob.get(), &NetJob::failed, this, [this](QString reason) { + m_fetchJob.reset(); + m_infoLabel->setText(tr("Failed to load versions: %1").arg(reason)); + }); + + m_fetchJob->start(); +} + +void JavaDownloadDialog::fetchRuntimes(const QString& uid, + const QString& versionId) +{ + m_fetchJob.reset(); + m_fetchData.clear(); + + QString url = + QString("%1%2/%3.json").arg(BuildConfig.META_URL, uid, versionId); + m_fetchJob = + new NetJob(tr("Fetch Java runtime details"), APPLICATION->network()); + auto dl = Net::Download::makeByteArray(QUrl(url), &m_fetchData); + m_fetchJob->addNetAction(dl); + + connect(m_fetchJob.get(), &NetJob::succeeded, this, [this]() { + m_fetchJob.reset(); + + QJsonDocument doc; + try { + doc = Json::requireDocument(m_fetchData); + } catch (const Exception& e) { + m_infoLabel->setText( + tr("Failed to parse runtime details: %1").arg(e.cause())); + return; + } + if (!doc.isObject()) { + m_infoLabel->setText(tr("Failed to parse runtime details.")); + return; + } + + auto allRuntimes = JavaDownload::parseRuntimes(doc.object()); + QString myOS = JavaDownload::currentRuntimeOS(); + + m_runtimes.clear(); + m_subVersionList->clear(); + for (const auto& rt : allRuntimes) { + if (rt.runtimeOS == myOS) { + m_runtimes.append(rt); + m_subVersionList->addItem(rt.version.toString()); + } + } + + if (m_runtimes.isEmpty()) { + m_infoLabel->setText( + tr("No builds available for your platform (%1).").arg(myOS)); + m_downloadBtn->setEnabled(false); + } else { + m_infoLabel->setText(tr("Select a build to download.")); + m_subVersionList->setCurrentRow(0); + } + }); + + connect(m_fetchJob.get(), &NetJob::failed, this, [this](QString reason) { + m_fetchJob.reset(); + m_infoLabel->setText( + tr("Failed to load runtime details: %1").arg(reason)); + }); + + m_fetchJob->start(); +} + +QString JavaDownloadDialog::javaInstallDir() const +{ + return JavaUtils::managedJavaRoot(); +} + +void JavaDownloadDialog::onDownloadClicked() +{ + int idx = m_subVersionList->currentRow(); + if (idx < 0 || idx >= m_runtimes.size()) + return; + + const auto& runtime = m_runtimes[idx]; + + // Build target directory path: {dataPath}/java/{vendor}/{name}-{version}/ + QString dirName = + QString("%1-%2").arg(runtime.name, runtime.version.toString()); + QString targetDir = + FS::PathCombine(javaInstallDir(), runtime.vendor, dirName); + + // Check if already installed + if (QDir(targetDir).exists()) { + auto result = QMessageBox::question( + this, tr("Already Installed"), + tr("This Java version appears to be already installed at:\n%1\n\n" + "Do you want to reinstall it?") + .arg(targetDir), + QMessageBox::Yes | QMessageBox::No); + if (result != QMessageBox::Yes) { + return; + } + // Remove existing installation + QDir(targetDir).removeRecursively(); + } + + m_downloadBtn->setEnabled(false); + m_providerList->setEnabled(false); + m_versionList->setEnabled(false); + m_subVersionList->setEnabled(false); + m_progressBar->setVisible(true); + m_progressBar->setValue(0); + m_statusLabel->setVisible(true); + + m_downloadTask = + std::make_unique<JavaDownloadTask>(runtime, targetDir, this); + + connect(m_downloadTask.get(), &Task::progress, this, + [this](qint64 current, qint64 total) { + if (total > 0) { + m_progressBar->setValue( + static_cast<int>(current * 100 / total)); + } + }); + + connect(m_downloadTask.get(), &Task::status, this, + [this](const QString& status) { m_statusLabel->setText(status); }); + + connect(m_downloadTask.get(), &Task::succeeded, this, [this]() { + m_installedJavaPath = m_downloadTask->installedJavaPath(); + m_progressBar->setValue(100); + m_statusLabel->setText(tr("Java installed successfully!")); + + QMessageBox::information( + this, tr("Download Complete"), + tr("Java has been downloaded and installed successfully.\n\n" + "Java binary: %1") + .arg(m_installedJavaPath)); + + accept(); + }); + + connect(m_downloadTask.get(), &Task::failed, this, + [this](const QString& reason) { + m_progressBar->setVisible(false); + m_statusLabel->setText(tr("Download failed: %1").arg(reason)); + m_downloadBtn->setEnabled(true); + m_providerList->setEnabled(true); + m_versionList->setEnabled(true); + m_subVersionList->setEnabled(true); + + QMessageBox::warning( + this, tr("Download Failed"), + tr("Failed to download Java:\n%1").arg(reason)); + }); + + m_downloadTask->start(); +} + +void JavaDownloadDialog::onCancelClicked() +{ + if (m_fetchJob) { + m_fetchJob->abort(); + m_fetchJob.reset(); + } + if (m_downloadTask) { + m_downloadTask->abort(); + m_downloadTask.reset(); + } + reject(); +} |
