Upload via GUI (39 Dateien)

This commit is contained in:
Git Manager GUI
2026-08-02 17:01:18 +02:00
parent a803f297ea
commit eff6403dd5
26 changed files with 3536 additions and 134 deletions

212
src/javadl.js Normal file
View File

@@ -0,0 +1,212 @@
'use strict';
/*
* javadl.js Java bei Bedarf selbst beschaffen
* ----------------------------------------------
* services.js sucht nur nach vorhandenen Java-Installationen. Wer keine hat,
* kam bisher nicht ins Spiel. Dieses Modul lädt die passende Laufzeit von
* Adoptium (Eclipse Temurin) und legt sie unter <userData>/java ab.
*
* Ablauf: Release suchen -> ZIP laden -> entpacken -> java.exe zurückgeben.
*/
const fs = require('fs');
const path = require('path');
const unzipper = require('unzipper');
const API = 'https://api.adoptium.net/v3';
const UA = 'AeroMC-Launcher';
let javaRoot = null;
// Ablageort festlegen (üblicherweise <userData>/java)
function setJavaDir(dir) {
javaRoot = dir;
return javaRoot;
}
function getJavaDir() { return javaRoot; }
/*
* Sucht das neueste Release für eine Major-Version.
* Bevorzugt die JRE (rund halb so groß), fällt sonst auf das JDK zurück.
*/
async function findRelease(major) {
for (const imageType of ['jre', 'jdk']) {
const url = new URL(`${API}/assets/latest/${major}/hotspot`);
url.searchParams.set('architecture', 'x64');
url.searchParams.set('image_type', imageType);
url.searchParams.set('os', 'windows');
url.searchParams.set('vendor', 'eclipse');
const res = await fetch(url, { headers: { 'User-Agent': UA, Accept: 'application/json' } });
if (!res.ok) continue;
const liste = await res.json();
const treffer = (Array.isArray(liste) ? liste : [])
.find((a) => a && a.binary && a.binary.package && a.binary.package.link);
if (!treffer) continue;
return {
major,
imageType,
releaseName: treffer.release_name || `temurin-${major}`,
url: treffer.binary.package.link,
size: Number(treffer.binary.package.size) || 0,
sha256: treffer.binary.package.checksum || '',
};
}
return null;
}
// Ordnername, unter dem eine Laufzeit abgelegt wird
function zielOrdner(release) {
const safe = String(release.releaseName).replace(/[^\w.+-]/g, '_');
return path.join(javaRoot, `${safe}-${release.imageType}`);
}
// java.exe innerhalb einer entpackten Laufzeit finden (bin/ liegt eine Ebene tiefer)
function findeJavaExe(wurzel) {
const direkt = path.join(wurzel, 'bin', 'java.exe');
if (fs.existsSync(direkt)) return direkt;
let eintraege = [];
try { eintraege = fs.readdirSync(wurzel, { withFileTypes: true }); } catch { return null; }
for (const e of eintraege) {
if (!e.isDirectory()) continue;
const p = path.join(wurzel, e.name, 'bin', 'java.exe');
if (fs.existsSync(p)) return p;
}
return null;
}
/*
* Bereits heruntergeladene Laufzeiten auflisten.
* -> [{ path, dir, major }]
*/
function listManaged() {
if (!javaRoot || !fs.existsSync(javaRoot)) return [];
const out = [];
for (const name of fs.readdirSync(javaRoot)) {
const dir = path.join(javaRoot, name);
let exe = null;
try { if (fs.statSync(dir).isDirectory()) exe = findeJavaExe(dir); } catch { continue; }
if (!exe) continue;
const m = name.match(/(?:jdk-?|temurin-?)(\d+)/i);
out.push({ path: exe, dir, major: m ? Number(m[1]) : null });
}
return out;
}
// schon eine passende Laufzeit da?
function findManaged(major) {
return listManaged().find((j) => j.major === major) || null;
}
async function ladeDatei(url, ziel, onProgress) {
const res = await fetch(url, { headers: { 'User-Agent': UA } });
if (!res.ok) throw new Error('Java-Download fehlgeschlagen: HTTP ' + res.status);
const gesamt = Number(res.headers.get('content-length')) || 0;
fs.mkdirSync(path.dirname(ziel), { recursive: true });
const out = fs.createWriteStream(ziel);
let geladen = 0;
let letzte = 0;
try {
for await (const stueck of res.body) {
const buf = Buffer.from(stueck);
geladen += buf.length;
if (!out.write(buf)) await new Promise((resolve, reject) => {
const onDrain = () => { out.off('error', onError); resolve(); };
const onError = (e) => { out.off('drain', onDrain); reject(e); };
out.once('drain', onDrain);
out.once('error', onError);
});
if (onProgress && Date.now() - letzte >= 250) {
letzte = Date.now();
onProgress({ phase: 'java-download', geladen, gesamt, percent: gesamt ? Math.round((geladen / gesamt) * 100) : 0 });
}
}
await new Promise((resolve, reject) => {
out.once('error', reject);
out.end((err) => (err ? reject(err) : resolve()));
});
} catch (err) {
out.destroy();
fs.rmSync(ziel, { force: true });
throw err;
}
return geladen;
}
// Pfade aus dem Archiv gegen Ausbrüche absichern
function saubererPfad(eintrag) {
const p = String(eintrag || '').replace(/\\/g, '/');
if (!p || p.startsWith('/') || /^[a-z]:/i.test(p)) return null;
if (p.split('/').includes('..')) return null;
return p;
}
async function entpacken(zipPfad, zielDir, onProgress) {
const archiv = await unzipper.Open.file(zipPfad);
const dateien = (archiv.files || []).filter((e) => e.type !== 'Directory');
fs.mkdirSync(zielDir, { recursive: true });
let fertig = 0;
let letzte = 0;
for (const eintrag of archiv.files || []) {
const rel = saubererPfad(eintrag.path);
if (!rel) continue;
const ziel = path.join(zielDir, rel);
if (eintrag.type === 'Directory') { fs.mkdirSync(ziel, { recursive: true }); continue; }
fs.mkdirSync(path.dirname(ziel), { recursive: true });
await new Promise((resolve, reject) => {
eintrag.stream()
.pipe(fs.createWriteStream(ziel))
.on('finish', resolve)
.on('error', reject);
});
fertig++;
if (onProgress && Date.now() - letzte >= 250) {
letzte = Date.now();
onProgress({ phase: 'java-entpacken', fertig, gesamt: dateien.length,
percent: dateien.length ? Math.round((fertig / dateien.length) * 100) : 0 });
}
}
return fertig;
}
/*
* Stellt sicher, dass eine Java-Laufzeit der gewünschten Major-Version da ist.
* -> { path, major, geladen } oder null
*/
async function ensureJava(major, onProgress) {
if (!javaRoot) throw new Error('Java-Ablageort ist nicht gesetzt.');
const vorhanden = findManaged(major);
if (vorhanden) return { path: vorhanden.path, major, geladen: false };
const release = await findRelease(major);
if (!release) throw new Error(`Für Java ${major} wird bei Adoptium keine Windows-Fassung angeboten.`);
const ziel = zielOrdner(release);
const tmpZip = path.join(javaRoot, `.download-${Date.now()}.zip`);
fs.mkdirSync(javaRoot, { recursive: true });
try {
if (onProgress) onProgress({ phase: 'java-download', geladen: 0, gesamt: release.size, percent: 0 });
await ladeDatei(release.url, tmpZip, onProgress);
// in einen Nebenordner entpacken und erst am Ende umbenennen -> kein
// halbfertiges Java, falls unterwegs etwas schiefgeht
const tmpDir = ziel + '.teil';
fs.rmSync(tmpDir, { recursive: true, force: true });
await entpacken(tmpZip, tmpDir, onProgress);
fs.rmSync(ziel, { recursive: true, force: true });
fs.renameSync(tmpDir, ziel);
} finally {
fs.rmSync(tmpZip, { force: true });
}
const exe = findeJavaExe(ziel);
if (!exe) throw new Error('Im heruntergeladenen Archiv wurde keine java.exe gefunden.');
return { path: exe, major, geladen: true, dir: ziel };
}
module.exports = { setJavaDir, getJavaDir, ensureJava, findRelease, listManaged, findManaged, findeJavaExe };