const https = require("https") const fs = require("fs") const path = require("path") const crypto = require("crypto") const { EventEmitter } = require("events") const web = require("./web") const sysinfo = require("./systeminfo") function extractDownloadableFiles(manifest) { const filePaths = Object.keys(manifest.files) const entries = [] for (const filePath of filePaths) { const fileEntry = manifest.files[filePath] if (fileEntry.type !== "file") { continue } const downloadInfo = fileEntry.downloads.raw || fileEntry.downloads.lzma if (!downloadInfo) { continue } entries.push({ filePath: filePath, url: downloadInfo.url, sha1: downloadInfo.sha1, size: downloadInfo.size, executable: fileEntry.executable || false }) } return entries } function requestWithRedirect(url, onResponse, maxRedirects = 5) { return new Promise((resolve, reject) => { function makeRequest(currentUrl, redirectsLeft) { https.get(currentUrl, (response) => { const statusCode = response.statusCode if (statusCode >= 300 && statusCode < 400 && response.headers.location) { response.resume() if (redirectsLeft <= 0) { reject(new Error("Too many http redirection")) return } makeRequest(response.headers.location, redirectsLeft - 1) return } if (statusCode !== 200) { response.resume() reject(new Error(`HTTP Failed: received status code ${statusCode} for ${currentUrl}`)) return } try { onResponse(response) resolve() } catch (err) { reject(err) } }).on("error", (err) => { reject(err) }) } makeRequest(url, maxRedirects) }) } async function downloadSingleFile(fileEntry, outputRoot, onProgress) { const outputPath = path.join(outputRoot, fileEntry.filePath) await fs.promises.mkdir(path.dirname(outputPath), { recursive: true }) const hasher = crypto.createHash("sha1") const writeStream = fs.createWriteStream(outputPath) let downloadedBytes = 0 try { await requestWithRedirect(fileEntry.url, (responseStream) => { responseStream.on("data", (chunk) => { downloadedBytes += chunk.length hasher.update(chunk) if (onProgress) { onProgress({ filePath: fileEntry.filePath, bytesDownloaded: downloadedBytes, totalBytes: fileEntry.size }) } }) responseStream.pipe(writeStream) }) await new Promise((resolve, reject) => { writeStream.on("finish", resolve) writeStream.on("error", reject) }) const computedSha1 = hasher.digest("hex") if (computedSha1 !== fileEntry.sha1) { await fs.promises.unlink(outputPath).catch(() => {}) throw new Error( `Hash SHA1 invalide pour ${fileEntry.filePath}. ` + `Attendu: ${fileEntry.sha1}, Obtenu: ${computedSha1}` ) } if (fileEntry.executable && process.platform !== "win32") { await fs.promises.chmod(outputPath, 0o755).catch(() => {}) } return { filePath: fileEntry.filePath, outputPath: outputPath, sha1: computedSha1, totalBytes: downloadedBytes } } catch (err) { if (!writeStream.destroyed) { writeStream.destroy() } throw err } } async function downloadJavaComponent(manifest, outputRoot, options) { const emitter = new EventEmitter() const concurrency = (options && options.concurrency) || 5 try { const files = extractDownloadableFiles(manifest) const totalFiles = files.length const totalBytesExpected = files.reduce((sum, f) => { return sum + (f.size || 0) }, 0) emitter.emit("start", { totalFiles: totalFiles, totalBytes: totalBytesExpected, outputRoot: outputRoot }) let completedFiles = 0 let failedFiles = 0 let totalBytesDownloaded = 0 const errors = [] const perFileProgress = {} function reportGlobalProgress() { const currentTotal = Object.keys(perFileProgress).reduce((sum, key) => { return sum + perFileProgress[key] }, 0) emitter.emit("progress", { completedFiles: completedFiles, totalFiles: totalFiles, bytesDownloaded: currentTotal, totalBytes: totalBytesExpected, percent: totalBytesExpected > 0 ? Math.round((currentTotal / totalBytesExpected) * 100) : null }) } let cursor = 0 async function worker() { while (cursor < files.length) { const currentIndex = cursor cursor += 1 const fileEntry = files[currentIndex] try { const result = await downloadSingleFile(fileEntry, outputRoot, (progressInfo) => { perFileProgress[progressInfo.filePath] = progressInfo.bytesDownloaded reportGlobalProgress() }) completedFiles += 1 totalBytesDownloaded += result.totalBytes perFileProgress[fileEntry.filePath] = result.totalBytes emitter.emit("fileComplete", result) reportGlobalProgress() } catch (err) { failedFiles += 1 errors.push({ filePath: fileEntry.filePath, message: err.message }) emitter.emit("fileError", { filePath: fileEntry.filePath, message: err.message, error: err }) } } } const workerCount = Math.min(concurrency, files.length) const workers = [] for (let i = 0; i < workerCount; i += 1) { workers.push(worker()) } await Promise.all(workers) emitter.emit("complete", { totalFiles: totalFiles, completedFiles: completedFiles, failedFiles: failedFiles, totalBytes: totalBytesDownloaded, errors: errors }) } catch (err) { emitter.emit("error", { message: err.message, error: err }) } return { events: emitter, path: outputRoot } } module.exports = { downloadJavaComponent }