This commit is contained in:
Linrador 2025-08-14 15:06:59 +02:00
parent ae11e8e17d
commit 64faf5ced6
7 changed files with 180 additions and 99 deletions

View File

@ -12,6 +12,8 @@ const stream_1 = require("stream");
const util_1 = require("util");
const unbzip2_stream_1 = __importDefault(require("unbzip2-stream"));
const pipe = (0, util_1.promisify)(stream_1.pipeline);
// In-Flight-Lock pro Ziel-.dem
const inflight = new Map();
/**
* Entpackt eine .bz2-Datei mithilfe von Streams nach .dem
*/
@ -114,54 +116,99 @@ async function downloadDemoFile(match, outputBaseDir = 'demos', onProgress) {
const isPremier = !!lastRound?.b_switched_teams;
const matchType = isPremier ? 'premier' : 'competitive';
const tempDir = path_1.default.join(outputBaseDir, 'temp');
const tempFileName = `match${appId}_${mapName}_${matchId}_${matchType}.bz2`;
const baseName = path_1.default.parse(tempFileName).name;
const tempFile = path_1.default.join(tempDir, tempFileName);
const finalDir = path_1.default.join(outputBaseDir, matchDate);
const finalFile = path_1.default.join(finalDir, `${baseName}.dem`);
// Stabiler finaler Basisname (wichtig fürs De-Duplizieren)
const baseFinalName = `match${appId}_${mapName}_${matchId}_${matchType}`;
const finalFile = path_1.default.join(finalDir, `${baseFinalName}.dem`);
const finalFileName = path_1.default.basename(finalFile);
fs_1.default.mkdirSync(tempDir, { recursive: true });
fs_1.default.mkdirSync(finalDir, { recursive: true });
console.log(`📥 Lade Demo von ${demoUrl}...`);
try {
const success = await downloadWithHttps(demoUrl, tempFile, onProgress);
if (!success || !fs_1.default.existsSync(tempFile) || fs_1.default.statSync(tempFile).size === 0) {
console.warn(`⚠️ Download fehlgeschlagen oder Datei leer lösche ${tempFileName}`);
const partialFile = `${finalFile}.part`;
// Bereits vorhanden? -> sofort zurück
if (fs_1.default.existsSync(finalFile)) {
console.log(`♻️ Demo existiert bereits: ${finalFileName}`);
return finalFile;
}
// Parallele Requests für dieselbe Zieldatei zusammenlegen
if (inflight.has(finalFile)) {
return await inflight.get(finalFile);
}
// Eindeutiger Temp-Dateiname im temp/
const rand = Math.random().toString(36).slice(2, 8);
const tempFileName = `${baseFinalName}_${rand}.bz2`;
const tempFile = path_1.default.join(tempDir, tempFileName);
const job = (async () => {
fs_1.default.mkdirSync(tempDir, { recursive: true });
fs_1.default.mkdirSync(finalDir, { recursive: true });
// Stale .part von früheren Abbrüchen entfernen
try {
if (fs_1.default.existsSync(partialFile))
fs_1.default.unlinkSync(partialFile);
}
catch { }
console.log(`📥 Lade Demo von ${demoUrl}...`);
try {
const success = await downloadWithHttps(demoUrl, tempFile, onProgress);
if (!success || !fs_1.default.existsSync(tempFile) || fs_1.default.statSync(tempFile).size === 0) {
console.warn(`⚠️ Download fehlgeschlagen oder Datei leer lösche ${tempFileName}`);
try {
if (fs_1.default.existsSync(tempFile))
fs_1.default.unlinkSync(tempFile);
}
catch { }
return '';
}
}
catch (err) {
throw new Error(`❌ Fehler beim Download: ${err instanceof Error ? err.message : err}`);
}
console.log(`✅ Gespeichert als ${tempFileName}`);
const entpackZeile = `🗜️ Entpacke nach ${path_1.default.basename(partialFile)}...`;
process.stdout.write(entpackZeile);
try {
await extractBz2Safe(tempFile, partialFile);
}
catch (e) {
try {
if (fs_1.default.existsSync(partialFile))
fs_1.default.unlinkSync(partialFile);
}
catch { }
try {
if (fs_1.default.existsSync(tempFile))
fs_1.default.unlinkSync(tempFile);
}
catch {
console.warn(`⚠️ Konnte leere Datei nicht löschen: ${tempFileName}`);
}
return '';
catch { }
throw e;
}
}
catch (err) {
throw new Error(`❌ Fehler beim Download: ${err instanceof Error ? err.message : err}`);
}
console.log(`✅ Gespeichert als ${tempFileName}`);
const entpackZeile = `🗜️ Entpacke ${finalFileName}...`;
process.stdout.write(entpackZeile);
await extractBz2Safe(tempFile, finalFile);
const successMessage = `✅ Entpackt: ${finalFileName}`;
const failMessage = `❌ Entpackung fehlgeschlagen Datei nicht vorhanden`;
// Max-Zeichenlänge bestimmen
const maxLength = Math.max(entpackZeile.length, successMessage.length, failMessage.length);
if (!fs_1.default.existsSync(finalFile)) {
const paddedFail = failMessage.padEnd(maxLength, ' ');
process.stdout.write(`\r${paddedFail}\n`);
throw new Error(failMessage);
}
const paddedSuccess = successMessage.padEnd(maxLength, ' ');
process.stdout.write(`\r${paddedSuccess}\n`);
// Aufräumen
// Prüfen & atomar nach .dem verschieben
const successMessage = `✅ Entpackt: ${finalFileName}`;
const failMessage = `❌ Entpackung fehlgeschlagen Datei nicht vorhanden`;
const maxLength = Math.max(entpackZeile.length, successMessage.length, failMessage.length);
if (!fs_1.default.existsSync(partialFile)) {
process.stdout.write(`\r${failMessage.padEnd(maxLength, ' ')}\n`);
try {
if (fs_1.default.existsSync(tempFile))
fs_1.default.unlinkSync(tempFile);
}
catch { }
throw new Error('Entpackung fehlgeschlagen');
}
fs_1.default.renameSync(partialFile, finalFile);
process.stdout.write(`\r${successMessage.padEnd(maxLength, ' ')}\n`);
// Aufräumen
try {
fs_1.default.unlinkSync(tempFile);
console.log(`🧹 Gelöscht: ${tempFileName}`);
}
catch {
console.log(`⚠️ Konnte temporäre Datei nicht löschen: ${tempFileName}`);
}
return finalFile;
})();
inflight.set(finalFile, job);
try {
fs_1.default.unlinkSync(tempFile);
console.log(`🧹 Gelöscht: ${tempFileName}`);
return await job;
}
catch {
console.log(`⚠️ Konnte temporäre Datei nicht löschen: ${tempFileName}`);
finally {
inflight.delete(finalFile);
}
return finalFile;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

16
dist/main.js vendored
View File

@ -49,12 +49,16 @@ async function start() {
console.log('✅ Download abgeschlossen');
}
});
// JSON-Dateipfad erstellen
const jsonFilePath = demoFilePath.replace(/\.dem$/, '.json');
const jsonFileName = path_1.default.basename(jsonFilePath);
// Match-Daten als JSON schreiben
await fs_1.default.promises.writeFile(jsonFilePath, JSON.stringify(match, null, 2), 'utf-8');
console.log(`📝 Match-Daten gespeichert unter: ${jsonFileName}`);
// JSON nur erstellen, wenn die .dem existiert
if (demoFilePath && fs_1.default.existsSync(demoFilePath)) {
const jsonFilePath = demoFilePath.replace(/\.dem$/, '.json');
const jsonFileName = path_1.default.basename(jsonFilePath);
await fs_1.default.promises.writeFile(jsonFilePath, JSON.stringify(match, null, 2), 'utf-8');
console.log(`📝 Match-Daten gespeichert unter: ${jsonFileName}`);
}
else {
console.warn('⚠️ Keine Demo-Datei vorhanden JSON wird nicht erstellt.');
}
// Antwort an den Client
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: true, path: demoFilePath }));

