diff --git a/src/lib/modules/filesystem/category.ts b/src/lib/modules/filesystem/category.ts index 491d43d..310a220 100644 --- a/src/lib/modules/filesystem/category.ts +++ b/src/lib/modules/filesystem/category.ts @@ -1,9 +1,9 @@ import * as IndexedDB from "$lib/indexedDB"; import { trpc, isTRPCClientError } from "$trpc/client"; import { FilesystemCache, decryptFileMetadata, decryptCategoryMetadata } from "./internal.svelte"; -import type { CategoryInfo } from "./types"; +import type { MaybeCategoryInfo } from "./types"; -const cache = new FilesystemCache>(); +const cache = new FilesystemCache>(); const fetchFromIndexedDB = async (id: CategoryId) => { const [category, subCategories] = await Promise.all([ @@ -29,10 +29,15 @@ const fetchFromIndexedDB = async (id: CategoryId) => { : undefined; if (id === "root") { - return { id, subCategories }; + return { + id, + exists: true as const, + subCategories, + }; } else if (category) { return { id, + exists: true as const, name: category.name, subCategories, files: files!.filter((file) => !!file), @@ -68,10 +73,15 @@ const fetchFromServer = async (id: CategoryId, masterKey: CryptoKey) => { ]); if (id === "root") { - return { id, subCategories }; + return { + id, + exists: true as const, + subCategories, + }; } else { return { id, + exists: true as const, subCategories, files, ...(await decryptCategoryMetadata(metadata!, masterKey)), @@ -79,9 +89,8 @@ const fetchFromServer = async (id: CategoryId, masterKey: CryptoKey) => { } } catch (e) { if (isTRPCClientError(e) && e.data?.code === "NOT_FOUND") { - cache.delete(id); await IndexedDB.deleteCategoryInfo(id as number); - return; + return { id, exists: false as const }; } throw e; } diff --git a/src/lib/modules/filesystem/directory.ts b/src/lib/modules/filesystem/directory.ts index b4f9aac..6417758 100644 --- a/src/lib/modules/filesystem/directory.ts +++ b/src/lib/modules/filesystem/directory.ts @@ -2,9 +2,9 @@ import * as IndexedDB from "$lib/indexedDB"; import { monotonicResolve } from "$lib/utils"; import { trpc, isTRPCClientError } from "$trpc/client"; import { FilesystemCache, decryptDirectoryMetadata, decryptFileMetadata } from "./internal.svelte"; -import type { DirectoryInfo } from "./types"; +import type { MaybeDirectoryInfo } from "./types"; -const cache = new FilesystemCache(); +const cache = new FilesystemCache(); const fetchFromIndexedDB = async (id: DirectoryId) => { const [directory, subDirectories, files] = await Promise.all([ @@ -14,9 +14,21 @@ const fetchFromIndexedDB = async (id: DirectoryId) => { ]); if (id === "root") { - return { id, subDirectories, files }; + return { + id, + exists: true as const, + subDirectories, + files, + }; } else if (directory) { - return { id, parentId: directory.parentId, name: directory.name, subDirectories, files }; + return { + id, + exists: true as const, + parentId: directory.parentId, + name: directory.name, + subDirectories, + files, + }; } }; @@ -44,10 +56,16 @@ const fetchFromServer = async (id: DirectoryId, masterKey: CryptoKey) => { ]); if (id === "root") { - return { id, subDirectories, files }; + return { + id, + exists: true as const, + subDirectories, + files, + }; } else { return { id, + exists: true as const, parentId: metadata!.parent, subDirectories, files, @@ -56,9 +74,8 @@ const fetchFromServer = async (id: DirectoryId, masterKey: CryptoKey) => { } } catch (e) { if (isTRPCClientError(e) && e.data?.code === "NOT_FOUND") { - cache.delete(id); await IndexedDB.deleteDirectoryInfo(id as number); - return; + return { id, exists: false as const }; } throw e; } diff --git a/src/lib/modules/filesystem/file.ts b/src/lib/modules/filesystem/file.ts index 450ef41..fc6f53c 100644 --- a/src/lib/modules/filesystem/file.ts +++ b/src/lib/modules/filesystem/file.ts @@ -2,9 +2,9 @@ import * as IndexedDB from "$lib/indexedDB"; import { monotonicResolve } from "$lib/utils"; import { trpc, isTRPCClientError } from "$trpc/client"; import { FilesystemCache, decryptFileMetadata, decryptCategoryMetadata } from "./internal.svelte"; -import type { FileInfo } from "./types"; +import type { MaybeFileInfo } from "./types"; -const cache = new FilesystemCache(); +const cache = new FilesystemCache(); const fetchFromIndexedDB = async (id: number) => { const file = await IndexedDB.getFileInfo(id); @@ -20,6 +20,7 @@ const fetchFromIndexedDB = async (id: number) => { if (file) { return { id, + exists: true as const, parentId: file.parentId, contentType: file.contentType, name: file.name, @@ -44,6 +45,7 @@ const fetchFromServer = async (id: number, masterKey: CryptoKey) => { return { id, + exists: true as const, parentId: metadata.parent, contentType: metadata.contentType, contentIv: metadata.contentIv, @@ -52,9 +54,8 @@ const fetchFromServer = async (id: number, masterKey: CryptoKey) => { }; } catch (e) { if (isTRPCClientError(e) && e.data?.code === "NOT_FOUND") { - cache.delete(id); await IndexedDB.deleteFileInfo(id); - return; + return { id, exists: false as const }; } throw e; } diff --git a/src/lib/modules/filesystem/internal.svelte.ts b/src/lib/modules/filesystem/internal.svelte.ts index f5e5e1f..f98320d 100644 --- a/src/lib/modules/filesystem/internal.svelte.ts +++ b/src/lib/modules/filesystem/internal.svelte.ts @@ -30,10 +30,6 @@ export class FilesystemCache { return info ?? promise; } - - delete(key: K) { - this.map.delete(key); - } } export const decryptDirectoryMetadata = async ( diff --git a/src/lib/modules/filesystem/types.ts b/src/lib/modules/filesystem/types.ts index 6374474..15b0e93 100644 --- a/src/lib/modules/filesystem/types.ts +++ b/src/lib/modules/filesystem/types.ts @@ -1,4 +1,5 @@ export type DataKey = { key: CryptoKey; version: Date }; +type AllUndefined = { [K in keyof T]?: undefined }; interface LocalDirectoryInfo { id: number; @@ -20,6 +21,9 @@ interface RootDirectoryInfo { export type DirectoryInfo = LocalDirectoryInfo | RootDirectoryInfo; export type SubDirectoryInfo = Omit; +export type MaybeDirectoryInfo = + | (DirectoryInfo & { exists: true }) + | ({ id: DirectoryId; exists: false } & AllUndefined>); export interface FileInfo { id: number; @@ -35,6 +39,9 @@ export interface FileInfo { export type SummarizedFileInfo = Omit; export type CategoryFileInfo = SummarizedFileInfo & { isRecursive: boolean }; +export type MaybeFileInfo = + | (FileInfo & { exists: true }) + | ({ id: number; exists: false } & AllUndefined>); interface LocalCategoryInfo { id: number; @@ -59,3 +66,6 @@ export type SubCategoryInfo = Omit< LocalCategoryInfo, "subCategories" | "files" | "isFileRecursive" >; +export type MaybeCategoryInfo = + | (CategoryInfo & { exists: true }) + | ({ id: CategoryId; exists: false } & AllUndefined>); diff --git a/src/routes/(fullscreen)/file/[id]/+page.svelte b/src/routes/(fullscreen)/file/[id]/+page.svelte index 113ddbe..7a04a7f 100644 --- a/src/routes/(fullscreen)/file/[id]/+page.svelte +++ b/src/routes/(fullscreen)/file/[id]/+page.svelte @@ -5,7 +5,7 @@ import { page } from "$app/state"; import { FullscreenDiv } from "$lib/components/atoms"; import { Categories, IconEntryButton, TopBar } from "$lib/components/molecules"; - import { getFileInfo, type FileInfo } from "$lib/modules/filesystem"; + import { getFileInfo, type FileInfo, type MaybeFileInfo } from "$lib/modules/filesystem"; import { captureVideoThumbnail } from "$lib/modules/thumbnail"; import { getFileDownloadState } from "$lib/modules/file"; import { masterKeyStore } from "$lib/stores"; @@ -26,7 +26,7 @@ let { data } = $props(); - let infoPromise: Promise | undefined = $state(); + let infoPromise: Promise | undefined = $state(); let info: FileInfo | null = $state(null); let downloadState = $derived(getFileDownloadState(data.id)); @@ -75,7 +75,9 @@ $effect(() => { infoPromise = getFileInfo(data.id, $masterKeyStore?.get(1)?.key!).then((fileInfo) => { - info = fileInfo; + if (fileInfo.exists) { + info = fileInfo; + } return fileInfo; }); info = null; diff --git a/src/routes/(fullscreen)/file/[id]/AddToCategoryBottomSheet.svelte b/src/routes/(fullscreen)/file/[id]/AddToCategoryBottomSheet.svelte index b39ef72..5d89512 100644 --- a/src/routes/(fullscreen)/file/[id]/AddToCategoryBottomSheet.svelte +++ b/src/routes/(fullscreen)/file/[id]/AddToCategoryBottomSheet.svelte @@ -2,7 +2,7 @@ import { BottomDiv, BottomSheet, Button, FullscreenDiv } from "$lib/components/atoms"; import { SubCategories } from "$lib/components/molecules"; import { CategoryCreateModal } from "$lib/components/organisms"; - import { getCategoryInfo, type CategoryInfo } from "$lib/modules/filesystem"; + import { getCategoryInfo, type MaybeCategoryInfo } from "$lib/modules/filesystem"; import { masterKeyStore } from "$lib/stores"; import { requestCategoryCreation } from "./service"; @@ -13,7 +13,7 @@ let { onAddToCategoryClick, isOpen = $bindable() }: Props = $props(); - let categoryInfoPromise: Promise | undefined = $state(); + let categoryInfoPromise: Promise | undefined = $state(); let isCategoryCreateModalOpen = $state(false); @@ -25,7 +25,7 @@ {#await categoryInfoPromise then categoryInfo} - {#if categoryInfo} + {#if categoryInfo?.exists} {#each files as file, index} - + {#if file.exists} + + {/if} {/each} {/await} diff --git a/src/routes/(fullscreen)/gallery/+page.svelte b/src/routes/(fullscreen)/gallery/+page.svelte index 4408cc1..39ae445 100644 --- a/src/routes/(fullscreen)/gallery/+page.svelte +++ b/src/routes/(fullscreen)/gallery/+page.svelte @@ -4,12 +4,12 @@ import { FullscreenDiv } from "$lib/components/atoms"; import { TopBar } from "$lib/components/molecules"; import { Gallery } from "$lib/components/organisms"; - import { getFileInfo, type FileInfo } from "$lib/modules/filesystem"; + import { getFileInfo, type MaybeFileInfo } from "$lib/modules/filesystem"; import { masterKeyStore } from "$lib/stores"; let { data } = $props(); - let files: (FileInfo | null)[] = $state([]); + let files: MaybeFileInfo[] = $state([]); onMount(async () => { files = await Promise.all( @@ -25,7 +25,7 @@ !!file)} + files={files.filter((file) => file?.exists)} onFileClick={({ id }) => goto(`/file/${id}?from=gallery`)} /> diff --git a/src/routes/(fullscreen)/settings/cache/+page.svelte b/src/routes/(fullscreen)/settings/cache/+page.svelte index 2812b93..b37701f 100644 --- a/src/routes/(fullscreen)/settings/cache/+page.svelte +++ b/src/routes/(fullscreen)/settings/cache/+page.svelte @@ -4,14 +4,14 @@ import { TopBar } from "$lib/components/molecules"; import type { FileCacheIndex } from "$lib/indexedDB"; import { getFileCacheIndex, deleteFileCache as doDeleteFileCache } from "$lib/modules/file"; - import { getFileInfo, type FileInfo } from "$lib/modules/filesystem"; + import { getFileInfo, type MaybeFileInfo } from "$lib/modules/filesystem"; import { masterKeyStore } from "$lib/stores"; import { formatFileSize } from "$lib/utils"; import File from "./File.svelte"; interface FileCache { index: FileCacheIndex; - fileInfo: FileInfo | null; + info: MaybeFileInfo; } let fileCache: FileCache[] | undefined = $state(); @@ -26,7 +26,7 @@ fileCache = await Promise.all( getFileCacheIndex().map(async (index) => ({ index, - fileInfo: await getFileInfo(index.fileId, $masterKeyStore?.get(1)?.key!), + info: await getFileInfo(index.fileId, $masterKeyStore?.get(1)?.key!), })), ); fileCache.sort((a, b) => a.index.lastRetrievedAt.getTime() - b.index.lastRetrievedAt.getTime()); @@ -55,8 +55,8 @@

캐시를 삭제하더라도 원본 파일은 삭제되지 않아요.

- {#each fileCache as { index, fileInfo }} - + {#each fileCache as { index, info }} + {/each}
diff --git a/src/routes/(fullscreen)/settings/cache/File.svelte b/src/routes/(fullscreen)/settings/cache/File.svelte index 5ae7794..2727381 100644 --- a/src/routes/(fullscreen)/settings/cache/File.svelte +++ b/src/routes/(fullscreen)/settings/cache/File.svelte @@ -1,6 +1,6 @@
- {#if info} + {#if info.exists}
@@ -27,7 +27,7 @@
{/if}
- {#if info} + {#if info.exists}

{info.name}

{:else}

삭제된 파일

diff --git a/src/routes/(fullscreen)/settings/thumbnail/+page.svelte b/src/routes/(fullscreen)/settings/thumbnail/+page.svelte index 6cc0aa8..127f5eb 100644 --- a/src/routes/(fullscreen)/settings/thumbnail/+page.svelte +++ b/src/routes/(fullscreen)/settings/thumbnail/+page.svelte @@ -1,6 +1,5 @@