display cache usage and warn if not enough disk space available
This commit is contained in:
@@ -17,6 +17,15 @@ const CHUNK_SIZE = 4 * 1024 * 1024;
|
||||
// as the intent is to catch socket breakage during cellular network changes for example
|
||||
const CHUNK_TIMEOUT = 60_000;
|
||||
|
||||
class QuotaError extends Error {
|
||||
constructor(readonly missing: number) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
// total bytes used by our OPFS origin
|
||||
export const usage = $state<{ bytes?: number }>({});
|
||||
|
||||
export const cached = new SvelteSet<string>(readStored<string[]>(STORAGE_KEY, []));
|
||||
export const downloading = new SvelteMap<string, number>();
|
||||
|
||||
@@ -26,6 +35,7 @@ export const partials = new SvelteMap<string, PartialDownload>(
|
||||
);
|
||||
|
||||
if (browser) {
|
||||
refreshUsage();
|
||||
$effect.root(() => {
|
||||
$effect(() => {
|
||||
writeStored(STORAGE_KEY, [...cached]);
|
||||
@@ -52,6 +62,12 @@ export async function download(stream: StreamSummary) {
|
||||
cached.add(stream.id);
|
||||
return;
|
||||
} catch (err) {
|
||||
if (err instanceof QuotaError) {
|
||||
alert(
|
||||
`not enough disk space ;_;, need ${Math.ceil(err.missing / 1_000_000)} MB more.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
console.error(`[streamCache] ${stream.id} attempt failed`, err);
|
||||
// reset attempts if download of at least one chunk successful
|
||||
attempt = (partials.get(stream.id)?.received ?? 0) > before ? 0 : attempt + 1;
|
||||
@@ -84,16 +100,33 @@ export async function getUrl(streamId: string): Promise<string | null> {
|
||||
|
||||
async function attemptDownload(stream: StreamSummary) {
|
||||
let state = partials.get(stream.id);
|
||||
let checkedSpace = false;
|
||||
while (!state || state.received < state.total) {
|
||||
state = await fetchChunk(stream, state);
|
||||
partials.set(stream.id, state);
|
||||
downloading.set(stream.id, percent(state.received, state.total));
|
||||
|
||||
// first chunk's HTTP response carries the total stream size
|
||||
if (!checkedSpace) {
|
||||
await checkSpace(stream.id, state);
|
||||
checkedSpace = true;
|
||||
}
|
||||
}
|
||||
console.log(`[streamCache] ${stream.id} assembling ${state.total} bytes`);
|
||||
await assemble(stream.id, state.total);
|
||||
console.log(`[streamCache] ${stream.id} assembled`);
|
||||
}
|
||||
|
||||
/** check available space below what's needed to download the stream */
|
||||
async function checkSpace(streamId: string, state: PartialDownload) {
|
||||
const { usage: used, quota } = await navigator.storage.estimate();
|
||||
if (used === undefined || quota === undefined) return;
|
||||
|
||||
// extra CHUNK_SIZE is due to the delta in `assemble()` merging one chunk in (+1) then deleting it
|
||||
const peak = used + (state.total - state.received) + CHUNK_SIZE;
|
||||
if (peak > quota) throw new QuotaError(peak - quota);
|
||||
}
|
||||
|
||||
/** fetch chunk starting from the last valid one, store in OPFS, and return updated state */
|
||||
async function fetchChunk(stream: StreamSummary, state?: PartialDownload) {
|
||||
const start = state?.received ?? 0;
|
||||
@@ -194,6 +227,12 @@ async function sweep(force?: string) {
|
||||
orphans.push(name);
|
||||
}
|
||||
for (const name of orphans) await parts.removeEntry(name, { recursive: true }).catch(() => {});
|
||||
|
||||
await refreshUsage();
|
||||
}
|
||||
|
||||
async function refreshUsage() {
|
||||
usage.bytes = (await navigator.storage.estimate()).usage;
|
||||
}
|
||||
|
||||
/** exponential backoff, cut short if the connection comes back before it elapses */
|
||||
|
||||
Reference in New Issue
Block a user