View File

@ -8,6 +8,9 @@ import bz2 from 'unbzip2-stream';
const pipe = promisify(pipeline);
// In-Flight-Lock pro Ziel-.dem
const inflight: Map<string, Promise<string>> = new Map();
/**
* Entpackt eine .bz2-Datei mithilfe von Streams nach .dem
*/
@ -107,8 +110,6 @@ function downloadWithHttps(
});
}
/**
* Hauptfunktion: lädt und entpackt eine CS2-Demo (.bz2), mit Fortschrittsanzeige.
*/
@ -146,66 +147,92 @@ export async function downloadDemoFile(
const matchType = isPremier ? 'premier' : 'competitive';
const tempDir = path.join(outputBaseDir, 'temp');
const tempFileName = `match${appId}_${mapName}_${matchId}_${matchType}.bz2`;
const baseName = path.parse(tempFileName).name;
const finalDir = path.join(outputBaseDir, matchDate);
// Stabiler finaler Basisname (wichtig fürs De-Duplizieren)
const baseFinalName = `match${appId}_${mapName}_${matchId}_${matchType}`;
const finalFile = path.join(finalDir, `${baseFinalName}.dem`);
const finalFileName = path.basename(finalFile);
const partialFile = `${finalFile}.part`;
// Bereits vorhanden? -> sofort zurück
if (fs.existsSync(finalFile)) {
console.log(`♻️ Demo existiert bereits: ${finalFileName}`);
return finalFile;
}
// Parallele Requests für dieselbe Zieldatei zusammenlegen
if (inflight.has(finalFile)) {
return await inflight.get(finalFile)!;
}
// Eindeutiger Temp-Dateiname im temp/
const rand = Math.random().toString(36).slice(2, 8);
const tempFileName = `${baseFinalName}_${rand}.bz2`;
const tempFile = path.join(tempDir, tempFileName);
const finalDir = path.join(outputBaseDir, matchDate);
const finalFile = path.join(finalDir, `${baseName}.dem`);
const finalFileName = path.basename(finalFile)
const job = (async () => {
fs.mkdirSync(tempDir, { recursive: true });
fs.mkdirSync(finalDir, { recursive: true });
fs.mkdirSync(tempDir, { recursive: true });
fs.mkdirSync(finalDir, { recursive: true });
// Stale .part von früheren Abbrüchen entfernen
try { if (fs.existsSync(partialFile)) fs.unlinkSync(partialFile); } catch {}
console.log(`📥 Lade Demo von ${demoUrl}...`);
console.log(`📥 Lade Demo von ${demoUrl}...`);
try {
const success = await downloadWithHttps(demoUrl, tempFile, onProgress);
if (!success || !fs.existsSync(tempFile) || fs.statSync(tempFile).size === 0) {
console.warn(`⚠️ Download fehlgeschlagen oder Datei leer lösche ${tempFileName}`);
try {
if (fs.existsSync(tempFile)) fs.unlinkSync(tempFile);
} catch {
console.warn(`⚠️ Konnte leere Datei nicht löschen: ${tempFileName}`);
try {
const success = await downloadWithHttps(demoUrl, tempFile, onProgress);
if (!success || !fs.existsSync(tempFile) || fs.statSync(tempFile).size === 0) {
console.warn(`⚠️ Download fehlgeschlagen oder Datei leer lösche ${tempFileName}`);
try { if (fs.existsSync(tempFile)) fs.unlinkSync(tempFile); } catch {}
return '';
}
return '';
} catch (err) {
throw new Error(`❌ Fehler beim Download: ${err instanceof Error ? err.message : err}`);
}
} catch (err) {
throw new Error(`❌ Fehler beim Download: ${err instanceof Error ? err.message : err}`);
}
console.log(`✅ Gespeichert als ${tempFileName}`);
console.log(`✅ Gespeichert als ${tempFileName}`);
const entpackZeile = `🗜️ Entpacke nach ${path.basename(partialFile)}...`;
process.stdout.write(entpackZeile);
const entpackZeile = `🗜️ Entpacke ${finalFileName}...`;
process.stdout.write(entpackZeile);
try {
await extractBz2Safe(tempFile, partialFile);
} catch (e) {
try { if (fs.existsSync(partialFile)) fs.unlinkSync(partialFile); } catch {}
try { if (fs.existsSync(tempFile)) fs.unlinkSync(tempFile); } catch {}
throw e;
}
await extractBz2Safe(tempFile, finalFile);
// Prüfen & atomar nach .dem verschieben
const successMessage = `✅ Entpackt: ${finalFileName}`;
const failMessage = `❌ Entpackung fehlgeschlagen Datei nicht vorhanden`;
const maxLength = Math.max(entpackZeile.length, successMessage.length, failMessage.length);
const successMessage = `✅ Entpackt: ${finalFileName}`;
const failMessage = `❌ Entpackung fehlgeschlagen Datei nicht vorhanden`;
if (!fs.existsSync(partialFile)) {
process.stdout.write(`\r${failMessage.padEnd(maxLength, ' ')}\n`);
try { if (fs.existsSync(tempFile)) fs.unlinkSync(tempFile); } catch {}
throw new Error('Entpackung fehlgeschlagen');
}
// Max-Zeichenlänge bestimmen
const maxLength = Math.max(entpackZeile.length, successMessage.length, failMessage.length);
fs.renameSync(partialFile, finalFile);
process.stdout.write(`\r${successMessage.padEnd(maxLength, ' ')}\n`);
if (!fs.existsSync(finalFile)) {
const paddedFail = failMessage.padEnd(maxLength, ' ');
process.stdout.write(`\r${paddedFail}\n`);
throw new Error(failMessage);
}
// Aufräumen
try {
fs.unlinkSync(tempFile);
console.log(`🧹 Gelöscht: ${tempFileName}`);
} catch {
console.log(`⚠️ Konnte temporäre Datei nicht löschen: ${tempFileName}`);
}
const paddedSuccess = successMessage.padEnd(maxLength, ' ');
process.stdout.write(`\r${paddedSuccess}\n`);
return finalFile;
})();
// Aufräumen
inflight.set(finalFile, job);
try {
fs.unlinkSync(tempFile);
console.log(`🧹 Gelöscht: ${tempFileName}`);
} catch {
console.log(`⚠️ Konnte temporäre Datei nicht löschen: ${tempFileName}`);
return await job;
} finally {
inflight.delete(finalFile);
}
return finalFile;
}

View File

@ -56,13 +56,16 @@ async function start() {
}
);
// JSON-Dateipfad erstellen
const jsonFilePath = demoFilePath.replace(/\.dem$/, '.json');
const jsonFileName = path.basename(jsonFilePath)
// JSON nur erstellen, wenn die .dem existiert
if (demoFilePath && fs.existsSync(demoFilePath)) {
const jsonFilePath = demoFilePath.replace(/\.dem$/, '.json');
const jsonFileName = path.basename(jsonFilePath);
// Match-Daten als JSON schreiben
await fs.promises.writeFile(jsonFilePath, JSON.stringify(match, null, 2), 'utf-8');
console.log(`📝 Match-Daten gespeichert unter: ${jsonFileName}`);
await fs.promises.writeFile(jsonFilePath, JSON.stringify(match, null, 2), 'utf-8');
console.log(`📝 Match-Daten gespeichert unter: ${jsonFileName}`);
} else {
console.warn('⚠️ Keine Demo-Datei vorhanden JSON wird nicht erstellt.');
}
// Antwort an den Client
res.writeHead(200, { 'Content-Type': 'application/json' });