Experiment to rebuild Diffuse using web applets.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: only save tracks when changed

+33 -21
+8 -10
src/pages/orchestrator/input-cache/_applet.astro
··· 1 1 <script> 2 2 import type { ManagedOutput, ResolvedUri, Track } from "@applets/core/types.d.ts"; 3 3 import { applet, register, wait } from "@scripts/applet/common"; 4 + import { tracksCacheId } from "@scripts/output/common"; 4 5 5 6 //////////////////////////////////////////// 6 7 // SETUP ··· 23 24 }; 24 25 25 26 // Start processing once settled and tracks are loaded 26 - // context 27 - // .settled() 28 - // .then(() => configurator.output) 29 - // .then((output) => wait(output, (d) => d?.tracks.state === "loaded")) 30 - // .then(() => (context.isMainInstance() ? process() : undefined)); 27 + context 28 + .settled() 29 + .then(() => configurator.output) 30 + .then((output) => wait(output, (d) => d?.tracks.state === "loaded")) 31 + .then(() => (context.isMainInstance() ? process() : undefined)); 31 32 32 33 //////////////////////////////////////////// 33 34 // ACTIONS ··· 53 54 }); 54 55 55 56 // Process 56 - let changed = true; // TODO 57 - 58 57 const tracksWithMetadata = await tracks.reduce( 59 58 async (promise: Promise<Track[]>, track: Track) => { 60 59 const acc = await promise; ··· 88 87 }, 89 88 ); 90 89 91 - console.log(stats, tags); 92 - changed = true; 93 - 94 90 return [...acc, { ...track, stats, tags }]; 95 91 }, 96 92 Promise.resolve([]), 97 93 ); 98 94 99 95 // Save 96 + const changed = tracksCacheId(tracksWithMetadata) !== output.data.tracks.cacheId; 97 + 100 98 if (changed) 101 99 await output.sendAction("tracks", tracksWithMetadata, { 102 100 timeoutDuration: 60000 * 5,
+2 -2
src/pages/output/storacha-automerge/_applet.astro
··· 5 5 6 6 import type { ManagedOutput, Track } from "@applets/core/types.d.ts"; 7 7 import { register } from "@scripts/applet/common"; 8 - import { INITIAL_MANAGED_OUTPUT } from "@scripts/output/common"; 8 + import { INITIAL_MANAGED_OUTPUT, tracksCacheId } from "@scripts/output/common"; 9 9 import { cleanUndefinedValuesForTracks } from "@scripts/common"; 10 10 11 11 //////////////////////////////////////////// ··· 61 61 tracks: { 62 62 ...context.data.tracks, 63 63 ...trackDocument, 64 - cacheId: crypto.randomUUID(), 64 + cacheId: tracksCacheId(context.data.tracks.collection), 65 65 state: "loaded", 66 66 }, 67 67 };
+12 -6
src/scripts/input/opensubsonic/worker.ts
··· 1 1 import { SubsonicAPI, type Child } from "subsonic-api"; 2 2 import * as URI from "uri-js"; 3 - import QS from "query-string"; 4 3 5 4 import type { Consult, ConsultGrouping, GroupConsult, Track } from "@applets/core/types.d.ts"; 6 5 import { SCHEME } from "./constants.ts"; ··· 72 71 const parsed = parseURI(t.uri); 73 72 if (!parsed || !parsed.path) return acc; 74 73 75 - const bid = serverId(parsed?.server); 76 - const trk = { [parsed.path]: t }; 74 + const sid = serverId(parsed?.server); 75 + const trk = { [URI.unescapeComponent(parsed.path)]: t }; 77 76 78 - return { ...acc, [bid]: acc[bid] ? { ...acc[bid], ...trk } : trk }; 77 + return { ...acc, [sid]: acc[sid] ? { ...acc[sid], ...trk } : trk }; 79 78 }, {}); 80 79 81 80 async function search(client: SubsonicAPI, offset = 0): Promise<Child[]> { ··· 100 99 const servers = await loadServers(); 101 100 const promises = Object.values(servers).map(async (server) => { 102 101 const client = createClient(server); 102 + const sid = serverId(server); 103 103 const list = await search(client, 0); 104 104 105 105 return list 106 106 .filter((song) => !song.isVideo) 107 107 .map((song) => { 108 - if (song.path && cache[song.path]) return cache[song.path]; 108 + const path = song.path 109 + ? song.path.startsWith("/") 110 + ? song.path 111 + : `/${song.path}` 112 + : undefined; 113 + const fromCache = path ? cache[sid]?.[path] : undefined; 114 + if (fromCache) return fromCache; 109 115 110 116 const track: Track = { 111 117 id: crypto.randomUUID(), 112 118 kind: autoTypeToTrackKind(song.type), 113 - uri: buildURI(server, { songId: song.id, path: song.path }), 119 + uri: buildURI(server, { songId: song.id, path }), 114 120 115 121 stats: { 116 122 bitrate: song.bitRate,
+11 -3
src/scripts/output/common.ts
··· 1 + import { xxh32r } from "xxh32/dist/raw.js"; 2 + 1 3 import type { ManagedOutput, Track } from "@applets/core/types"; 2 4 import type { BroadcastedApplet } from "@scripts/applet/common"; 5 + import { jsonEncode } from "@scripts/common"; 3 6 4 7 export const INITIAL_MANAGED_OUTPUT: ManagedOutput = { 5 8 tracks: { 6 - cacheId: crypto.randomUUID(), 9 + cacheId: tracksCacheId([]), 7 10 state: "loading", 8 11 collection: [], 9 12 }, ··· 32 35 context.data = { 33 36 ...context.data, 34 37 tracks: { 35 - cacheId: crypto.randomUUID(), 38 + cacheId: tracksCacheId(collection), 36 39 state: "loaded", 37 40 collection, 38 41 }, ··· 49 52 context.data = { 50 53 ...context.data, 51 54 tracks: { 52 - cacheId: crypto.randomUUID(), 55 + cacheId: tracksCacheId(tracks), 53 56 state: "loaded", 54 57 collection: tracks, 55 58 }, ··· 67 70 tracks, 68 71 }; 69 72 } 73 + 74 + export function tracksCacheId(tracks: Track[]): string { 75 + // TODO: Probably should work with encoded tracks directly? 76 + return xxh32r(jsonEncode(tracks)).toString(); 77 + }