Files
Hytale-F2P/backend/utils/fileManager.js
2026-01-19 23:15:29 +01:00

212 lines
6.4 KiB
JavaScript

const fs = require('fs');
const path = require('path');
const axios = require('axios');
async function downloadFile(url, dest, progressCallback, maxRetries = 3) {
let lastError = null;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
console.log(`Download attempt ${attempt + 1}/${maxRetries} for ${url}`);
if (attempt > 0 && progressCallback) {
progressCallback(`Retry ${attempt}/${maxRetries - 1}...`, null, null, null, null);
await new Promise(resolve => setTimeout(resolve, 2000 * attempt)); // Délai progressif
}
const response = await axios({
method: 'GET',
url: url,
responseType: 'stream',
timeout: 60000, // 60 secondes timeout
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': '*/*',
'Accept-Language': 'en-US,en;q=0.9',
'Referer': 'https://launcher.hytale.com/',
'Connection': 'keep-alive'
},
// Configuration Axios pour la robustesse réseau
validateStatus: function (status) {
return status >= 200 && status < 300;
},
// Retry configuration
maxRedirects: 5,
// Network resilience
family: 4 // Force IPv4
});
const totalSize = parseInt(response.headers['content-length'], 10);
let downloaded = 0;
let lastProgressTime = Date.now();
const startTime = Date.now();
// Nettoyer le fichier de destination s'il existe
if (fs.existsSync(dest)) {
fs.unlinkSync(dest);
}
const writer = fs.createWriteStream(dest);
let downloadStalled = false;
let stalledTimeout = null;
response.data.on('data', (chunk) => {
downloaded += chunk.length;
const now = Date.now();
// Reset stalled timer on data received
if (stalledTimeout) {
clearTimeout(stalledTimeout);
}
// Set new stalled timer (30 seconds without data = stalled)
stalledTimeout = setTimeout(() => {
downloadStalled = true;
writer.destroy();
response.data.destroy();
}, 30000);
if (progressCallback && totalSize > 0 && (now - lastProgressTime > 100)) { // Update every 100ms max
const percent = Math.min(100, Math.max(0, (downloaded / totalSize) * 100));
const elapsed = (now - startTime) / 1000;
const speed = elapsed > 0 ? downloaded / elapsed : 0;
progressCallback(null, percent, speed, downloaded, totalSize);
lastProgressTime = now;
}
});
response.data.on('error', (error) => {
if (stalledTimeout) {
clearTimeout(stalledTimeout);
}
console.error(`Stream error on attempt ${attempt + 1}:`, error.code || error.message);
writer.destroy();
});
response.data.pipe(writer);
await new Promise((resolve, reject) => {
writer.on('finish', () => {
if (stalledTimeout) {
clearTimeout(stalledTimeout);
}
if (!downloadStalled) {
console.log(`Download completed successfully on attempt ${attempt + 1}`);
resolve();
} else {
reject(new Error('Download stalled'));
}
});
writer.on('error', (error) => {
if (stalledTimeout) {
clearTimeout(stalledTimeout);
}
reject(error);
});
response.data.on('error', (error) => {
if (stalledTimeout) {
clearTimeout(stalledTimeout);
}
reject(error);
});
});
// Si on arrive ici, le téléchargement a réussi
return;
} catch (error) {
lastError = error;
console.error(`Download attempt ${attempt + 1} failed:`, error.code || error.message);
// Nettoyer le fichier partiel en cas d'erreur
if (fs.existsSync(dest)) {
try {
fs.unlinkSync(dest);
} catch (cleanupError) {
console.warn('Could not cleanup partial file:', cleanupError.message);
}
}
// Vérifier si c'est une erreur réseau que l'on peut retry
const retryableErrors = ['ECONNRESET', 'ENOTFOUND', 'ECONNREFUSED', 'ETIMEDOUT', 'ESOCKETTIMEDOUT', 'EPROTO'];
const isRetryable = retryableErrors.includes(error.code) ||
error.message.includes('timeout') ||
error.message.includes('stalled') ||
(error.response && error.response.status >= 500);
if (!isRetryable || attempt === maxRetries - 1) {
console.error(`Non-retryable error or max retries reached: ${error.code || error.message}`);
break;
}
console.log(`Retryable error detected, will retry in ${2000 * (attempt + 1)}ms...`);
}
}
throw new Error(`Download failed after ${maxRetries} attempts. Last error: ${lastError?.code || lastError?.message || 'Unknown error'}`);
}
function findHomePageUIPath(gameLatest) {
function searchDirectory(dir) {
try {
const items = fs.readdirSync(dir, { withFileTypes: true });
for (const item of items) {
if (item.isFile() && item.name === 'HomePage.ui') {
return path.join(dir, item.name);
} else if (item.isDirectory()) {
const found = searchDirectory(path.join(dir, item.name));
if (found) {
return found;
}
}
}
} catch (error) {
}
return null;
}
if (!fs.existsSync(gameLatest)) {
return null;
}
return searchDirectory(gameLatest);
}
function findLogoPath(gameLatest) {
function searchDirectory(dir) {
try {
const items = fs.readdirSync(dir, { withFileTypes: true });
for (const item of items) {
if (item.isFile() && item.name === 'Logo@2x.png') {
return path.join(dir, item.name);
} else if (item.isDirectory()) {
const found = searchDirectory(path.join(dir, item.name));
if (found) {
return found;
}
}
}
} catch (error) {
}
return null;
}
if (!fs.existsSync(gameLatest)) {
return null;
}
return searchDirectory(gameLatest);
}
module.exports = {
downloadFile,
findHomePageUIPath,
findLogoPath
};