Keep artwork on disk and serve every image from iPX (#111)
The page loaded artwork from each publisher's server, or through /api/art, fetched every time, for http-only hosts. Nothing was kept, every visit asked every publisher, and artwork went when a publisher's server did. - The page draws every image a feed or item names from /api/art. The first time, iPX fetches it (only an address a feed or item names, only an image, up to 5 MB, within the feed timeout) and keeps it in art/ beside the database, under a hash of its address with its type beside it (src/art.rs). Later it comes from disk, which marks it as used. - art_cache_mb, a server setting on the admin page, 500 by default, caps what is kept: the sweep before each scan drops the least recently shown until it fits. 0 keeps nothing, and artwork is fetched through iPX each time. Settings saved before it get the default. - Served from iPX's own address, someone else's image must stay an image: nosniff, and a CSP with sandbox, so an SVG opened on its own runs no script as iPX. - The fixture server sends .jpg as image/jpeg, which /api/art requires. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -49,6 +49,10 @@ async function drawServer(){
|
||||
items are never touched.</span></div>
|
||||
<div class="field"><label>Delete items older than (days, 0 = keep)</label>
|
||||
<input type="number" id="gage" min="0" value="${g.max_age_days}"></div>
|
||||
<div class="field"><label>Artwork kept (MB, 0 = none)</label>
|
||||
<input type="number" id="gart" min="0" value="${g.art_cache_mb}">
|
||||
<span class="hint">Show and episode artwork is kept here once shown, so it loads from iPX
|
||||
instead of each publisher. Over this, what has gone longest unshown is dropped first.</span></div>
|
||||
<div class="field"><label>Download folder</label>
|
||||
<span class="hint" style="overflow-wrap:anywhere">${esc(g.download_dir)}</span></div>
|
||||
<div class="cardacts"><button class="btn primary" id="gsave">${ICON.check} Save</button></div>`;
|
||||
@@ -59,7 +63,8 @@ async function drawServer(){
|
||||
max_new_per_check: Math.max(0, Number($('#gmax').value) || 0),
|
||||
media_types: $('#gtypes').value.split(',').map(t => t.trim()).filter(Boolean),
|
||||
max_total_gb: Number($('#gquota').value) || 0,
|
||||
max_age_days: Number($('#gage').value) || 0})});
|
||||
max_age_days: Number($('#gage').value) || 0,
|
||||
art_cache_mb: Math.max(0, Number($('#gart').value) || 0)})});
|
||||
toast('Settings saved');
|
||||
}catch(e){ toast(e.message, true); }
|
||||
};
|
||||
|
||||
@@ -46,7 +46,7 @@ function mediaSession(e,f){
|
||||
if(!('mediaSession' in navigator)) return;
|
||||
navigator.mediaSession.metadata=new MediaMetadata({
|
||||
title:e.title||'', artist:f?(f.title||f.id):'', album:f?(f.title||''):'',
|
||||
artwork:(e.image||(f&&f.image))?[{src:e.image||f.image,sizes:'512x512'}]:[],
|
||||
artwork:(e.image||(f&&f.image))?[{src:artSrc(e.image||f.image),sizes:'512x512'}]:[],
|
||||
});
|
||||
const h={play:()=>audio.play(),pause:()=>audio.pause(),
|
||||
seekbackward:()=>audio.currentTime-=15,seekforward:()=>audio.currentTime+=30};
|
||||
|
||||
@@ -145,10 +145,11 @@ const tint=name=>{ let h=0; for(const c of name||'?') h=(h*31+c.charCodeAt(0))>>
|
||||
function tileHTML(name,cls){
|
||||
return `<div class="art ini ${cls||''}" style="--tint:${tint(name)}">${esc(initials(name))}</div>`;
|
||||
}
|
||||
/// On the https page, the browser upgrades an http:// image to https, and a host that has no
|
||||
/// https shows nothing (issue #90); ipx fetches those itself.
|
||||
/// Every image a feed names comes from iPX, which keeps a copy: fast after the first time, and
|
||||
/// no publisher's server asked on every visit. It began with http-only artwork, which the https
|
||||
/// page could not load (#90).
|
||||
function artSrc(url: string){
|
||||
return location.protocol==='https:'&&/^http:\/\//i.test(url) ? '/api/art?u='+encodeURIComponent(url) : url;
|
||||
return /^https?:\/\//i.test(url) ? '/api/art?u='+encodeURIComponent(url) : url;
|
||||
}
|
||||
function artHTML(url: string | null, name: string, cls?: string){
|
||||
return url
|
||||
|
||||
Reference in New Issue
Block a user