From dd0a8875763543ec96e622758406800d4d0e4034 Mon Sep 17 00:00:00 2001 From: static Date: Wed, 22 Jan 2025 22:49:07 +0900 Subject: [PATCH] =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EC=A0=95=EB=B3=B4=EB=8F=84=20IndexedDB?= =?UTF-8?q?=EC=97=90=20=EC=BA=90=EC=8B=B1=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/indexedDB/filesystem.ts | 51 ++++++++++++++++++++++++++++++--- src/lib/modules/filesystem.ts | 41 +++++++++++++++++++++++--- 2 files changed, 84 insertions(+), 8 deletions(-) diff --git a/src/lib/indexedDB/filesystem.ts b/src/lib/indexedDB/filesystem.ts index 5c9fc4d..293c16d 100644 --- a/src/lib/indexedDB/filesystem.ts +++ b/src/lib/indexedDB/filesystem.ts @@ -15,16 +15,28 @@ interface FileInfo { contentType: string; createdAt?: Date; lastModifiedAt: Date; + categoryIds: number[]; +} + +export type CategoryId = "root" | number; + +interface CategoryInfo { + id: number; + parentId: CategoryId; + name: string; + files: { id: number; isRecursive: boolean }[]; } const filesystem = new Dexie("filesystem") as Dexie & { directory: EntityTable; file: EntityTable; + category: EntityTable; }; -filesystem.version(1).stores({ +filesystem.version(2).stores({ directory: "id, parentId", file: "id, parentId", + category: "id, parentId", }); export const getDirectoryInfos = async (parentId: DirectoryId) => { @@ -59,13 +71,29 @@ export const deleteFileInfo = async (id: number) => { await filesystem.file.delete(id); }; +export const getCategoryInfos = async (parentId: CategoryId) => { + return await filesystem.category.where({ parentId }).toArray(); +}; + +export const getCategoryInfo = async (id: number) => { + return await filesystem.category.get(id); +}; + +export const storeCategoryInfo = async (categoryInfo: CategoryInfo) => { + await filesystem.category.put(categoryInfo); +}; + +export const deleteCategoryInfo = async (id: number) => { + await filesystem.category.delete(id); +}; + export const cleanupDanglingInfos = async () => { const validDirectoryIds: number[] = []; const validFileIds: number[] = []; - const queue: DirectoryId[] = ["root"]; + const directoryQueue: DirectoryId[] = ["root"]; while (true) { - const directoryId = queue.shift(); + const directoryId = directoryQueue.shift(); if (!directoryId) break; const [subDirectories, files] = await Promise.all([ @@ -74,13 +102,28 @@ export const cleanupDanglingInfos = async () => { ]); subDirectories.forEach(({ id }) => { validDirectoryIds.push(id); - queue.push(id); + directoryQueue.push(id); }); files.forEach(({ id }) => validFileIds.push(id)); } + const validCategoryIds: number[] = []; + const categoryQueue: CategoryId[] = ["root"]; + + while (true) { + const categoryId = categoryQueue.shift(); + if (!categoryId) break; + + const subCategories = await filesystem.category.where({ parentId: categoryId }).toArray(); + subCategories.forEach(({ id }) => { + validCategoryIds.push(id); + categoryQueue.push(id); + }); + } + await Promise.all([ filesystem.directory.where("id").noneOf(validDirectoryIds).delete(), filesystem.file.where("id").noneOf(validFileIds).delete(), + filesystem.category.where("id").noneOf(validCategoryIds).delete(), ]); }; diff --git a/src/lib/modules/filesystem.ts b/src/lib/modules/filesystem.ts index 0e786ce..dc1208e 100644 --- a/src/lib/modules/filesystem.ts +++ b/src/lib/modules/filesystem.ts @@ -9,7 +9,12 @@ import { getFileInfo as getFileInfoFromIndexedDB, storeFileInfo, deleteFileInfo, + getCategoryInfos as getCategoryInfosFromIndexedDB, + getCategoryInfo as getCategoryInfoFromIndexedDB, + storeCategoryInfo, + deleteCategoryInfo, type DirectoryId, + type CategoryId, } from "$lib/indexedDB"; import { unwrapDataKey, decryptString } from "$lib/modules/crypto"; import type { @@ -49,8 +54,6 @@ export interface FileInfo { categoryIds: number[]; } -type CategoryId = "root" | number; - export type CategoryInfo = | { id: "root"; @@ -161,7 +164,7 @@ const fetchFileInfoFromIndexedDB = async (id: number, info: Writable { @@ -214,6 +217,7 @@ const fetchFileInfoFromServer = async ( contentType: metadata.contentType, createdAt, lastModifiedAt, + categoryIds: metadata.categories, }); }; @@ -235,6 +239,26 @@ export const getFileInfo = (fileId: number, masterKey: CryptoKey) => { return info; }; +const fetchCategoryInfoFromIndexedDB = async ( + id: CategoryId, + info: Writable, +) => { + if (get(info)) return; + + const [category, subCategories] = await Promise.all([ + id !== "root" ? getCategoryInfoFromIndexedDB(id) : undefined, + getCategoryInfosFromIndexedDB(id), + ]); + const subCategoryIds = subCategories.map(({ id }) => id); + + if (id === "root") { + info.set({ id, subCategoryIds }); + } else { + if (!category) return; + info.set({ id, name: category.name, subCategoryIds, files: category.files }); + } +}; + const fetchCategoryInfoFromServer = async ( id: CategoryId, info: Writable, @@ -243,6 +267,7 @@ const fetchCategoryInfoFromServer = async ( let res = await callGetApi(`/api/category/${id}`); if (res.status === 404) { info.set(null); + await deleteCategoryInfo(id as number); return; } else if (!res.ok) { throw new Error("Failed to fetch category information"); @@ -262,6 +287,7 @@ const fetchCategoryInfoFromServer = async ( } const { files }: CategoryFileListResponse = await res.json(); + const filesMapped = files.map(({ file, isRecursive }) => ({ id: file, isRecursive })); info.set({ id, @@ -269,7 +295,13 @@ const fetchCategoryInfoFromServer = async ( dataKeyVersion: new Date(metadata!.dekVersion), name, subCategoryIds: subCategories, - files: files.map(({ file, isRecursive }) => ({ id: file, isRecursive })), + files: filesMapped, + }); + await storeCategoryInfo({ + id, + parentId: metadata!.parent, + name, + files: filesMapped, }); } }; @@ -279,6 +311,7 @@ const fetchCategoryInfo = async ( info: Writable, masterKey: CryptoKey, ) => { + await fetchCategoryInfoFromIndexedDB(id, info); await fetchCategoryInfoFromServer(id, info, masterKey); };