java implementation and downloading
This commit is contained in:
+293
-8
@@ -2,7 +2,10 @@
|
||||
|
||||
#include "authservice.h"
|
||||
#include "gamelauncher.h"
|
||||
#include "javainstaller.h"
|
||||
#include "javalocator.h"
|
||||
#include "javaruntime.h"
|
||||
#include "javaruntimeservice.h"
|
||||
#include "launcherpaths.h"
|
||||
#include "versioninstaller.h"
|
||||
#include "versionmanifestservice.h"
|
||||
@@ -47,6 +50,8 @@ LauncherBackend::LauncherBackend(QObject *parent)
|
||||
, m_loaderMeta(new ModLoaderVersionService(this))
|
||||
, m_loaderInstaller(new ModLoaderInstaller(m_loaderMeta, m_installer, this))
|
||||
, m_switcher(new BuildSwitcher(this))
|
||||
, m_javaMeta(new JavaRuntimeService(this))
|
||||
, m_javaInstaller(new JavaInstaller(this))
|
||||
{
|
||||
// Папка лаунчера создаётся при первом запуске и проверяется при каждом
|
||||
// следующем — если её удалили, она появится снова.
|
||||
@@ -88,6 +93,41 @@ LauncherBackend::LauncherBackend(QObject *parent)
|
||||
connect(m_manifest, &VersionManifestService::refreshingChanged,
|
||||
this, &LauncherBackend::catalogLoadingChanged);
|
||||
|
||||
connect(m_javaMeta, &JavaRuntimeService::catalogChanged,
|
||||
this, &LauncherBackend::invalidateJavaCatalog);
|
||||
connect(m_javaMeta, &JavaRuntimeService::refreshingChanged,
|
||||
this, &LauncherBackend::javaCatalogLoadingChanged);
|
||||
|
||||
connect(m_javaInstaller, &JavaInstaller::started, this, [this](const QString &label) {
|
||||
emit downloadChanged();
|
||||
emit busyChanged();
|
||||
emit launchProgress(tr("Загрузка Java «%1»…").arg(label));
|
||||
});
|
||||
connect(m_javaInstaller, &JavaInstaller::progressChanged,
|
||||
this, &LauncherBackend::downloadChanged);
|
||||
connect(m_javaInstaller, &JavaInstaller::finished, this,
|
||||
[this](const QString &runtimeId, const QString &javaPath) {
|
||||
invalidateJavaCatalog();
|
||||
emit downloadChanged();
|
||||
emit busyChanged();
|
||||
emit javaRuntimeInstalled(runtimeId);
|
||||
emit launchProgress(tr("Java установлена: %1")
|
||||
.arg(QDir::toNativeSeparators(javaPath)));
|
||||
});
|
||||
connect(m_javaInstaller, &JavaInstaller::failed, this,
|
||||
[this](const QString &label, const QString &message) {
|
||||
invalidateJavaCatalog();
|
||||
emit downloadChanged();
|
||||
emit busyChanged();
|
||||
emit launchError(tr("Не удалось установить Java «%1»: %2").arg(label, message));
|
||||
});
|
||||
connect(m_javaInstaller, &JavaInstaller::canceled, this, [this](const QString &label) {
|
||||
invalidateJavaCatalog();
|
||||
emit downloadChanged();
|
||||
emit busyChanged();
|
||||
emit launchProgress(tr("Загрузка Java «%1» отменена").arg(label));
|
||||
});
|
||||
|
||||
connect(m_installer, &VersionInstaller::started, this, [this](const QString &id) {
|
||||
emit downloadChanged();
|
||||
emit busyChanged();
|
||||
@@ -653,19 +693,32 @@ void LauncherBackend::cancelDownload()
|
||||
// Установщик лоадера сам остановит вложенную установку версии.
|
||||
if (m_loaderInstaller->isRunning())
|
||||
m_loaderInstaller->cancel();
|
||||
else
|
||||
else if (m_installer->isRunning())
|
||||
m_installer->cancel();
|
||||
else
|
||||
m_javaInstaller->cancel();
|
||||
}
|
||||
|
||||
// Панель загрузки одна на два установщика: пока идёт установка модлоадера, она
|
||||
// показывает его стадии, включая вложенную загрузку файлов версии.
|
||||
// Панель загрузки одна на три установщика: пока идёт установка модлоадера, она
|
||||
// показывает его стадии, включая вложенную загрузку файлов версии. Установка
|
||||
// Java начинается только когда остальные не заняты, поэтому за панель они не спорят.
|
||||
bool LauncherBackend::downloading() const
|
||||
{
|
||||
return m_loaderInstaller->isRunning() || m_installer->isRunning();
|
||||
return m_loaderInstaller->isRunning() || m_installer->isRunning()
|
||||
|| m_javaInstaller->isRunning();
|
||||
}
|
||||
|
||||
// Java качается сама по себе, а версия и лоадер — вместе; отсюда и порядок проверок.
|
||||
bool LauncherBackend::javaDownloadShown() const
|
||||
{
|
||||
return m_javaInstaller->isRunning() && !m_installer->isRunning()
|
||||
&& !m_loaderInstaller->isRunning();
|
||||
}
|
||||
|
||||
double LauncherBackend::downloadProgress() const
|
||||
{
|
||||
if (javaDownloadShown())
|
||||
return m_javaInstaller->fraction();
|
||||
if (!m_loaderInstaller->isRunning())
|
||||
return m_installer->fraction();
|
||||
// Внутри установки лоадера файлы качает VersionInstaller — его доля точнее.
|
||||
@@ -674,6 +727,8 @@ double LauncherBackend::downloadProgress() const
|
||||
|
||||
QString LauncherBackend::downloadVersion() const
|
||||
{
|
||||
if (javaDownloadShown())
|
||||
return m_javaInstaller->label();
|
||||
if (m_loaderInstaller->isRunning())
|
||||
return m_loaderInstaller->label();
|
||||
return m_installer->versionId();
|
||||
@@ -683,7 +738,10 @@ QString LauncherBackend::downloadStatus() const
|
||||
{
|
||||
QString stage = m_installer->stage();
|
||||
QString file = m_installer->currentFile();
|
||||
if (m_loaderInstaller->isRunning() && !m_installer->isRunning()) {
|
||||
if (javaDownloadShown()) {
|
||||
stage = m_javaInstaller->stage();
|
||||
file = m_javaInstaller->currentFile();
|
||||
} else if (m_loaderInstaller->isRunning() && !m_installer->isRunning()) {
|
||||
stage = m_loaderInstaller->stage();
|
||||
file = m_loaderInstaller->currentFile();
|
||||
}
|
||||
@@ -696,6 +754,8 @@ QString LauncherBackend::downloadStatus() const
|
||||
|
||||
qint64 LauncherBackend::downloadBytesDone() const
|
||||
{
|
||||
if (javaDownloadShown())
|
||||
return m_javaInstaller->bytesDone();
|
||||
if (m_loaderInstaller->isRunning() && !m_installer->isRunning())
|
||||
return m_loaderInstaller->bytesDone();
|
||||
return m_installer->bytesDone();
|
||||
@@ -703,6 +763,8 @@ qint64 LauncherBackend::downloadBytesDone() const
|
||||
|
||||
qint64 LauncherBackend::downloadBytesTotal() const
|
||||
{
|
||||
if (javaDownloadShown())
|
||||
return m_javaInstaller->bytesTotal();
|
||||
if (m_loaderInstaller->isRunning() && !m_installer->isRunning())
|
||||
return m_loaderInstaller->bytesTotal();
|
||||
return m_installer->bytesTotal();
|
||||
@@ -791,7 +853,9 @@ void LauncherBackend::installLoaderForBuild(int index)
|
||||
*loader,
|
||||
build.minecraftVersion,
|
||||
build.loaderVersion,
|
||||
m_settings.value(QStringLiteral("javaPath")).toString());
|
||||
managedJavaPath().isEmpty()
|
||||
? m_settings.value(QStringLiteral("javaPath")).toString()
|
||||
: managedJavaPath());
|
||||
}
|
||||
|
||||
void LauncherBackend::launchGame(int profileIndex, int buildIndex)
|
||||
@@ -806,6 +870,13 @@ void LauncherBackend::launchGame(int profileIndex, int buildIndex)
|
||||
.arg(m_installer->versionId()));
|
||||
return;
|
||||
}
|
||||
// Игра ищет java при запуске: стартовать посреди её установки — значит
|
||||
// найти половину распакованного дерева.
|
||||
if (m_javaInstaller->isRunning()) {
|
||||
emit launchError(tr("Идёт установка Java «%1» — дождитесь окончания")
|
||||
.arg(m_javaInstaller->label()));
|
||||
return;
|
||||
}
|
||||
if (m_busy) {
|
||||
emit launchError(tr("Запуск уже выполняется"));
|
||||
return;
|
||||
@@ -1013,7 +1084,15 @@ void LauncherBackend::continueLaunch(const AuthResult &auth)
|
||||
options.clientToken = auth.clientToken;
|
||||
options.userType = auth.userType;
|
||||
|
||||
options.javaPath = m_settings.value(QStringLiteral("javaPath")).toString();
|
||||
// Скачанная лаунчером сборка важнее пути из настроек: её выбрали явно в
|
||||
// окне выбора версии Java.
|
||||
options.javaPath = managedJavaPath();
|
||||
if (options.javaPath.isEmpty()) {
|
||||
if (!m_settings.value(QStringLiteral("javaRuntime")).toString().trimmed().isEmpty())
|
||||
emit launchProgress(tr("Выбранная сборка Java не найдена в папке лаунчера — "
|
||||
"ищем установленную в системе"));
|
||||
options.javaPath = m_settings.value(QStringLiteral("javaPath")).toString();
|
||||
}
|
||||
options.minMemoryMb = settingInt(m_settings, QStringLiteral("minMemoryMb"), 512);
|
||||
options.maxMemoryMb = settingInt(m_settings, QStringLiteral("maxMemoryMb"), 4096);
|
||||
options.windowWidth = settingInt(m_settings, QStringLiteral("windowWidth"), 0);
|
||||
@@ -1081,7 +1160,7 @@ QString LauncherBackend::switchStatus() const
|
||||
bool LauncherBackend::busy() const
|
||||
{
|
||||
return m_busy || m_installer->isRunning() || m_loaderInstaller->isRunning()
|
||||
|| m_switcher->isRunning();
|
||||
|| m_javaInstaller->isRunning() || m_switcher->isRunning();
|
||||
}
|
||||
|
||||
void LauncherBackend::setBusy(bool busy)
|
||||
@@ -1133,6 +1212,210 @@ QStringList LauncherBackend::detectedJava() const
|
||||
return result;
|
||||
}
|
||||
|
||||
// ── Сборки Java ────────────────────────────────────────────────────────────
|
||||
|
||||
void LauncherBackend::invalidateJavaCatalog()
|
||||
{
|
||||
m_javaCatalogDirty = true;
|
||||
emit javaCatalogChanged();
|
||||
}
|
||||
|
||||
bool LauncherBackend::javaCatalogLoading() const
|
||||
{
|
||||
return m_javaMeta->isRefreshing();
|
||||
}
|
||||
|
||||
void LauncherBackend::refreshJavaCatalog(bool force)
|
||||
{
|
||||
// Список установленного мог поменяться и без нас (папку удалили руками).
|
||||
invalidateJavaCatalog();
|
||||
m_javaMeta->ensureLoaded([this](bool ok, const QString &warning) {
|
||||
if (!ok && !warning.isEmpty())
|
||||
emit launchError(warning);
|
||||
}, force);
|
||||
}
|
||||
|
||||
QVariantList LauncherBackend::javaCatalog() const
|
||||
{
|
||||
if (m_javaCatalogDirty) {
|
||||
rebuildJavaCatalog();
|
||||
m_javaCatalogDirty = false;
|
||||
}
|
||||
return m_javaCatalog;
|
||||
}
|
||||
|
||||
void LauncherBackend::rebuildJavaCatalog() const
|
||||
{
|
||||
m_javaCatalog.clear();
|
||||
|
||||
const QList<InstalledJavaRuntime> installed = JavaRuntimeStore::installed();
|
||||
QHash<QString, qint64> installedSizes;
|
||||
for (const InstalledJavaRuntime &runtime : installed)
|
||||
installedSizes.insert(runtime.id, runtime.size);
|
||||
|
||||
const auto megabytes = [](qint64 bytes) {
|
||||
return bytes > 0 ? int((bytes + 512 * 1024) / (1024 * 1024)) : 0;
|
||||
};
|
||||
|
||||
const auto makeRow = [&megabytes](const QString &id,
|
||||
const QString &kind,
|
||||
int major,
|
||||
const QString &version,
|
||||
const QString &detail,
|
||||
qint64 size,
|
||||
bool isInstalled,
|
||||
bool lts,
|
||||
bool downloadable) {
|
||||
return QVariantMap{
|
||||
{QStringLiteral("id"), id},
|
||||
{QStringLiteral("kind"), kind},
|
||||
{QStringLiteral("major"), major},
|
||||
{QStringLiteral("version"), version},
|
||||
{QStringLiteral("label"), QStringLiteral("Java %1").arg(version)},
|
||||
{QStringLiteral("detail"), detail},
|
||||
{QStringLiteral("coverage"), JavaRequirement::coverage(major)},
|
||||
{QStringLiteral("sizeMb"), megabytes(size)},
|
||||
{QStringLiteral("installed"), isInstalled},
|
||||
{QStringLiteral("lts"), lts},
|
||||
{QStringLiteral("downloadable"), downloadable},
|
||||
{QStringLiteral("search"),
|
||||
QStringLiteral("%1 %2 %3 %4").arg(version, QString::number(major), kind, detail).toLower()}};
|
||||
};
|
||||
|
||||
// Скачанное — сверху: этим уже можно пользоваться прямо сейчас.
|
||||
QSet<QString> listed;
|
||||
for (const InstalledJavaRuntime &runtime : installed) {
|
||||
listed.insert(runtime.id);
|
||||
const auto remote = m_javaMeta->find(runtime.id);
|
||||
const QString detail = remote ? (remote->kind == JavaRuntimeKind::Mojang
|
||||
? tr("Mojang · %1").arg(remote->component)
|
||||
: tr("Temurin · %1").arg(remote->architecture))
|
||||
: tr("скачана лаунчером");
|
||||
m_javaCatalog.append(makeRow(runtime.id,
|
||||
runtime.kind,
|
||||
runtime.major,
|
||||
runtime.version,
|
||||
detail,
|
||||
runtime.size,
|
||||
true,
|
||||
remote && remote->lts,
|
||||
remote.has_value()));
|
||||
}
|
||||
|
||||
const QList<JavaRuntimeKind> kinds{JavaRuntimeKind::Mojang, JavaRuntimeKind::Jdk,
|
||||
JavaRuntimeKind::Jre};
|
||||
for (const JavaRuntimeKind kind : kinds) {
|
||||
const QList<JavaRuntimeEntry> entries = m_javaMeta->entries(kind);
|
||||
for (const JavaRuntimeEntry &entry : entries) {
|
||||
if (listed.contains(entry.id))
|
||||
continue;
|
||||
const QString detail = kind == JavaRuntimeKind::Mojang
|
||||
? tr("Mojang · %1").arg(entry.component)
|
||||
: tr("Temurin · %1").arg(entry.architecture);
|
||||
m_javaCatalog.append(makeRow(entry.id,
|
||||
javaKindKey(kind),
|
||||
entry.major,
|
||||
entry.version,
|
||||
detail,
|
||||
entry.size,
|
||||
false,
|
||||
entry.lts,
|
||||
true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LauncherBackend::installJavaRuntime(const QString &runtimeId)
|
||||
{
|
||||
if (m_javaInstaller->isRunning()) {
|
||||
emit launchError(tr("Идёт установка Java «%1» — дождитесь окончания")
|
||||
.arg(m_javaInstaller->label()));
|
||||
return;
|
||||
}
|
||||
if (m_installer->isRunning() || m_loaderInstaller->isRunning() || m_switcher->isRunning()) {
|
||||
emit launchError(tr("Дождитесь окончания текущей операции"));
|
||||
return;
|
||||
}
|
||||
|
||||
const auto entry = m_javaMeta->find(runtimeId);
|
||||
if (!entry) {
|
||||
emit launchError(tr("Сборка Java «%1» не найдена в каталоге").arg(runtimeId));
|
||||
return;
|
||||
}
|
||||
m_javaInstaller->install(*entry);
|
||||
}
|
||||
|
||||
void LauncherBackend::removeJavaRuntime(const QString &runtimeId)
|
||||
{
|
||||
if (m_javaInstaller->isRunning() && m_javaInstaller->runtimeId() == runtimeId) {
|
||||
emit launchError(tr("Эта сборка Java сейчас скачивается"));
|
||||
return;
|
||||
}
|
||||
|
||||
QString error;
|
||||
if (!JavaRuntimeStore::remove(runtimeId, &error)) {
|
||||
emit launchError(tr("Не удалось удалить сборку Java: %1").arg(error));
|
||||
return;
|
||||
}
|
||||
|
||||
// Удалили выбранную — настройка указывала бы в пустоту.
|
||||
if (m_settings.value(QStringLiteral("javaRuntime")).toString() == runtimeId) {
|
||||
m_settings.insert(QStringLiteral("javaRuntime"), QString());
|
||||
saveSettings();
|
||||
emit settingsChanged();
|
||||
}
|
||||
invalidateJavaCatalog();
|
||||
emit launchProgress(tr("Сборка Java удалена"));
|
||||
}
|
||||
|
||||
QVariantMap LauncherBackend::javaRuntimeInfo(const QString &runtimeId) const
|
||||
{
|
||||
if (runtimeId.trimmed().isEmpty())
|
||||
return {};
|
||||
|
||||
const auto runtime = JavaRuntimeStore::find(runtimeId);
|
||||
if (runtime) {
|
||||
return QVariantMap{{QStringLiteral("id"), runtime->id},
|
||||
{QStringLiteral("kind"), runtime->kind},
|
||||
{QStringLiteral("major"), runtime->major},
|
||||
{QStringLiteral("version"), runtime->version},
|
||||
{QStringLiteral("label"), QStringLiteral("Java %1").arg(runtime->version)},
|
||||
{QStringLiteral("javaPath"), QDir::toNativeSeparators(runtime->javaPath)},
|
||||
{QStringLiteral("installed"), true}};
|
||||
}
|
||||
|
||||
// Выбрана, но ещё не скачана: подпись всё равно нужна.
|
||||
const auto entry = m_javaMeta->find(runtimeId);
|
||||
if (!entry)
|
||||
return QVariantMap{{QStringLiteral("id"), runtimeId},
|
||||
{QStringLiteral("label"), runtimeId},
|
||||
{QStringLiteral("installed"), false}};
|
||||
|
||||
return QVariantMap{{QStringLiteral("id"), entry->id},
|
||||
{QStringLiteral("kind"), javaKindKey(entry->kind)},
|
||||
{QStringLiteral("major"), entry->major},
|
||||
{QStringLiteral("version"), entry->version},
|
||||
{QStringLiteral("label"), QStringLiteral("Java %1").arg(entry->version)},
|
||||
{QStringLiteral("javaPath"), QString()},
|
||||
{QStringLiteral("installed"), false}};
|
||||
}
|
||||
|
||||
int LauncherBackend::requiredJavaMajor(int buildIndex) const
|
||||
{
|
||||
if (buildIndex < 0 || buildIndex >= m_builds.size())
|
||||
return 0;
|
||||
return JavaRequirement::minimumFor(m_builds.at(buildIndex).minecraftVersion);
|
||||
}
|
||||
|
||||
QString LauncherBackend::managedJavaPath() const
|
||||
{
|
||||
const QString id = m_settings.value(QStringLiteral("javaRuntime")).toString();
|
||||
if (id.trimmed().isEmpty())
|
||||
return {};
|
||||
const auto runtime = JavaRuntimeStore::find(id);
|
||||
return runtime ? runtime->javaPath : QString();
|
||||
}
|
||||
|
||||
void LauncherBackend::openMinecraftFolder()
|
||||
{
|
||||
const QString modsDir = gameDir() + QStringLiteral("/mods");
|
||||
@@ -1396,6 +1679,8 @@ void LauncherBackend::loadSettings()
|
||||
{
|
||||
m_settings = {{QStringLiteral("gameDir"), QString()},
|
||||
{QStringLiteral("javaPath"), QString()},
|
||||
// id сборки Java из <root>/java. Пусто — java ищется сама.
|
||||
{QStringLiteral("javaRuntime"), QString()},
|
||||
{QStringLiteral("minMemoryMb"), 512},
|
||||
{QStringLiteral("maxMemoryMb"), 4096},
|
||||
{QStringLiteral("jvmArgs"), QString()},
|
||||
|
||||
Reference in New Issue
Block a user