Line data Source code
1 : import { exists } from '../../platform/electron_fs.js';
2 : import { type MigrationFolder, NativeFolder, OPFSFolder } from './migration_folder.js';
3 :
4 : /// The manifest of the storage root, it holds the storage version
5 1 : export const MANIFEST_FILE = 'dashql-manifest.json';
6 :
7 : /// A manifest as it is stored, of any storage version
8 : export type ManifestJson = Record<string, unknown>;
9 :
10 : /// A session folder that a manifest registers
11 : export interface RegisteredSessionFolder {
12 : /// The session id, the path of the manifest entry
13 : sessionId: string;
14 : /// The top-level files of the session
15 : folder: MigrationFolder;
16 : }
17 :
18 : /// Read the manifest, null if the storage has none
19 1 : export async function readManifestJson(root: FileSystemDirectoryHandle): Promise<ManifestJson | null> {
20 1 : let text: string;
21 1 : try {
22 1 : text = await (await (await root.getFileHandle(MANIFEST_FILE, { create: false })).getFile()).text();
23 1 : } catch (error) {
24 1 : if ((error as DOMException).name === 'NotFoundError') {
25 1 : return null;
26 : }
27 1 : throw error;
28 1 : }
29 1 : const manifest: unknown = JSON.parse(text);
30 1 : if (manifest == null || typeof manifest !== 'object' || Array.isArray(manifest)) {
31 1 : throw new Error(`Invalid ${MANIFEST_FILE}: not a JSON object`);
32 : }
33 1 : return manifest as ManifestJson;
34 1 : }
35 :
36 : /// Write the manifest
37 1 : export async function writeManifestJson(root: FileSystemDirectoryHandle, manifest: ManifestJson): Promise<void> {
38 1 : const writable = await (await root.getFileHandle(MANIFEST_FILE, { create: true })).createWritable();
39 1 : await writable.write(JSON.stringify(manifest, null, 2));
40 1 : await writable.close();
41 1 : }
42 :
43 : /// The session entries of a manifest of storage version 2 to 6, later versions register the sessions
44 : /// in the session tree
45 1 : export function manifestSessionEntries(manifest: ManifestJson): unknown[] {
46 1 : return Array.isArray(manifest.sessions) ? manifest.sessions as unknown[] : [];
47 1 : }
48 :
49 : /// The session folders of session entries that can be reached: OPFS sessions live in `sessions/<uuid>`,
50 : /// native sessions in their own directory, which may be missing or unavailable outside of the native app.
51 1 : export async function listSessionFolders(root: FileSystemDirectoryHandle, entries: readonly unknown[]): Promise<RegisteredSessionFolder[]> {
52 1 : const folders: RegisteredSessionFolder[] = [];
53 1 : for (const entry of entries) {
54 1 : const { path, storageType, nativePath } = (entry ?? {}) as { path?: unknown; storageType?: unknown; nativePath?: unknown };
55 1 : if (typeof path !== 'string' || path.length === 0) {
56 1 : continue;
57 : }
58 1 : if (storageType === 'native') {
59 1 : if (typeof nativePath !== 'string' || globalThis.dashqlElectron === undefined) {
60 1 : continue;
61 : }
62 : // Folders registered before the app granted folders are claimed once, later starts reject the claim
63 1 : await globalThis.dashqlElectron.claimSessionFolder(nativePath);
64 1 : if (!(await exists(nativePath))) {
65 1 : continue;
66 : }
67 1 : folders.push({ sessionId: path, folder: new NativeFolder(nativePath) });
68 1 : continue;
69 1 : }
70 1 : try {
71 1 : const dir = await (await root.getDirectoryHandle('sessions', { create: false })).getDirectoryHandle(path, { create: false });
72 1 : folders.push({ sessionId: path, folder: new OPFSFolder(dir) });
73 1 : } catch (error) {
74 1 : if ((error as DOMException).name !== 'NotFoundError') {
75 1 : throw error;
76 : }
77 1 : }
78 1 : }
79 1 : return folders;
80 1 : }
|