refactor: split downloader into modules
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
const fs = require('fs');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
|
||||
function getProtocol(url) {
|
||||
return url.startsWith('https') ? https : http;
|
||||
}
|
||||
|
||||
function fetchJSON(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getProtocol(url).get(url, (res) => {
|
||||
let data = '';
|
||||
res.on('data', chunk => data += chunk);
|
||||
res.on('end', () => {
|
||||
try { resolve(JSON.parse(data)); }
|
||||
catch { reject(new Error(`JSON 解析失败: ${data.slice(0, 200)}`)); }
|
||||
});
|
||||
}).on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
function downloadFile(url, dest, showProgress = true) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getProtocol(url).get(url, (res) => {
|
||||
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
||||
return downloadFile(res.headers.location, dest, showProgress).then(resolve).catch(reject);
|
||||
}
|
||||
if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode}`));
|
||||
|
||||
const total = parseInt(res.headers['content-length'], 10);
|
||||
let downloaded = 0;
|
||||
let lastTime = Date.now();
|
||||
let lastBytes = 0;
|
||||
const file = fs.createWriteStream(dest);
|
||||
|
||||
res.on('data', (chunk) => {
|
||||
downloaded += chunk.length;
|
||||
if (!showProgress) return;
|
||||
const now = Date.now();
|
||||
const elapsed = (now - lastTime) / 1000;
|
||||
if (elapsed < 0.3) return;
|
||||
|
||||
const speed = (downloaded - lastBytes) / elapsed;
|
||||
lastTime = now;
|
||||
lastBytes = downloaded;
|
||||
const speedStr = speed >= 1048576
|
||||
? `${(speed / 1048576).toFixed(1)} MB/s`
|
||||
: `${(speed / 1024).toFixed(0)} KB/s`;
|
||||
|
||||
if (total) {
|
||||
const pct = downloaded / total;
|
||||
const barWidth = 30;
|
||||
const filled = Math.round(pct * barWidth);
|
||||
const bar = '█'.repeat(filled) + '░'.repeat(barWidth - filled);
|
||||
const eta = speed > 0 ? Math.ceil((total - downloaded) / speed) : 0;
|
||||
const etaStr = eta > 60 ? `${Math.floor(eta / 60)}m${eta % 60}s` : `${eta}s`;
|
||||
process.stdout.write(`\r ⬇️ [${bar}] ${(pct * 100).toFixed(1)}% ${(downloaded / 1048576).toFixed(1)}/${(total / 1048576).toFixed(1)} MB ${speedStr} ETA ${etaStr} `);
|
||||
} else {
|
||||
process.stdout.write(`\r ⬇️ ${(downloaded / 1048576).toFixed(1)} MB ${speedStr} `);
|
||||
}
|
||||
});
|
||||
|
||||
res.pipe(file);
|
||||
file.on('finish', () => { file.close(); if (showProgress) console.log(); resolve(); });
|
||||
file.on('error', (err) => { fs.unlink(dest, () => {}); reject(err); });
|
||||
}).on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
function downloadBuffer(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getProtocol(url).get(url, (res) => {
|
||||
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
||||
return downloadBuffer(res.headers.location).then(resolve).catch(reject);
|
||||
}
|
||||
if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode}`));
|
||||
const chunks = [];
|
||||
res.on('data', chunk => chunks.push(chunk));
|
||||
res.on('end', () => resolve(Buffer.concat(chunks)));
|
||||
}).on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { fetchJSON, downloadFile, downloadBuffer };
|
||||
Reference in New Issue
Block a user