88 lines
2.9 KiB
TypeScript
88 lines
2.9 KiB
TypeScript
export default defineBackground(() => {
|
||
console.log('Hello background!', { id: browser.runtime.id });
|
||
|
||
// 监听下载请求
|
||
browser.runtime.onMessage.addListener(async (msg) => {
|
||
if (msg.type === 'DOWNLOAD_PINS') {
|
||
const pins: PinData[] = msg.pins;
|
||
const concurrency: number = msg.concurrency || 3;
|
||
const result = await startBatchDownload(pins, concurrency);
|
||
// 通知 popup 下载完成
|
||
browser.runtime.sendMessage({
|
||
type: 'DOWNLOAD_DONE',
|
||
success: result.success,
|
||
failed: result.failed,
|
||
total: result.total,
|
||
});
|
||
}
|
||
});
|
||
});
|
||
|
||
// 定义单个下载任务 (封装成 Promise)
|
||
async function downloadOne(pin: PinData): Promise<void> {
|
||
return new Promise(async (resolve) => {
|
||
// 处理文件名非法字符(清理文件名)
|
||
const safeName = (pin.title || 'image').replace(/[\n\r\t]/g, ' ').replace(/[<>:"/\\|?*]/g, '').replace(/\s+/g, ' ').trim().slice(0, 100);
|
||
const filename = `Pins/${safeName}.jpg`;
|
||
|
||
browser.downloads.download({
|
||
url: pin.imgSrc,
|
||
filename: filename,
|
||
headers: [{ name: 'Referer', value: 'https://huaban.com/' }],
|
||
conflictAction: 'uniquify'
|
||
}, (id) => {
|
||
if (!id) {
|
||
console.warn(`跳过: ${pin.title} (可能 URL 无效)`);
|
||
return resolve(); // 即使失败也要 resolve,否则会阻塞队列
|
||
}
|
||
|
||
// 监听下载状态,只有当下载真正完成(或中断)时才释放资源
|
||
const listener = (delta: Browser.downloads.DownloadDelta) => {
|
||
if (delta.id !== id) return;
|
||
|
||
if (delta.state && delta.state.current === 'complete') {
|
||
browser.downloads.onChanged.removeListener(listener);
|
||
resolve();
|
||
}
|
||
// 处理中断或取消的情况,防止队列卡死
|
||
else if (delta.state && delta.state.current === 'interrupted') {
|
||
browser.downloads.onChanged.removeListener(listener);
|
||
console.warn(`下载中断: ${pin.title}`, JSON.stringify(delta));
|
||
resolve();
|
||
}
|
||
};
|
||
browser.downloads.onChanged.addListener(listener);
|
||
});
|
||
});
|
||
}
|
||
|
||
// 并发调度器
|
||
export async function startBatchDownload(pins: PinData[], limit: number = 3) {
|
||
let success = 0;
|
||
let failed = 0;
|
||
|
||
const executing: Promise<void>[] = []; // 正在运行的任务池
|
||
|
||
for (const pin of pins) {
|
||
// 创建任务
|
||
const task = downloadOne(pin);
|
||
|
||
// 加入任务池
|
||
const wrapper = task.finally(() => {
|
||
const idx = executing.indexOf(wrapper);
|
||
if (idx !== -1) executing.splice(idx, 1);
|
||
});
|
||
executing.push(wrapper);
|
||
|
||
// 关键逻辑:如果池子满了(达到 limit),等待任意一个任务完成
|
||
if (executing.length >= limit) {
|
||
await Promise.race(executing);
|
||
}
|
||
}
|
||
|
||
// 等待剩余任务全部完成
|
||
await Promise.all(executing);
|
||
console.log("所有下载任务已处理完毕");
|
||
|
||
return { success, failed, total: pins.length };
|
||
} |