diff --git a/src/lib/components/organisms/Category/File.svelte b/src/lib/components/organisms/Category/File.svelte index 23465c1..7d49cf3 100644 --- a/src/lib/components/organisms/Category/File.svelte +++ b/src/lib/components/organisms/Category/File.svelte @@ -2,7 +2,6 @@ import type { Writable } from "svelte/store"; import { ActionEntryButton } from "$lib/components/atoms"; import { DirectoryEntryLabel } from "$lib/components/molecules"; - import { getFileThumbnail } from "$lib/modules/file"; import type { FileInfo } from "$lib/modules/filesystem"; import { requestFileThumbnailDownload, type SelectedFile } from "./service"; @@ -34,10 +33,7 @@ $effect(() => { if ($info?.dataKey) { - getFileThumbnail($info.id) - .then( - (thumbnailUrl) => thumbnailUrl || requestFileThumbnailDownload($info.id, $info.dataKey!), - ) + requestFileThumbnailDownload($info.id, $info.dataKey) .then((thumbnailUrl) => { thumbnail = thumbnailUrl ?? undefined; }) diff --git a/src/lib/modules/file/cache.ts b/src/lib/modules/file/cache.ts index fe3c66c..31eac28 100644 --- a/src/lib/modules/file/cache.ts +++ b/src/lib/modules/file/cache.ts @@ -1,12 +1,15 @@ +import { LRUCache } from "lru-cache"; import { getFileCacheIndex as getFileCacheIndexFromIndexedDB, storeFileCacheIndex, deleteFileCacheIndex, type FileCacheIndex, } from "$lib/indexedDB"; -import { readFile, writeFile, deleteFile } from "$lib/modules/opfs"; +import { readFile, writeFile, deleteFile, deleteDirectory } from "$lib/modules/opfs"; +import { getThumbnailUrl } from "$lib/modules/thumbnail"; const fileCacheIndex = new Map(); +const loadedThumbnails = new LRUCache({ max: 100 }); export const prepareFileCache = async () => { for (const cache of await getFileCacheIndexFromIndexedDB()) { @@ -48,3 +51,32 @@ export const deleteFileCache = async (fileId: number) => { await deleteFile(`/cache/${fileId}`); await deleteFileCacheIndex(fileId); }; + +export const getFileThumbnailCache = async (fileId: number) => { + const thumbnail = loadedThumbnails.get(fileId); + if (thumbnail) { + return thumbnail; + } + + const thumbnailBuffer = await readFile(`/thumbnail/file/${fileId}`); + if (!thumbnailBuffer) return null; + + const thumbnailUrl = getThumbnailUrl(thumbnailBuffer); + loadedThumbnails.set(fileId, thumbnailUrl); + return thumbnailUrl; +}; + +export const storeFileThumbnailCache = async (fileId: number, thumbnailBuffer: ArrayBuffer) => { + await writeFile(`/thumbnail/file/${fileId}`, thumbnailBuffer); + loadedThumbnails.set(fileId, getThumbnailUrl(thumbnailBuffer)); +}; + +export const deleteFileThumbnailCache = async (fileId: number) => { + loadedThumbnails.delete(fileId); + await deleteFile(`/thumbnail/file/${fileId}`); +}; + +export const deleteAllFileThumbnailCaches = async () => { + loadedThumbnails.clear(); + await deleteDirectory("/thumbnail/file"); +}; diff --git a/src/lib/modules/file/index.ts b/src/lib/modules/file/index.ts index dc708ac..42a5613 100644 --- a/src/lib/modules/file/index.ts +++ b/src/lib/modules/file/index.ts @@ -1,4 +1,3 @@ export * from "./cache"; export * from "./download"; -export * from "./thumbnail"; export * from "./upload"; diff --git a/src/lib/modules/file/thumbnail.ts b/src/lib/modules/file/thumbnail.ts deleted file mode 100644 index 6757ffc..0000000 --- a/src/lib/modules/file/thumbnail.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { LRUCache } from "lru-cache"; -import { readFile, writeFile, deleteFile, deleteDirectory } from "$lib/modules/opfs"; -import { getThumbnailUrl } from "$lib/modules/thumbnail"; - -const loadedThumbnails = new LRUCache({ max: 100 }); - -export const getFileThumbnail = async (fileId: number) => { - const thumbnail = loadedThumbnails.get(fileId); - if (thumbnail) { - return thumbnail; - } - - const thumbnailBuffer = await readFile(`/thumbnails/${fileId}`); - if (!thumbnailBuffer) return null; - - const thumbnailUrl = getThumbnailUrl(thumbnailBuffer); - loadedThumbnails.set(fileId, thumbnailUrl); - return thumbnailUrl; -}; - -export const storeFileThumbnail = async (fileId: number, thumbnailBuffer: ArrayBuffer) => { - await writeFile(`/thumbnails/${fileId}`, thumbnailBuffer); - loadedThumbnails.set(fileId, getThumbnailUrl(thumbnailBuffer)); -}; - -export const deleteFileThumbnail = async (fileId: number) => { - loadedThumbnails.delete(fileId); - await deleteFile(`/thumbnails/${fileId}`); -}; - -export const deleteAllFileThumbnails = async () => { - loadedThumbnails.clear(); - await deleteDirectory("/thumbnails"); -}; diff --git a/src/lib/services/file.ts b/src/lib/services/file.ts index 57a0749..1a95538 100644 --- a/src/lib/services/file.ts +++ b/src/lib/services/file.ts @@ -1,6 +1,12 @@ import { callGetApi } from "$lib/hooks"; import { decryptData } from "$lib/modules/crypto"; -import { getFileCache, storeFileCache, downloadFile, storeFileThumbnail } from "$lib/modules/file"; +import { + getFileCache, + storeFileCache, + getFileThumbnailCache, + storeFileThumbnailCache, + downloadFile, +} from "$lib/modules/file"; import { getThumbnailUrl } from "$lib/modules/thumbnail"; import type { FileThumbnailInfoResponse } from "$lib/server/schemas"; @@ -18,6 +24,9 @@ export const requestFileDownload = async ( }; export const requestFileThumbnailDownload = async (fileId: number, dataKey: CryptoKey) => { + const cache = await getFileThumbnailCache(fileId); + if (cache) return cache; + let res = await callGetApi(`/api/file/${fileId}/thumbnail`); if (!res.ok) return null; @@ -29,6 +38,6 @@ export const requestFileThumbnailDownload = async (fileId: number, dataKey: Cryp const thumbnailEncrypted = await res.arrayBuffer(); const thumbnail = await decryptData(thumbnailEncrypted, thumbnailEncryptedIv, dataKey); - storeFileThumbnail(fileId, thumbnail); // Intended + storeFileThumbnailCache(fileId, thumbnail); // Intended return getThumbnailUrl(thumbnail); }; diff --git a/src/routes/(fullscreen)/settings/thumbnails/+page.svelte b/src/routes/(fullscreen)/settings/thumbnails/+page.svelte index a498f1c..68c6d6f 100644 --- a/src/routes/(fullscreen)/settings/thumbnails/+page.svelte +++ b/src/routes/(fullscreen)/settings/thumbnails/+page.svelte @@ -4,7 +4,7 @@ import { goto } from "$app/navigation"; import { BottomDiv, Button, FullscreenDiv } from "$lib/components/atoms"; import { IconEntryButton, TopBar } from "$lib/components/molecules"; - import { deleteAllFileThumbnails } from "$lib/modules/file"; + import { deleteAllFileThumbnailCaches } from "$lib/modules/file"; import { getFileInfo } from "$lib/modules/filesystem"; import { masterKeyStore } from "$lib/stores"; import File from "./File.svelte"; @@ -44,7 +44,7 @@
- + 저장된 썸네일 모두 삭제하기
diff --git a/src/routes/(fullscreen)/settings/thumbnails/+page.ts b/src/routes/(fullscreen)/settings/thumbnails/+page.ts index 3fc7cff..a16cb8e 100644 --- a/src/routes/(fullscreen)/settings/thumbnails/+page.ts +++ b/src/routes/(fullscreen)/settings/thumbnails/+page.ts @@ -1,6 +1,6 @@ import { error } from "@sveltejs/kit"; import { callPostApi } from "$lib/hooks"; -import type { MissingThumbnailFileScanResponse } from "$lib/server/schemas/file"; +import type { MissingThumbnailFileScanResponse } from "$lib/server/schemas"; import type { PageLoad } from "./$types"; export const load: PageLoad = async ({ fetch }) => { diff --git a/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts b/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts index a3f77ce..4e430d5 100644 --- a/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts +++ b/src/routes/(fullscreen)/settings/thumbnails/service.svelte.ts @@ -1,7 +1,7 @@ import { limitFunction } from "p-limit"; import { get, writable, type Writable } from "svelte/store"; import { encryptData } from "$lib/modules/crypto"; -import { storeFileThumbnail } from "$lib/modules/file"; +import { storeFileThumbnailCache } from "$lib/modules/file"; import type { FileInfo } from "$lib/modules/filesystem"; import { generateImageThumbnail, generateVideoThumbnail } from "$lib/modules/thumbnail"; import type { FileThumbnailUploadRequest } from "$lib/server/schemas"; @@ -104,7 +104,7 @@ const requestThumbnailUpload = limitFunction( workingFiles.delete(fileId); persistentStates.files = persistentStates.files.filter(({ id }) => id != fileId); - storeFileThumbnail(fileId, thumbnail.plaintext); // Intended + storeFileThumbnailCache(fileId, thumbnail.plaintext); // Intended return true; }, { concurrency: 4 }, diff --git a/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte b/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte index 4245898..8251331 100644 --- a/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte +++ b/src/routes/(main)/directory/[[id]]/DirectoryEntries/File.svelte @@ -2,7 +2,6 @@ import type { Writable } from "svelte/store"; import { ActionEntryButton } from "$lib/components/atoms"; import { DirectoryEntryLabel } from "$lib/components/molecules"; - import { getFileThumbnail } from "$lib/modules/file"; import type { FileInfo } from "$lib/modules/filesystem"; import { formatDateTime } from "$lib/modules/util"; import { requestFileThumbnailDownload } from "./service"; @@ -36,10 +35,7 @@ $effect(() => { if ($info?.dataKey) { - getFileThumbnail($info.id) - .then( - (thumbnailUrl) => thumbnailUrl || requestFileThumbnailDownload($info.id, $info.dataKey!), - ) + requestFileThumbnailDownload($info.id, $info.dataKey) .then((thumbnailUrl) => { thumbnail = thumbnailUrl ?? undefined; }) diff --git a/src/routes/(main)/directory/[[id]]/service.svelte.ts b/src/routes/(main)/directory/[[id]]/service.svelte.ts index b29630a..ba5fc4a 100644 --- a/src/routes/(main)/directory/[[id]]/service.svelte.ts +++ b/src/routes/(main)/directory/[[id]]/service.svelte.ts @@ -5,8 +5,8 @@ import { generateDataKey, wrapDataKey, unwrapHmacSecret, encryptString } from "$ import { storeFileCache, deleteFileCache, - storeFileThumbnail, - deleteFileThumbnail, + storeFileThumbnailCache, + deleteFileThumbnailCache, uploadFile, } from "$lib/modules/file"; import type { @@ -88,7 +88,7 @@ export const requestFileUpload = async ( storeFileCache(res.fileId, res.fileBuffer); // Intended if (res.thumbnailBuffer) { - storeFileThumbnail(res.fileId, res.thumbnailBuffer); // Intended + storeFileThumbnailCache(res.fileId, res.thumbnailBuffer); // Intended } return true; @@ -121,11 +121,11 @@ export const requestEntryDeletion = async (entry: SelectedEntry) => { if (entry.type === "directory") { const { deletedFiles }: DirectoryDeleteResponse = await res.json(); await Promise.all( - deletedFiles.flatMap((fileId) => [deleteFileCache(fileId), deleteFileThumbnail(fileId)]), + deletedFiles.flatMap((fileId) => [deleteFileCache(fileId), deleteFileThumbnailCache(fileId)]), ); return true; } else { - await Promise.all([deleteFileCache(entry.id), deleteFileThumbnail(entry.id)]); + await Promise.all([deleteFileCache(entry.id), deleteFileThumbnailCache(entry.id)]); return true; } };