* 下载文件 * @author hmjz100 * @description 发送 GET 请求,一般用于文件下载,支持进度监控、自动重试、断点续传、非断回退 * @param {String} url - 请求地址 * @param {Object} headers - 请求头配置 * @param {Number} [size=0] - 响应类型 * @param {Object} [extra] - 附加参数(必须 `name`、`index`、`size` 属性;可选 `thread`、`retry` 属性) * @return
(url, headers, extra)
| 1238 | * @returns {Promise} 包含响应数据的 `Promise` 对象 |
| 1239 | */ |
| 1240 | async download(url, headers, extra) { |
| 1241 | headers = this.standHeaders(headers); |
| 1242 | // 初始化全局共享状态 |
| 1243 | this.download.active = this.download.active || 0; // 全局活跃线程数 |
| 1244 | this.download.taskCount = this.download.taskCount || 0; // 当前正在运行的 download 任务数 |
| 1245 | var global_maxThreads = 8; // 整个允许的最大并发数 |
| 1246 | |
| 1247 | if (extra) base.console.log(`【LinkSwift】Download\n收到数据:`, extra); |
| 1248 | if (!extra || !extra.index || !extra.name || !extra.size) throw new Error("extra 缺少内容。"); |
| 1249 | |
| 1250 | let status = { |
| 1251 | aborted: false, |
| 1252 | requests: new Set(), |
| 1253 | results: [], |
| 1254 | active: 0, |
| 1255 | maxSpeed: 0 |
| 1256 | }; |
| 1257 | |
| 1258 | let promise = new Promise(async (resolve, reject) => { |
| 1259 | this.download.taskCount++; // 任务进入 |
| 1260 | |
| 1261 | try { |
| 1262 | var finalHead = await base.getFinal(url, headers, false, false).catch(reject); |
| 1263 | if (!finalHead) return; |
| 1264 | url = finalHead.finalUrl; |
| 1265 | |
| 1266 | var responseHeaders = finalHead.responseHeaders; |
| 1267 | let size = parseInt(extra.size || responseHeaders?.["Content-Length"] || 0, 10); |
| 1268 | if (responseHeaders?.["Content-Range"]) { |
| 1269 | size = parseInt((responseHeaders["Content-Range"]?.match(/\/(\d+)$/)?.[1] || size), 10); |
| 1270 | } |
| 1271 | |
| 1272 | if (!status.aborted && typeof extra?.onProgress === "function") extra.onProgress(0, 0, size); |
| 1273 | if (!(finalHead.status >= 200 && finalHead.status < 400)) return reject(finalHead); |
| 1274 | if (finalHead.status == 204 && finalHead.statusText === "IDM") return reject(finalHead); |
| 1275 | |
| 1276 | var supportRange = finalHead.status == 206 && (responseHeaders?.["Accept-Ranges"]?.includes("bytes") || responseHeaders?.["Content-Range"]?.includes("bytes")); |
| 1277 | |
| 1278 | if (!!supportRange || size > 0) { |
| 1279 | base.console.log(`【LinkSwift】Download(Start)\n文件名称:${extra.name}\n断点续传:支持`); |
| 1280 | |
| 1281 | var maxRetry = extra.retry || 10; |
| 1282 | let index = 0; |
| 1283 | let offset = 0; |
| 1284 | let totalLoaded = 0; |
| 1285 | |
| 1286 | var worker = async () => { |
| 1287 | var minChunk = extra.minChunk || 50 * 1024; // 最小 50KB |
| 1288 | var maxChunk = extra.maxChunk || 1 * 1024 * 1024; // 最大 1MB |
| 1289 | let chunk = Math.floor(minChunk + (maxChunk - minChunk) * 0.37); |
| 1290 | |
| 1291 | while (offset < size && !status.aborted) { |
| 1292 | // 如果全局线程满了,且当前任务已经抢到了 1 条以上的线程,则 “让路” 给后来的任务 |
| 1293 | let fairShare = Math.max(1, Math.floor(global_maxThreads / this.download.taskCount)); |
| 1294 | while (!status.aborted && this.download.active >= global_maxThreads && status.active >= fairShare) { |
| 1295 | await new Promise(r => setTimeout(r, 200)); // 等待,直到其他任务释放或有空位 |
| 1296 | } |
| 1297 |
nothing calls this directly
no test coverage detected