Files
PrismLauncher/launcher/ui/java/InstallJavaDialog.cpp
morsz 2219c37d7f fix: force metadata version list refreshes to reload
manual refreshes on version selection screens could reuse cached metadata and skip downloading updated manifests, so new versions would not appear until Prism was restarted. pass an explicit forced reload through the shared version list loading path and use it from refresh actions so manual refresh always reloads metadata

Signed-off-by: morsz <morsz@morsz.dev>
2026-04-10 02:19:28 +02:00

361 lines
14 KiB
C++

// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2024 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 "InstallJavaDialog.h"
#include <QCheckBox>
#include <QDialogButtonBox>
#include <QMessageBox>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
#include "Application.h"
#include "BaseVersionList.h"
#include "FileSystem.h"
#include "Filter.h"
#include "java/download/ArchiveDownloadTask.h"
#include "java/download/ManifestDownloadTask.h"
#include "java/download/SymlinkTask.h"
#include "meta/Index.h"
#include "meta/VersionList.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
#include "tasks/SequentialTask.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/ProgressDialog.h"
#include "ui/java/VersionList.h"
#include "ui/widgets/PageContainer.h"
#include "ui/widgets/VersionSelectWidget.h"
class InstallJavaPage : public QWidget, public BasePage {
public:
Q_OBJECT
public:
explicit InstallJavaPage(const QString& id, const QString& iconName, const QString& name, QWidget* parent = nullptr)
: QWidget(parent), uid(id), iconName(iconName), name(name)
{
setObjectName(QStringLiteral("VersionSelectWidget"));
horizontalLayout = new QHBoxLayout(this);
horizontalLayout->setObjectName(QStringLiteral("horizontalLayout"));
horizontalLayout->setContentsMargins(0, 0, 0, 0);
majorVersionSelect = new VersionSelectWidget(this);
majorVersionSelect->selectCurrent();
majorVersionSelect->setEmptyString(tr("No Java versions are currently available in the meta."));
majorVersionSelect->setEmptyErrorString(tr("Couldn't load or download the Java version lists!"));
horizontalLayout->addWidget(majorVersionSelect, 1);
javaVersionSelect = new VersionSelectWidget(this);
javaVersionSelect->setEmptyString(tr("No Java versions are currently available for your OS."));
javaVersionSelect->setEmptyErrorString(tr("Couldn't load or download the Java version lists!"));
horizontalLayout->addWidget(javaVersionSelect, 4);
connect(majorVersionSelect, &VersionSelectWidget::selectedVersionChanged, this, &InstallJavaPage::setSelectedVersion);
connect(majorVersionSelect, &VersionSelectWidget::selectedVersionChanged, this, &InstallJavaPage::selectionChanged);
connect(javaVersionSelect, &VersionSelectWidget::selectedVersionChanged, this, &InstallJavaPage::selectionChanged);
QMetaObject::connectSlotsByName(this);
}
~InstallJavaPage()
{
delete horizontalLayout;
delete majorVersionSelect;
delete javaVersionSelect;
}
//! loads the list if needed.
void initialize(Meta::VersionList::Ptr vlist)
{
vlist->setProvidedRoles({ BaseVersionList::JavaMajorRole, BaseVersionList::RecommendedRole, BaseVersionList::VersionPointerRole });
majorVersionSelect->initialize(vlist.get());
}
void setSelectedVersion(BaseVersion::Ptr version)
{
auto dcast = std::dynamic_pointer_cast<Meta::Version>(version);
if (!dcast) {
return;
}
javaVersionSelect->initialize(new Java::VersionList(dcast, this));
javaVersionSelect->selectCurrent();
}
QString id() const override { return uid; }
QString displayName() const override { return name; }
QIcon icon() const override { return QIcon::fromTheme(iconName); }
void openedImpl() override
{
if (loaded)
return;
const auto versions = APPLICATION->metadataIndex()->get(uid);
if (!versions)
return;
initialize(versions);
loaded = true;
}
void setParentContainer(BasePageContainer* container) override
{
auto dialog = dynamic_cast<QDialog*>(dynamic_cast<PageContainer*>(container)->parent());
connect(javaVersionSelect->view(), &QAbstractItemView::doubleClicked, dialog, &QDialog::accept);
}
BaseVersion::Ptr selectedVersion() const { return javaVersionSelect->selectedVersion(); }
void selectSearch() { javaVersionSelect->selectSearch(); }
void loadList()
{
majorVersionSelect->loadList(true);
javaVersionSelect->loadList(true);
}
public slots:
void setRecommendedMajors(const QStringList& majors)
{
m_recommended_majors = majors;
recommendedFilterChanged();
}
void setRecommend(bool recommend)
{
m_recommend = recommend;
recommendedFilterChanged();
}
void recommendedFilterChanged()
{
if (m_recommend) {
majorVersionSelect->setFilter(BaseVersionList::ModelRoles::JavaMajorRole, Filters::equalsAny(m_recommended_majors));
} else {
majorVersionSelect->setFilter(BaseVersionList::ModelRoles::JavaMajorRole, Filters::equalsAny());
}
}
signals:
void selectionChanged();
private:
const QString uid;
const QString iconName;
const QString name;
bool loaded = false;
QHBoxLayout* horizontalLayout = nullptr;
VersionSelectWidget* majorVersionSelect = nullptr;
VersionSelectWidget* javaVersionSelect = nullptr;
QStringList m_recommended_majors;
bool m_recommend;
};
static InstallJavaPage* pageCast(BasePage* page)
{
auto result = dynamic_cast<InstallJavaPage*>(page);
Q_ASSERT(result != nullptr);
return result;
}
namespace Java {
QStringList getRecommendedJavaVersionsFromVersionList(Meta::VersionList::Ptr list)
{
QStringList recommendedJavas;
for (auto ver : list->versions()) {
auto major = ver->version();
if (major.startsWith("java")) {
major = "Java " + major.mid(4);
}
recommendedJavas.append(major);
}
return recommendedJavas;
}
InstallDialog::InstallDialog(const QString& uid, BaseInstance* instance, QWidget* parent)
: QDialog(parent), container(new PageContainer(this, QString(), this)), buttons(new QDialogButtonBox(this))
{
auto layout = new QVBoxLayout(this);
// small margins look ugly on macOS on modal windows
#ifndef Q_OS_MACOS
layout->setContentsMargins(0, 0, 0, 0);
#endif
container->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
layout->addWidget(container);
auto buttonLayout = new QHBoxLayout(this);
// small margins look ugly on macOS on modal windows
#ifndef Q_OS_MACOS
buttonLayout->setContentsMargins(0, 0, 6, 6);
#endif
auto refreshLayout = new QHBoxLayout(this);
auto refreshButton = new QPushButton(tr("&Refresh"), this);
connect(refreshButton, &QPushButton::clicked, this, [this] { pageCast(container->selectedPage())->loadList(); });
refreshLayout->addWidget(refreshButton);
auto recommendedCheckBox = new QCheckBox("Recommended", this);
recommendedCheckBox->setCheckState(Qt::CheckState::Checked);
connect(recommendedCheckBox, &QCheckBox::stateChanged, this, [this](int state) {
for (BasePage* page : container->getPages()) {
pageCast(page)->setRecommend(state == Qt::Checked);
}
});
refreshLayout->addWidget(recommendedCheckBox);
buttonLayout->addLayout(refreshLayout);
buttons->setOrientation(Qt::Horizontal);
buttons->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok);
buttons->button(QDialogButtonBox::Ok)->setText(tr("Download"));
buttons->button(QDialogButtonBox::Cancel)->setText(tr("Cancel"));
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
buttonLayout->addWidget(buttons);
container->addButtons(buttonLayout);
setWindowTitle(dialogTitle());
setWindowModality(Qt::WindowModal);
resize(840, 480);
QStringList recommendedJavas;
if (auto mcInst = dynamic_cast<MinecraftInstance*>(instance); mcInst) {
auto mc = mcInst->getPackProfile()->getComponent("net.minecraft");
if (mc) {
auto file = mc->getVersionFile(); // no need for load as it should already be loaded
if (file) {
for (auto major : file->compatibleJavaMajors) {
recommendedJavas.append(QString("Java %1").arg(major));
}
}
}
} else {
const auto versions = APPLICATION->metadataIndex()->get("net.minecraft.java");
if (versions) {
if (versions->isLoaded()) {
recommendedJavas = getRecommendedJavaVersionsFromVersionList(versions);
} else {
auto newTask = versions->getLoadTask();
if (newTask) {
connect(newTask.get(), &Task::succeeded, this, [this, versions] {
auto recommendedJavas = getRecommendedJavaVersionsFromVersionList(versions);
for (BasePage* page : container->getPages()) {
pageCast(page)->setRecommendedMajors(recommendedJavas);
}
});
if (!newTask->isRunning())
newTask->start();
} else {
recommendedJavas = getRecommendedJavaVersionsFromVersionList(versions);
}
}
}
}
for (BasePage* page : container->getPages()) {
if (page->id() == uid)
container->selectPage(page->id());
auto cast = pageCast(page);
cast->setRecommend(true);
connect(cast, &InstallJavaPage::selectionChanged, this, [this, cast] { validate(cast); });
if (!recommendedJavas.isEmpty()) {
cast->setRecommendedMajors(recommendedJavas);
}
}
connect(container, &PageContainer::selectedPageChanged, this, [this](BasePage* previous, BasePage* selected) { validate(selected); });
pageCast(container->selectedPage())->selectSearch();
validate(container->selectedPage());
}
QList<BasePage*> InstallDialog::getPages()
{
return {
// Mojang
new InstallJavaPage("net.minecraft.java", "mojang", tr("Mojang")),
// Adoptium
new InstallJavaPage("net.adoptium.java", "adoptium", tr("Adoptium")),
// Azul
new InstallJavaPage("com.azul.java", "azul", tr("Azul Zulu")),
// IBM
/* Must watch out in case the AdoptOpenJDK infrastructure is deprecated.
In case of happening, IBM does not seem to provide as of today (03/2026) an API like Adoptium does and rather uses GitHub directly in its website: `developer.ibm.com`.
GitHub is known for rate limiting requests that do not use an API key from an account. */
new InstallJavaPage("com.ibm.java", "openj9_hex_custom", tr("IBM Semeru Open")),
};
}
QString InstallDialog::dialogTitle()
{
return tr("Install Java");
}
void InstallDialog::validate(BasePage* selected)
{
buttons->button(QDialogButtonBox::Ok)->setEnabled(!!std::dynamic_pointer_cast<Java::Metadata>(pageCast(selected)->selectedVersion()));
}
void InstallDialog::done(int result)
{
if (result == Accepted) {
auto* page = pageCast(container->selectedPage());
if (page->selectedVersion()) {
auto meta = std::dynamic_pointer_cast<Java::Metadata>(page->selectedVersion());
if (meta) {
Task::Ptr task;
auto final_path = FS::PathCombine(APPLICATION->javaPath(), meta->m_name);
auto deletePath = [final_path] { FS::deletePath(final_path); };
switch (meta->downloadType) {
case Java::DownloadType::Manifest:
task = makeShared<ManifestDownloadTask>(meta->url, final_path, meta->checksumType, meta->checksumHash);
break;
case Java::DownloadType::Archive:
task = makeShared<ArchiveDownloadTask>(meta->url, final_path, meta->checksumType, meta->checksumHash);
break;
case Java::DownloadType::Unknown:
QString error = QString(tr("Could not determine Java download type!"));
CustomMessageBox::selectable(this, tr("Error"), error, QMessageBox::Warning)->show();
deletePath();
return;
}
#if defined(Q_OS_MACOS)
auto seq = makeShared<SequentialTask>(tr("Install Java"));
seq->addTask(task);
seq->addTask(makeShared<Java::SymlinkTask>(final_path));
task = seq;
#endif
connect(task.get(), &Task::failed, this, [this, &deletePath](QString reason) {
QString error = QString("Java download failed: %1").arg(reason);
CustomMessageBox::selectable(this, tr("Error"), error, QMessageBox::Warning)->show();
deletePath();
});
connect(task.get(), &Task::aborted, this, deletePath);
ProgressDialog pg(this);
pg.setSkipButton(true, tr("Abort"));
pg.execWithTask(task.get());
} else {
return;
}
} else {
return;
}
}
QDialog::done(result);
}
} // namespace Java
#include "InstallJavaDialog.moc"