diff --git a/src/hooks.client.ts b/src/hooks.client.ts index d947c8e..ec6f620 100644 --- a/src/hooks.client.ts +++ b/src/hooks.client.ts @@ -1,5 +1,5 @@ import type { ClientInit } from "@sveltejs/kit"; -import { getClientKey, getMasterKeys, getHmacSecrets } from "$lib/indexedDB"; +import { cleanupDanglingInfos, getClientKey, getMasterKeys, getHmacSecrets } from "$lib/indexedDB"; import { prepareFileCache } from "$lib/modules/file"; import { prepareOpfs } from "$lib/modules/opfs"; import { clientKeyStore, masterKeyStore, hmacSecretStore } from "$lib/stores"; @@ -38,4 +38,6 @@ export const init: ClientInit = async () => { prepareHmacSecretStore(), prepareOpfs(), ]); + + cleanupDanglingInfos(); // Intended }; diff --git a/src/lib/indexedDB/filesystem.ts b/src/lib/indexedDB/filesystem.ts index a6567ff..5c9fc4d 100644 --- a/src/lib/indexedDB/filesystem.ts +++ b/src/lib/indexedDB/filesystem.ts @@ -39,6 +39,10 @@ export const storeDirectoryInfo = async (directoryInfo: DirectoryInfo) => { await filesystem.directory.put(directoryInfo); }; +export const deleteDirectoryInfo = async (id: number) => { + await filesystem.directory.delete(id); +}; + export const getFileInfos = async (parentId: DirectoryId) => { return await filesystem.file.where({ parentId }).toArray(); }; @@ -50,3 +54,33 @@ export const getFileInfo = async (id: number) => { export const storeFileInfo = async (fileInfo: FileInfo) => { await filesystem.file.put(fileInfo); }; + +export const deleteFileInfo = async (id: number) => { + await filesystem.file.delete(id); +}; + +export const cleanupDanglingInfos = async () => { + const validDirectoryIds: number[] = []; + const validFileIds: number[] = []; + const queue: DirectoryId[] = ["root"]; + + while (true) { + const directoryId = queue.shift(); + if (!directoryId) break; + + const [subDirectories, files] = await Promise.all([ + filesystem.directory.where({ parentId: directoryId }).toArray(), + filesystem.file.where({ parentId: directoryId }).toArray(), + ]); + subDirectories.forEach(({ id }) => { + validDirectoryIds.push(id); + queue.push(id); + }); + files.forEach(({ id }) => validFileIds.push(id)); + } + + await Promise.all([ + filesystem.directory.where("id").noneOf(validDirectoryIds).delete(), + filesystem.file.where("id").noneOf(validFileIds).delete(), + ]); +}; diff --git a/src/lib/modules/filesystem.ts b/src/lib/modules/filesystem.ts index e1c929e..7313cb5 100644 --- a/src/lib/modules/filesystem.ts +++ b/src/lib/modules/filesystem.ts @@ -4,9 +4,11 @@ import { getDirectoryInfos as getDirectoryInfosFromIndexedDB, getDirectoryInfo as getDirectoryInfoFromIndexedDB, storeDirectoryInfo, + deleteDirectoryInfo, getFileInfos as getFileInfosFromIndexedDB, getFileInfo as getFileInfoFromIndexedDB, storeFileInfo, + deleteFileInfo, type DirectoryId, } from "$lib/indexedDB"; import { unwrapDataKey, decryptString } from "$lib/modules/crypto"; @@ -72,7 +74,13 @@ const fetchDirectoryInfoFromServer = async ( masterKey: CryptoKey, ) => { const res = await callGetApi(`/api/directory/${id}`); - if (!res.ok) throw new Error("Failed to fetch directory information"); // TODO: Handle 404 + if (res.status === 404) { + info.set(null); + await deleteDirectoryInfo(id as number); + } else if (!res.ok) { + throw new Error("Failed to fetch directory information"); + } + const { metadata, subDirectories: subDirectoryIds, @@ -138,10 +146,16 @@ const fetchFileInfoFromServer = async ( masterKey: CryptoKey, ) => { const res = await callGetApi(`/api/file/${id}`); - if (!res.ok) throw new Error("Failed to fetch file information"); // TODO: Handle 404 - const metadata: FileInfoResponse = await res.json(); + if (res.status === 404) { + info.set(null); + await deleteFileInfo(id); + } else if (!res.ok) { + throw new Error("Failed to fetch file information"); + } + const metadata: FileInfoResponse = await res.json(); const { dataKey } = await unwrapDataKey(metadata.dek, masterKey); + const name = await decryptString(metadata.name, metadata.nameIv, dataKey); const createdAt = metadata.createdAt && metadata.createdAtIv