diff options
Diffstat (limited to 'meshmc/launcher/java/download/JavaDownloadTask.cpp')
| -rw-r--r-- | meshmc/launcher/java/download/JavaDownloadTask.cpp | 344 |
1 files changed, 344 insertions, 0 deletions
diff --git a/meshmc/launcher/java/download/JavaDownloadTask.cpp b/meshmc/launcher/java/download/JavaDownloadTask.cpp new file mode 100644 index 0000000000..eb73c4d6d5 --- /dev/null +++ b/meshmc/launcher/java/download/JavaDownloadTask.cpp @@ -0,0 +1,344 @@ +/* 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 "JavaDownloadTask.h" + +#include <QDir> +#include <QProcess> +#include <QDebug> +#include <QDirIterator> +#include <QFileInfo> + +#include "Application.h" +#include "FileSystem.h" +#include "Json.h" +#include "net/Download.h" +#include "net/ChecksumValidator.h" +#include "MMCZip.h" + +JavaDownloadTask::JavaDownloadTask(const JavaDownload::RuntimeEntry& runtime, + const QString& targetDir, QObject* parent) + : Task(parent), m_runtime(runtime), m_targetDir(targetDir) +{ +} + +void JavaDownloadTask::executeTask() +{ + if (m_runtime.downloadType == "manifest") { + downloadManifest(); + } else { + downloadArchive(); + } +} + +void JavaDownloadTask::downloadArchive() +{ + setStatus(tr("Downloading %1...").arg(m_runtime.name)); + + // Determine archive extension and path + QUrl url(m_runtime.url); + QString filename = url.fileName(); + m_archivePath = FS::PathCombine(m_targetDir, filename); + + // Create target directory + if (!FS::ensureFolderPathExists(m_targetDir)) { + emitFailed( + tr("Failed to create target directory: %1").arg(m_targetDir)); + return; + } + + m_downloadJob = new NetJob(tr("Java download"), APPLICATION->network()); + + auto dl = Net::Download::makeFile(m_runtime.url, m_archivePath); + + // Add checksum validation + if (!m_runtime.checksumHash.isEmpty()) { + QCryptographicHash::Algorithm algo = QCryptographicHash::Sha256; + if (m_runtime.checksumType == "sha1") + algo = QCryptographicHash::Sha1; + auto validator = new Net::ChecksumValidator( + algo, QByteArray::fromHex(m_runtime.checksumHash.toLatin1())); + dl->addValidator(validator); + } + + m_downloadJob->addNetAction(dl); + + connect(m_downloadJob.get(), &NetJob::succeeded, this, + &JavaDownloadTask::downloadFinished); + connect(m_downloadJob.get(), &NetJob::failed, this, + &JavaDownloadTask::downloadFailed); + connect(m_downloadJob.get(), &NetJob::progress, this, &Task::setProgress); + + m_downloadJob->start(); +} + +void JavaDownloadTask::downloadFinished() +{ + m_downloadJob.reset(); + extractArchive(); +} + +void JavaDownloadTask::downloadFailed(QString reason) +{ + m_downloadJob.reset(); + // Clean up partial download + if (!m_archivePath.isEmpty() && !QFile::remove(m_archivePath)) + qWarning() << "Failed to remove partial download:" << m_archivePath; + emitFailed(tr("Failed to download Java: %1").arg(reason)); +} + +void JavaDownloadTask::extractArchive() +{ + setStatus(tr("Extracting %1...").arg(m_runtime.name)); + + bool success = false; + + if (m_archivePath.endsWith(".zip")) { + // Use QuaZip for zip files + auto result = MMCZip::extractDir(m_archivePath, m_targetDir); + success = result.has_value(); + } else if (m_archivePath.endsWith(".tar.gz") || + m_archivePath.endsWith(".tgz")) { + // Use system tar for tar.gz files + QProcess tarProcess; + tarProcess.setWorkingDirectory(m_targetDir); + tarProcess.start("tar", QStringList() << "xzf" << m_archivePath); + tarProcess.waitForFinished(300000); // 5 minute timeout + success = (tarProcess.exitCode() == 0 && + tarProcess.exitStatus() == QProcess::NormalExit); + if (!success) { + qWarning() << "tar extraction failed:" + << tarProcess.readAllStandardError(); + } + } else { + if (!QFile::remove(m_archivePath)) + qWarning() << "Failed to remove archive:" << m_archivePath; + emitFailed(tr("Unsupported archive format: %1").arg(m_archivePath)); + return; + } + + // Clean up archive file + if (!QFile::remove(m_archivePath)) + qWarning() << "Failed to remove archive:" << m_archivePath; + + if (!success) { + emitFailed(tr("Failed to extract Java archive.")); + return; + } + + // Fix permissions on extracted files + QDirIterator it(m_targetDir, QDirIterator::Subdirectories); + while (it.hasNext()) { + auto filepath = it.next(); + QFileInfo file(filepath); + auto permissions = QFile::permissions(filepath); + if (file.isDir()) { + permissions |= QFileDevice::ReadUser | QFileDevice::WriteUser | + QFileDevice::ExeUser; + } else { + permissions |= QFileDevice::ReadUser | QFileDevice::WriteUser; + } + QFile::setPermissions(filepath, permissions); + } + + // Find the java binary + m_installedJavaPath = findJavaBinary(m_targetDir); + if (m_installedJavaPath.isEmpty()) { + emitFailed(tr("Could not find java binary in extracted archive.")); + return; + } + + // Make java binary executable + auto perms = QFile::permissions(m_installedJavaPath) | + QFileDevice::ExeUser | QFileDevice::ExeGroup | + QFileDevice::ExeOther; + QFile::setPermissions(m_installedJavaPath, perms); + + qDebug() << "Java installed successfully at:" << m_installedJavaPath; + emitSucceeded(); +} + +void JavaDownloadTask::downloadManifest() +{ + setStatus(tr("Downloading manifest for %1...").arg(m_runtime.name)); + + if (!FS::ensureFolderPathExists(m_targetDir)) { + emitFailed( + tr("Failed to create target directory: %1").arg(m_targetDir)); + return; + } + + m_downloadJob = + new NetJob(tr("Java manifest download"), APPLICATION->network()); + auto dl = + Net::Download::makeByteArray(QUrl(m_runtime.url), &m_manifestData); + + if (m_runtime.checksumType == "sha1" && !m_runtime.checksumHash.isEmpty()) { + dl->addValidator(new Net::ChecksumValidator( + QCryptographicHash::Sha1, + QByteArray::fromHex(m_runtime.checksumHash.toLatin1()))); + } + + m_downloadJob->addNetAction(dl); + + connect(m_downloadJob.get(), &NetJob::succeeded, this, + &JavaDownloadTask::manifestDownloaded); + connect(m_downloadJob.get(), &NetJob::failed, this, + &JavaDownloadTask::downloadFailed); + + m_downloadJob->start(); +} + +void JavaDownloadTask::manifestDownloaded() +{ + m_downloadJob.reset(); + + QJsonDocument doc; + try { + doc = Json::requireDocument(m_manifestData); + } catch (const Exception& e) { + m_manifestData.clear(); + emitFailed(tr("Failed to parse Java manifest: %1").arg(e.cause())); + return; + } + m_manifestData.clear(); + + if (!doc.isObject()) { + emitFailed(tr("Failed to parse Java manifest.")); + return; + } + + auto files = doc.object()["files"].toObject(); + m_executableFiles.clear(); + m_linkEntries.clear(); + + // Create directories first + for (auto it = files.begin(); it != files.end(); ++it) { + auto entry = it.value().toObject(); + if (entry["type"].toString() == "directory") { + QDir().mkpath(FS::PathCombine(m_targetDir, it.key())); + } + } + + // Queue file downloads + setStatus(tr("Downloading %1 files...").arg(m_runtime.name)); + m_downloadJob = + new NetJob(tr("Java runtime files"), APPLICATION->network()); + + for (auto it = files.begin(); it != files.end(); ++it) { + auto entry = it.value().toObject(); + + if (entry["type"].toString() == "file") { + auto downloads = entry["downloads"].toObject(); + auto raw = downloads["raw"].toObject(); + + QString url = raw["url"].toString(); + QString sha1 = raw["sha1"].toString(); + QString filePath = FS::PathCombine(m_targetDir, it.key()); + + // Ensure parent directory exists + QFileInfo fi(filePath); + QDir().mkpath(fi.absolutePath()); + + auto dl = Net::Download::makeFile(QUrl(url), filePath); + if (!sha1.isEmpty()) { + dl->addValidator(new Net::ChecksumValidator( + QCryptographicHash::Sha1, + QByteArray::fromHex(sha1.toLatin1()))); + } + m_downloadJob->addNetAction(dl); + + if (entry["executable"].toBool()) { + m_executableFiles.append(filePath); + } + } else if (entry["type"].toString() == "link") { + m_linkEntries.append({it.key(), entry["target"].toString()}); + } + } + + connect(m_downloadJob.get(), &NetJob::succeeded, this, + &JavaDownloadTask::manifestFilesDownloaded); + connect(m_downloadJob.get(), &NetJob::failed, this, + &JavaDownloadTask::downloadFailed); + connect(m_downloadJob.get(), &NetJob::progress, this, &Task::setProgress); + + m_downloadJob->start(); +} + +void JavaDownloadTask::manifestFilesDownloaded() +{ + m_downloadJob.reset(); + + // Create symlinks + for (const auto& link : m_linkEntries) { + QString linkPath = FS::PathCombine(m_targetDir, link.first); + QFileInfo fi(linkPath); + QDir().mkpath(fi.absolutePath()); + QFile::link(link.second, linkPath); + } + + // Set executable permissions + for (const auto& path : m_executableFiles) { + QFile::setPermissions( + path, QFile::permissions(path) | QFileDevice::ExeUser | + QFileDevice::ExeGroup | QFileDevice::ExeOther); + } + + // Find java binary + m_installedJavaPath = findJavaBinary(m_targetDir); + if (m_installedJavaPath.isEmpty()) { + emitFailed(tr("Could not find java binary in downloaded runtime.")); + return; + } + + qDebug() << "Java installed successfully at:" << m_installedJavaPath; + emitSucceeded(); +} + +QString JavaDownloadTask::findJavaBinary(const QString& dir) const +{ +#if defined(Q_OS_WIN) + QString binaryName = "javaw.exe"; +#else + QString binaryName = "java"; +#endif + + // Search for java binary in bin/ subdirectories + QDirIterator it(dir, QStringList() << binaryName, QDir::Files, + QDirIterator::Subdirectories); + while (it.hasNext()) { + it.next(); + QString path = it.filePath(); + if (path.contains("/bin/")) { + return path; + } + } + + // Fallback: any match + QDirIterator it2(dir, QStringList() << binaryName, QDir::Files, + QDirIterator::Subdirectories); + if (it2.hasNext()) { + it2.next(); + return it2.filePath(); + } + + return QString(); +} |
