minecraft-core-master
Version:
Núcleo avanzado para launchers de Minecraft. Descarga, instala y ejecuta versiones de Minecraft, assets, librerías, Java y loaders de forma automática y eficiente.
359 lines (358 loc) • 14 kB
JavaScript
import { EventEmitter } from 'events';
import { ClientDownloader } from "../Minecraft/Version.js";
import { LibrariesDownloader } from "../Minecraft/Libraries.js";
import { AssetsDownloader } from "../Minecraft/Assets.js";
import { NativesDownloader } from "../Minecraft/Natives.js";
import { RuntimeDownloader } from "../Minecraft/Runtime.js";
class MinecraftDownloader extends EventEmitter {
downloaders = new Map();
totalBytes = 0;
downloadedBytes = 0;
isDownloading = false;
startTime = 0;
lastUpdateTime = 0;
lastDownloadedBytes = 0;
currentSpeed = 0;
eta = 0;
progressInterval = null;
activeDownloads = new Set();
// Estadísticas para detección de problemas
connectionErrors = 0;
lastWarningTime = 0;
warningCooldown = 10000; // 10 segundos entre advertencias
highSpeedThreshold = 50 * 1024 * 1024; // 50 MB/s
slowSpeedThreshold = 100 * 1024; // 100 KB/s
constructor() {
super();
}
getTotalMB() {
return (this.totalBytes / (1024 * 1024)).toFixed(2);
}
getTotalGB() {
return (this.totalBytes / (1024 * 1024 * 1024)).toFixed(2);
}
async StartDownload(options) {
if (this.isDownloading)
return;
this.isDownloading = true;
this.startTime = Date.now();
this.lastUpdateTime = Date.now();
this.lastDownloadedBytes = 0;
this.downloadedBytes = 0;
this.totalBytes = 0;
this.connectionErrors = 0;
// Verificar concurrencia alta ANTES de empezar
this.checkConcurrencyWarning(options);
this.emit("Start");
try {
// Calcular tamaño total exacto primero
await this.calculateTotalSize(options);
const totalMB = this.getTotalMB();
const totalGB = this.getTotalGB();
this.emit("TotalCalculated", { totalMB, totalGB });
// Iniciar monitor de progreso
this.startProgressMonitor();
// Ejecutar descargas en paralelo
await this.executeParallelDownloads();
this.stopProgressMonitor();
this.emit("Done");
}
catch (error) {
this.stopProgressMonitor();
this.emit("Error", error);
throw error;
}
finally {
this.isDownloading = false;
this.activeDownloads.clear();
}
}
checkConcurrencyWarning(options) {
const totalConcurry = this.calculateTotalConcurrency(options);
if (totalConcurry > 50) {
this.emitWarning({
type: 'high-concurrency',
message: `Concurrencia muy alta detectada (${totalConcurry} conexiones). Puede causar problemas en los servidores.`,
severity: 'high',
data: { totalConcurry, recommended: 20 }
});
}
else if (totalConcurry > 30) {
this.emitWarning({
type: 'high-concurrency',
message: `Concurrencia elevada (${totalConcurry} conexiones). Monitoreando rendimiento.`,
severity: 'medium',
data: { totalConcurry, recommended: 20 }
});
}
}
calculateTotalConcurrency(options) {
const sections = options.sections || {};
let total = 0;
total += sections.Client?.concurry ?? options.concurry;
total += sections.Libraries?.concurry ?? options.concurry;
total += sections.Assets?.concurry ?? options.concurry;
total += sections.Natives?.concurry ?? options.concurry;
total += sections.Runtime?.concurrency ?? options.concurry;
return total;
}
emitWarning(warning) {
const now = Date.now();
if (now - this.lastWarningTime < this.warningCooldown) {
return; // Evitar spam de advertencias
}
this.lastWarningTime = now;
this.emit("NetworkWarning", warning);
}
async calculateTotalSize(options) {
const sections = options.sections || {};
const clientConfig = {
version: options.version,
root: options.root,
concurry: sections.Client?.concurry ?? options.concurry,
decodeJson: sections.Client?.decodeJson ?? false,
maxRetries: sections.Client?.maxRetries ?? options.maxRetries
};
const librariesConfig = {
version: options.version,
root: options.root,
concurry: sections.Libraries?.concurry ?? options.concurry,
maxRetries: sections.Libraries?.maxRetries ?? options.maxRetries
};
const assetsConfig = {
version: options.version,
root: options.root,
concurry: sections.Assets?.concurry ?? options.concurry,
maxRetries: sections.Assets?.maxRetries ?? options.maxRetries
};
const nativesConfig = {
version: options.version,
root: options.root,
concurry: sections.Natives?.concurry ?? options.concurry,
maxRetries: sections.Natives?.maxRetries ?? options.maxRetries
};
const runtimeConfig = {
version: options.version,
root: options.root,
concurrency: sections.Runtime?.concurrency ?? options.concurry,
maxRetries: sections.Runtime?.maxRetries ?? options.maxRetries
};
const downloadersConfig = [
{ name: 'Client', class: ClientDownloader, config: clientConfig },
{ name: 'Libraries', class: LibrariesDownloader, config: librariesConfig },
{ name: 'Assets', class: AssetsDownloader, config: assetsConfig },
{ name: 'Natives', class: NativesDownloader, config: nativesConfig }
];
if (options.installJava) {
downloadersConfig.push({
name: 'Runtime',
class: RuntimeDownloader,
config: runtimeConfig
});
}
for (const { name, class: DownloaderClass, config } of downloadersConfig) {
try {
const downloader = new DownloaderClass(config);
const bytes = await downloader.getTotalBytes();
this.totalBytes += bytes;
this.emit("SectionSize", { name, size: this.formatBytes(bytes) });
this.downloaders.set(name, downloader);
}
catch (error) {
this.emit("SectionError", { name, error });
}
}
}
async executeParallelDownloads() {
const downloadPromises = [];
for (const [name, downloader] of this.downloaders) {
const promise = this.startSingleDownload(name, downloader);
this.activeDownloads.add(promise);
downloadPromises.push(promise);
// Iniciar descarga inmediatamente
promise.finally(() => {
this.activeDownloads.delete(promise);
});
}
// Esperar a que todas las descargas terminen
await Promise.all(downloadPromises);
}
async startSingleDownload(name, downloader) {
return new Promise((resolve, reject) => {
// Configurar event listeners para este descargador
downloader.on("Bytes", (bytes) => {
this.handleDownloadProgress(bytes);
});
downloader.on("Done", () => {
this.emit("SectionDone", name);
resolve();
});
downloader.on("Error", (error) => {
this.handleDownloadError(name, error);
this.emit("SectionError", { name, error });
reject(error);
});
downloader.on("Paused", () => {
this.emit("Paused");
});
downloader.on("Resumed", () => {
this.emit("Resumed");
});
downloader.on("Stopped", () => {
this.emit("Stopped");
reject(new Error(`Descarga de ${name} detenida`));
});
// Iniciar descarga
downloader.start().catch(reject);
});
}
handleDownloadError(section, error) {
this.connectionErrors++;
// Detectar ECONNRESET específicamente
if (error.code === 'ECONNRESET' || error.message?.includes('ECONNRESET')) {
this.emitWarning({
type: 'connection-reset',
message: `El servidor cerró la conexión (${section}). Demasiadas peticiones simultáneas.`,
severity: 'high',
data: { section, errorCount: this.connectionErrors, error: error.message }
});
}
// Advertencia después de múltiples errores
if (this.connectionErrors >= 3) {
this.emitWarning({
type: 'server-overload',
message: `Múltiples errores de conexión (${this.connectionErrors}). Los servidores pueden estar sobrecargados.`,
severity: 'medium',
data: { totalErrors: this.connectionErrors, section }
});
}
}
handleDownloadProgress(bytes) {
this.downloadedBytes += bytes;
// Calcular velocidad y ETA en tiempo real
const now = Date.now();
const timeDiff = (now - this.lastUpdateTime) / 1000; // en segundos
if (timeDiff >= 0.5) { // Actualizar cada 500ms para mayor precisión
const bytesDiff = this.downloadedBytes - this.lastDownloadedBytes;
const speed = bytesDiff / timeDiff;
// Detectar velocidad anormalmente alta
if (speed > this.highSpeedThreshold) {
this.emitWarning({
type: 'high-traffic',
message: `Tráfico de red muy alto (${this.formatBytes(speed)}/s). Puede saturar tu conexión.`,
severity: 'medium',
data: { speed, threshold: this.highSpeedThreshold }
});
}
// Detectar velocidad muy lenta
if (speed < this.slowSpeedThreshold && this.downloadedBytes > 1024 * 1024) {
this.emitWarning({
type: 'slow-download',
message: `Velocidad de descarga muy lenta (${this.formatBytes(speed)}/s). Verifica tu conexión.`,
severity: 'low',
data: { speed, threshold: this.slowSpeedThreshold }
});
}
this.currentSpeed = speed;
if (speed > 0) {
this.eta = (this.totalBytes - this.downloadedBytes) / speed;
}
this.lastUpdateTime = now;
this.lastDownloadedBytes = this.downloadedBytes;
// Emitir eventos de progreso
this.emitProgress();
}
}
startProgressMonitor() {
this.progressInterval = setInterval(() => {
if (!this.isDownloading) {
this.stopProgressMonitor();
return;
}
// Emitir progreso actual incluso si no hay nuevos datos recientes
this.emitProgress();
}, 1000);
}
stopProgressMonitor() {
if (this.progressInterval) {
clearInterval(this.progressInterval);
this.progressInterval = null;
}
}
emitProgress() {
const downloadedMB = (this.downloadedBytes / (1024 * 1024)).toFixed(2);
const downloadedGB = (this.downloadedBytes / (1024 * 1024 * 1024)).toFixed(2);
const percentage = this.totalBytes > 0 ? ((this.downloadedBytes / this.totalBytes) * 100).toFixed(1) : "0.0";
this.emit("Download-MB", downloadedMB);
this.emit("Download-GB", downloadedGB);
this.emit("SpeedDownload", this.formatBytes(this.currentSpeed));
this.emit("ETA", Math.ceil(this.eta));
this.emit("Percentage", percentage);
}
formatBytes(bytes) {
if (bytes >= 1073741824)
return (bytes / 1073741824).toFixed(2) + ' GB';
if (bytes >= 1048576)
return (bytes / 1048576).toFixed(2) + ' MB';
if (bytes >= 1024)
return (bytes / 1024).toFixed(2) + ' KB';
return Math.round(bytes) + ' B';
}
pause() {
for (const [_name, downloader] of this.downloaders) {
if (downloader.pause) {
downloader.pause();
}
}
this.emit("Paused");
}
resume() {
for (const [_name, downloader] of this.downloaders) {
if (downloader.resume) {
downloader.resume();
}
}
this.emit("Resumed");
}
stop() {
for (const [_name, downloader] of this.downloaders) {
if (downloader.stop) {
downloader.stop();
}
}
this.stopProgressMonitor();
this.isDownloading = false;
this.activeDownloads.clear();
this.emit("Stopped");
}
// Método para ajustar configuración basado en advertencias
adjustForNetworkIssues() {
return {
concurry: Math.max(5, Math.floor(this.connectionErrors / 2)),
maxRetries: 10 // Aumentar reintentos si hay problemas
};
}
// Métodos adicionales para obtener información en tiempo real
getDownloadedMB() {
return (this.downloadedBytes / (1024 * 1024)).toFixed(2);
}
getDownloadedGB() {
return (this.downloadedBytes / (1024 * 1024 * 1024)).toFixed(2);
}
getPercentage() {
return this.totalBytes > 0 ? ((this.downloadedBytes / this.totalBytes) * 100).toFixed(1) : "0.0";
}
getCurrentSpeed() {
return this.formatBytes(this.currentSpeed);
}
getETA() {
return Math.ceil(this.eta);
}
isCurrentlyDownloading() {
return this.isDownloading;
}
getConnectionErrorCount() {
return this.connectionErrors;
}
}
export { MinecraftDownloader };