import { withKey, GlobalStateContext, PiletEntries, PiletMetadata, PiletEntry } from 'piral-core'; import { PiletUpdateMode } from './types'; interface HashEntry { name: string; version: string; } function getPiletHash(pilet: PiletMetadata): HashEntry { return { name: pilet.name, version: pilet.version, }; } function sortPilets(a: HashEntry, b: HashEntry) { return a.name.localeCompare(b.name); } function computePiletHash(pilets: PiletEntries) { return JSON.stringify(pilets.map(getPiletHash).sort(sortPilets)); } function reset(ctx: GlobalStateContext) { ctx.dispatch((state) => ({ ...state, updatability: { ...state.updatability, added: [], updated: [], removed: [], active: false, }, })); } async function apply(ctx: GlobalStateContext) { const { added, removed, updated } = ctx.readState((s) => s.updatability); for (const pilet of removed) { await ctx.removePilet(pilet.name); } for (const pilet of updated) { await ctx.removePilet(pilet.name); await ctx.addPilet(pilet); } for (const pilet of added) { await ctx.addPilet(pilet); } } export function rejectUpdate(ctx: GlobalStateContext) { reset(ctx); } export function approveUpdate(ctx: GlobalStateContext) { apply(ctx); reset(ctx); } export function checkForUpdates(ctx: GlobalStateContext, pilets: PiletEntries) { const currentHash = computePiletHash(pilets); const currentPilets = ctx.readState((s) => s.modules); const previousHash = ctx.readState((s) => s.updatability.lastHash || computePiletHash(currentPilets)); if (currentHash !== previousHash) { const currentModes = ctx.readState((s) => s.registry.updatability); const currentPiletNames = currentPilets.map((m) => m.name); const isPending = (pilet: PiletEntry) => currentModes[pilet.name]?.mode === 'ask'; const isNotBlocked = (pilet: PiletEntry) => currentModes[pilet.name]?.mode !== 'block'; const added = pilets.filter((m) => !currentPiletNames.includes(m.name)); const removed = currentPilets.filter((m) => !pilets.some((p) => p.name === m.name) && isNotBlocked(m)); const updated = pilets.filter((pilet) => { if ('version' in pilet && isNotBlocked(pilet)) { const version = currentPilets.find((m) => m.name === pilet.name)?.version; return !!version && version !== pilet.version; } return false; }); ctx.dispatch((state) => { const anyPendingDecision = [...removed, ...updated].some(isPending); // no need to ask for approval if (!anyPendingDecision) { // automatically start the update in the next cycle setTimeout(ctx.approveUpdate, 0); } return { ...state, updatability: { active: anyPendingDecision, lastHash: currentHash, added, removed, updated, }, }; }); } } export function setUpdateMode(ctx: GlobalStateContext, piletName: string, mode: PiletUpdateMode) { ctx.dispatch((state) => ({ ...state, registry: { ...state.registry, updatability: withKey(state.registry.updatability, piletName, { mode }), }, })); }