Podcast-Artikel: Reservierung mit Gerätenamen (claim/release, deviceId pro Browser oder App)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
cedc14f714
commit
d4a47064b2
2 changed files with 21 additions and 7 deletions
|
|
@ -260,6 +260,20 @@ const send = (url, method, body) => fetch(url, {
|
||||||
body: body === undefined ? undefined : JSON.stringify(body),
|
body: body === undefined ? undefined : JSON.stringify(body),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// This device's name for reserving a wish (/article/claim/), kept per browser or app.
|
||||||
|
let memDevice = '';
|
||||||
|
export function deviceId() {
|
||||||
|
try {
|
||||||
|
let id = localStorage.getItem('diora-device-id');
|
||||||
|
if (!id) { id = 'd-' + Math.random().toString(36).slice(2, 12); localStorage.setItem('diora-device-id', id); }
|
||||||
|
return id;
|
||||||
|
} catch (e) {
|
||||||
|
return memDevice || (memDevice = 'd-' + Math.random().toString(36).slice(2, 12));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const claimWish = id => send('/podcasts/episodes/' + id + '/article/claim/', 'POST', {device: deviceId()});
|
||||||
|
export const release = id => send('/podcasts/episodes/' + id + '/article/claim/', 'DELETE', {device: deviceId()});
|
||||||
|
|
||||||
async function getJson(url) {
|
async function getJson(url) {
|
||||||
const r = await fetch(url, {cache: 'no-store'});
|
const r = await fetch(url, {cache: 'no-store'});
|
||||||
return {status: r.status, ok: r.ok, data: r.ok ? await r.json() : null};
|
return {status: r.status, ok: r.ok, data: r.ok ? await r.json() : null};
|
||||||
|
|
@ -330,7 +344,7 @@ export async function pickUp({onState} = {}) {
|
||||||
for (const wish of pending) {
|
for (const wish of pending) {
|
||||||
if (gaveUp.has(wish.episode_id)) { result.skipped++; continue; }
|
if (gaveUp.has(wish.episode_id)) { result.skipped++; continue; }
|
||||||
let claim;
|
let claim;
|
||||||
try { claim = await send('/podcasts/episodes/' + wish.episode_id + '/article/claim/', 'POST', {}); } catch (e) { return result; }
|
try { claim = await claimWish(wish.episode_id); } catch (e) { return result; }
|
||||||
if (!claim.ok) { result.skipped++; continue; } // 409: another device has it
|
if (!claim.ok) { result.skipped++; continue; } // 409: another device has it
|
||||||
try {
|
try {
|
||||||
const saved = await write(wish.episode_id, {onState});
|
const saved = await write(wish.episode_id, {onState});
|
||||||
|
|
@ -339,7 +353,7 @@ export async function pickUp({onState} = {}) {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
result.failed++;
|
result.failed++;
|
||||||
gaveUp.add(wish.episode_id);
|
gaveUp.add(wish.episode_id);
|
||||||
try { await send('/podcasts/episodes/' + wish.episode_id + '/article/claim/', 'DELETE'); } catch (e2) { /* it runs out in 15 minutes */ }
|
try { await release(wish.episode_id); } catch (e2) { /* it runs out in 15 minutes */ }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -359,7 +373,7 @@ export function watch(on) {
|
||||||
export const _reset = () => { running = false; gaveUp.clear(); };
|
export const _reset = () => { running = false; gaveUp.clear(); };
|
||||||
|
|
||||||
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
|
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
|
||||||
window.DioraPodcastArticle = {write, pickUp, watch, notifyDone, markdownToHtml, transcriptToText};
|
window.DioraPodcastArticle = {write, pickUp, watch, notifyDone, claim: claimWish, release, deviceId, markdownToHtml, transcriptToText};
|
||||||
window.dispatchEvent(new Event('diora-podcast-article'));
|
window.dispatchEvent(new Event('diora-podcast-article'));
|
||||||
// At the start of diora.
|
// At the start of diora.
|
||||||
setTimeout(() => { pickUp(); }, 4000);
|
setTimeout(() => { pickUp(); }, 4000);
|
||||||
|
|
|
||||||
|
|
@ -135,10 +135,10 @@ test('pickUp: claim, 409 skipped, failure releases the claim, notifier per succe
|
||||||
});
|
});
|
||||||
const r = await pa.pickUp();
|
const r = await pa.pickUp();
|
||||||
assert.deepEqual(r, {done: 1, failed: 1, skipped: 1});
|
assert.deepEqual(r, {done: 1, failed: 1, skipped: 1});
|
||||||
assert.ok(w.calls.includes('POST /podcasts/episodes/2/article/claim/ {}'));
|
assert.ok(w.calls.some(c => /^POST \/podcasts\/episodes\/2\/article\/claim\/ \{"device":"d-/.test(c)));
|
||||||
assert.ok(!w.calls.some(c => c.startsWith('GET /podcasts/episodes/2/'))); // 409: not written
|
assert.ok(!w.calls.some(c => c.startsWith('GET /podcasts/episodes/2/'))); // 409: not written
|
||||||
assert.ok(w.calls.includes('DELETE /podcasts/episodes/4/article/claim/')); // failed: released
|
assert.ok(w.calls.some(c => c.startsWith('DELETE /podcasts/episodes/4/article/claim/'))); // failed: released
|
||||||
assert.ok(!w.calls.includes('DELETE /podcasts/episodes/1/article/claim/'));
|
assert.ok(!w.calls.some(c => c.startsWith('DELETE /podcasts/episodes/1/article/claim/')));
|
||||||
assert.deepEqual(w.notices, ['Artikel fertig: A']);
|
assert.deepEqual(w.notices, ['Artikel fertig: A']);
|
||||||
// The failed one is not tried again in this visit.
|
// The failed one is not tried again in this visit.
|
||||||
const w2 = stubWorld({pending: [{episode_id: 4, title: 'Vier'}]});
|
const w2 = stubWorld({pending: [{episode_id: 4, title: 'Vier'}]});
|
||||||
|
|
@ -168,5 +168,5 @@ test('pickUp: no key does nothing, not even a request; no second run at the same
|
||||||
const b = await pa.pickUp();
|
const b = await pa.pickUp();
|
||||||
assert.deepEqual(b, {busy: true});
|
assert.deepEqual(b, {busy: true});
|
||||||
assert.equal((await a).done, 1);
|
assert.equal((await a).done, 1);
|
||||||
assert.equal(w2.calls.filter(c => c.startsWith('POST') && c.endsWith('/claim/ {}')).length, 1);
|
assert.equal(w2.calls.filter(c => c.startsWith('POST') && c.includes('/claim/ {"device":')).length, 1);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue