Files
Bangumi-Vault/app/js/covers.js
T
Stardream e3bf77296c
Build and Push Docker Image / build (push) Successful in 17s
Initial release
2026-06-16 20:27:36 +10:00

86 lines
5.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
async function cacheCoversAuto(){
const portable = await localApiAvailable();
const todo = getAllCollections().filter(c => c.status !== 'confirmed_deleted' && !c.cover_local_url);
if(!todo.length){ log('syncLog','封面自动缓存:全部已缓存,跳过。'); return; }
log('syncLog', `封面自动缓存:开始缓存 ${todo.length} 个未缓存封面${portable ? '到 Docker 文件卷' : '到浏览器 IndexedDB'}...`);
let ok=0, fail=0;
for(let i=0; i<todo.length; i++){
const c=todo[i]; const id=String(c.subject_id);
const url=coverUrl({...c, cover_local_url:'', cover_data_url:''}); if(!url){ fail++; continue; }
try{
if(portable){
const js=await localPostJSON('/api/cache-cover', {subject_id:id, url});
if(!js?.ok || !js.url) throw new Error(js?.error||'no url');
const row=state.collections[id];
if(row){ row.cover_local_url=js.url; row.cover_local_file=js.file||''; row.cover_cached_at=nowISO(); }
} else {
const res=await fetch(url,{mode:'cors',referrerPolicy:'no-referrer'}); if(!res.ok) throw new Error(res.statusText);
const blob=await res.blob(); const dataUrl=await blobToDataURL(blob); await idbSet('img:'+id,{dataUrl,updated_at:nowISO(),source:url});
}
ok++; if((i+1)%20===0){ log('syncLog',`封面自动缓存 ${i+1}/${todo.length}`); await saveState(); }
await sleep(portable ? 35 : 80);
}catch(e){ fail++; if(fail<=5) log('syncLog',`封面失败 ${id}${e.message||e}`); }
}
await saveState();
log('syncLog', `封面自动缓存完成:成功 ${ok},失败 ${fail}`);
}
async function cacheCoversCurrent(){
const arr = visibleCollections(); if(!arr.length){ toast('没有可缓存的封面'); return; }
const portable = await localApiAvailable();
openModal('syncModal'); $('syncLog').textContent='';
log('syncLog', portable ? `开始缓存当前筛选下 ${arr.length} 个封面到 Docker 文件卷...` : `未检测到 Web 服务,临时缓存到浏览器 IndexedDB:${arr.length} 个封面...`);
setProgress(0);
let ok=0, fail=0, i=0;
for(const c of arr){
i++; setProgress(i/arr.length*100); const id=String(c.subject_id);
if(c.cover_local_url || (!portable && await getCachedImage(id))) { ok++; continue; }
const url=coverUrl({...c, cover_local_url:'', cover_data_url:''}); if(!url){ fail++; continue; }
try{
if(portable){
const js = await localPostJSON('/api/cache-cover', {subject_id:id, url, title:c.title||''});
if(!js?.ok || !js.url) throw new Error(js?.error || '本地服务没有返回图片路径');
const row = state.collections[id];
if(row){ row.cover_local_url = js.url; row.cover_local_file = js.file || ''; row.cover_cached_at = nowISO(); }
} else {
const res=await fetch(url, {mode:'cors', referrerPolicy:'no-referrer'}); if(!res.ok) throw new Error(res.statusText); const blob=await res.blob(); const dataUrl=await blobToDataURL(blob); await idbSet('img:'+id, {dataUrl, updated_at:nowISO(), source:url});
}
ok++; if(i%10===0){ log('syncLog',`已处理 ${i}/${arr.length}`); await saveState(); }
await sleep(portable ? 35 : 80);
}catch(e){ fail++; if(fail <= 5) log('syncLog',`封面失败 ${id}${e.message || e}`); }
}
await saveState();
log('syncLog',`封面缓存完成:成功 ${ok},失败 ${fail}`); toast(`封面缓存完成:成功 ${ok},失败 ${fail}`); render();
}
function blobToDataURL(blob){ return new Promise((resolve,reject)=>{ const r=new FileReader(); r.onload=()=>resolve(r.result); r.onerror=()=>reject(r.error); r.readAsDataURL(blob); }); }
async function imageBlobToDataURLFromCache(c){
const offlineData = c.cover_data_url || '';
const localUrl = c.cover_local_url || '';
const remoteUrl = onlineCoverUrl(c);
// v0.20: 导出用的 base64 只在内存中的离线副本里生成,不写回 收藏数据.json。
// Web 运行界面优先使用服务端缓存封面或在线原图。
if(localUrl){
try{ const res = await fetch(localUrl, {cache:'no-store'}); if(res.ok) return await blobToDataURL(await res.blob()); }catch{}
}
try{ const cached = await idbGet('img:'+c.subject_id); if(cached?.dataUrl) return cached.dataUrl; }catch{}
if(offlineData) return offlineData;
// 没有本地缓存时,不主动在线拉远端图片进 HTML,避免导出完整 ZIP 时大量跨域下载卡住。
// 需要离线封面的条目,请先点“缓存当前封面到目录”。
return '';
}
async function collectCachedImageFiles(arr,onProgress){
const files = {};
let i = 0;
for(const c of arr){
i++;
if(onProgress) onProgress(i, arr.length, c);
if(!c.cover_local_url) { if(i % 40 === 0) await sleep(0); continue; }
const file = (c.cover_local_file || '').split(/[\\/]/).pop() || (`${c.subject_id}.jpg`);
try{ const res = await fetch(c.cover_local_url, {cache:'no-store'}); if(!res.ok) continue; files['images/' + file] = new Uint8Array(await res.arrayBuffer()); }
catch{}
if(i % 30 === 0) await sleep(0);
}
return files;
}