86 lines
5.1 KiB
JavaScript
86 lines
5.1 KiB
JavaScript
'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;
|
||
}
|