Experiment to rebuild Diffuse using web applets.
at main 157 lines 4.7 kB view raw
1import * as URI from "uri-js"; 2 3import type { Consult, ConsultGrouping, GroupConsult, Track } from "@applets/core/types.d.ts"; 4import { SCHEME } from "./constants"; 5import { 6 fetchHandles, 7 fetchHandlesList, 8 groupTracksByHandle, 9 recursiveList, 10 trackHandleId, 11} from "./common"; 12import { provide, transfer } from "@scripts/common"; 13 14//////////////////////////////////////////// 15// TASKS 16//////////////////////////////////////////// 17const actions = { 18 consult, 19 contextualize, 20 groupConsult, 21 list, 22 resolve, 23}; 24 25const { tasks } = provide({ actions, tasks: actions }); 26 27export type Actions = typeof actions; 28export type Tasks = typeof tasks; 29 30// Tasks 31 32export async function consult(fileUriOrScheme: string): Promise<Consult> { 33 if (!self.FileSystemDirectoryHandle) { 34 return { supported: false, reason: "File System Access API is not supported" }; 35 } 36 37 if (!fileUriOrScheme.includes(":")) { 38 if (fileUriOrScheme !== SCHEME) return { supported: false, reason: "Scheme does not match" }; 39 return { supported: true, consult: "undetermined" }; 40 } 41 42 const handles = await fetchHandles(); 43 const uri = URI.parse(fileUriOrScheme); 44 if (uri.scheme !== SCHEME) return { supported: false, reason: "Scheme does not match" }; 45 return { supported: true, consult: uri.host && !!handles[uri.host] ? true : false }; 46} 47 48export async function contextualize(cachedTracks: Track[]) {} 49 50async function groupConsult(tracks: Track[]): Promise<GroupConsult> { 51 const groups = groupTracksByHandle(tracks); 52 const handles = await fetchHandles(); 53 54 const promises = Object.entries(groups).map(async ([handleId, { tracks }]) => { 55 const handle = handles[handleId]; 56 const grouping: ConsultGrouping = handle 57 ? { available: true, tracks } 58 : { available: false, reason: "Handle not available", tracks }; 59 60 return { 61 key: URI.serialize({ scheme: SCHEME, host: handleId }), 62 grouping, 63 }; 64 }); 65 66 const entries = (await Promise.all(promises)).map((entry) => [entry.key, entry.grouping]); 67 const obj = Object.fromEntries(entries); 68 69 return transfer(obj); 70} 71 72export async function list(cachedTracks: Track[] = []) { 73 const handles = await fetchHandlesList(); 74 75 // Recursive listing of all tracks of available handles 76 const processed: Track[][] = await Promise.all( 77 handles.map(({ id, handle }) => { 78 return recursiveList(handle, id, []); 79 }), 80 ); 81 82 // Group tracks by handle id & index by track uri 83 const cache: Record<string, Record<string, Track>> = {}; 84 85 cachedTracks.forEach((track: Track) => { 86 const handleId = trackHandleId(track); 87 if (!handleId) return; 88 89 cache[handleId] ??= {}; 90 cache[handleId][track.uri] = track; 91 }); 92 93 // Replace indexes in groups of which we have the handle. 94 // Keeping around tracks with handles we don't have access to, 95 // and removing tracks that are no longer available (for handles we do have access to). 96 97 // TODO: Refactor to not use `reduce`, for performance. 98 const groups = processed.flat(1).reduce( 99 (acc, track) => { 100 const handleId = trackHandleId(track); 101 if (!handleId) throw new Error("New tracks are missing a handle id!"); 102 103 return { ...acc, [handleId]: { ...acc[handleId], [track.uri]: track } }; 104 }, 105 handles.reduce((acc: Record<string, Record<string, Track>>, handle) => { 106 return { ...acc, [handle.id]: {} }; 107 }, cache), 108 ); 109 110 // Transform in track list and sort by uri 111 const data = Object.values(groups) 112 .map((tracks) => Object.values(tracks)) 113 .flat(1) 114 .sort((a: any, b: any) => { 115 if (a.uri < b.uri) return -1; 116 if (a.uri > b.uri) return 1; 117 return 0; 118 }); 119 120 // Fin 121 return transfer(data); 122} 123 124export async function resolve(args: { uri: string }) { 125 const fileUri = args.uri; 126 127 const uri = URI.parse(fileUri); 128 if (uri.scheme !== SCHEME) return undefined; 129 if (!uri.host || !uri.path) return undefined; 130 131 const handles = await fetchHandles(); 132 const handle = handles[uri.host]; 133 if (!handle) return undefined; 134 135 const path = URI.unescapeComponent(uri.path); 136 const parts = (path.startsWith("/") ? path.slice(1) : path).split("/"); 137 const filename = parts[parts.length - 1]; 138 139 const dirHandle = await parts 140 .slice(0, -1) 141 .reduce( 142 async ( 143 acc: Promise<FileSystemDirectoryHandle>, 144 part: string, 145 ): Promise<FileSystemDirectoryHandle> => { 146 const h = await acc; 147 return await h.getDirectoryHandle(part); 148 }, 149 Promise.resolve(handle), 150 ); 151 152 const fileHandle = await dirHandle.getFileHandle(filename); 153 const file = await fileHandle.getFile(); 154 const url = URL.createObjectURL(file); 155 156 return { expiresAt: Infinity, url }; 157}