mirror of
https://github.com/kmc7468/arkvault.git
synced 2025-12-12 21:08:46 +00:00
파일 썸네일이 캐시되는 OPFS의 경로 변경
This commit is contained in:
@@ -2,7 +2,6 @@
|
|||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
import { ActionEntryButton } from "$lib/components/atoms";
|
import { ActionEntryButton } from "$lib/components/atoms";
|
||||||
import { DirectoryEntryLabel } from "$lib/components/molecules";
|
import { DirectoryEntryLabel } from "$lib/components/molecules";
|
||||||
import { getFileThumbnail } from "$lib/modules/file";
|
|
||||||
import type { FileInfo } from "$lib/modules/filesystem";
|
import type { FileInfo } from "$lib/modules/filesystem";
|
||||||
import { requestFileThumbnailDownload, type SelectedFile } from "./service";
|
import { requestFileThumbnailDownload, type SelectedFile } from "./service";
|
||||||
|
|
||||||
@@ -34,10 +33,7 @@
|
|||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if ($info?.dataKey) {
|
if ($info?.dataKey) {
|
||||||
getFileThumbnail($info.id)
|
requestFileThumbnailDownload($info.id, $info.dataKey)
|
||||||
.then(
|
|
||||||
(thumbnailUrl) => thumbnailUrl || requestFileThumbnailDownload($info.id, $info.dataKey!),
|
|
||||||
)
|
|
||||||
.then((thumbnailUrl) => {
|
.then((thumbnailUrl) => {
|
||||||
thumbnail = thumbnailUrl ?? undefined;
|
thumbnail = thumbnailUrl ?? undefined;
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
|
import { LRUCache } from "lru-cache";
|
||||||
import {
|
import {
|
||||||
getFileCacheIndex as getFileCacheIndexFromIndexedDB,
|
getFileCacheIndex as getFileCacheIndexFromIndexedDB,
|
||||||
storeFileCacheIndex,
|
storeFileCacheIndex,
|
||||||
deleteFileCacheIndex,
|
deleteFileCacheIndex,
|
||||||
type FileCacheIndex,
|
type FileCacheIndex,
|
||||||
} from "$lib/indexedDB";
|
} 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<number, FileCacheIndex>();
|
const fileCacheIndex = new Map<number, FileCacheIndex>();
|
||||||
|
const loadedThumbnails = new LRUCache<number, string>({ max: 100 });
|
||||||
|
|
||||||
export const prepareFileCache = async () => {
|
export const prepareFileCache = async () => {
|
||||||
for (const cache of await getFileCacheIndexFromIndexedDB()) {
|
for (const cache of await getFileCacheIndexFromIndexedDB()) {
|
||||||
@@ -48,3 +51,32 @@ export const deleteFileCache = async (fileId: number) => {
|
|||||||
await deleteFile(`/cache/${fileId}`);
|
await deleteFile(`/cache/${fileId}`);
|
||||||
await deleteFileCacheIndex(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");
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
export * from "./cache";
|
export * from "./cache";
|
||||||
export * from "./download";
|
export * from "./download";
|
||||||
export * from "./thumbnail";
|
|
||||||
export * from "./upload";
|
export * from "./upload";
|
||||||
|
|||||||
@@ -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<number, string>({ 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");
|
|
||||||
};
|
|
||||||
@@ -1,6 +1,12 @@
|
|||||||
import { callGetApi } from "$lib/hooks";
|
import { callGetApi } from "$lib/hooks";
|
||||||
import { decryptData } from "$lib/modules/crypto";
|
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 { getThumbnailUrl } from "$lib/modules/thumbnail";
|
||||||
import type { FileThumbnailInfoResponse } from "$lib/server/schemas";
|
import type { FileThumbnailInfoResponse } from "$lib/server/schemas";
|
||||||
|
|
||||||
@@ -18,6 +24,9 @@ export const requestFileDownload = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const requestFileThumbnailDownload = async (fileId: number, dataKey: CryptoKey) => {
|
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`);
|
let res = await callGetApi(`/api/file/${fileId}/thumbnail`);
|
||||||
if (!res.ok) return null;
|
if (!res.ok) return null;
|
||||||
|
|
||||||
@@ -29,6 +38,6 @@ export const requestFileThumbnailDownload = async (fileId: number, dataKey: Cryp
|
|||||||
const thumbnailEncrypted = await res.arrayBuffer();
|
const thumbnailEncrypted = await res.arrayBuffer();
|
||||||
const thumbnail = await decryptData(thumbnailEncrypted, thumbnailEncryptedIv, dataKey);
|
const thumbnail = await decryptData(thumbnailEncrypted, thumbnailEncryptedIv, dataKey);
|
||||||
|
|
||||||
storeFileThumbnail(fileId, thumbnail); // Intended
|
storeFileThumbnailCache(fileId, thumbnail); // Intended
|
||||||
return getThumbnailUrl(thumbnail);
|
return getThumbnailUrl(thumbnail);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import { goto } from "$app/navigation";
|
import { goto } from "$app/navigation";
|
||||||
import { BottomDiv, Button, FullscreenDiv } from "$lib/components/atoms";
|
import { BottomDiv, Button, FullscreenDiv } from "$lib/components/atoms";
|
||||||
import { IconEntryButton, TopBar } from "$lib/components/molecules";
|
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 { getFileInfo } from "$lib/modules/filesystem";
|
||||||
import { masterKeyStore } from "$lib/stores";
|
import { masterKeyStore } from "$lib/stores";
|
||||||
import File from "./File.svelte";
|
import File from "./File.svelte";
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
<FullscreenDiv class="bg-gray-100 !px-0">
|
<FullscreenDiv class="bg-gray-100 !px-0">
|
||||||
<div class="flex flex-grow flex-col space-y-4">
|
<div class="flex flex-grow flex-col space-y-4">
|
||||||
<div class="flex-shrink-0 bg-white p-4 !pt-0">
|
<div class="flex-shrink-0 bg-white p-4 !pt-0">
|
||||||
<IconEntryButton icon={IconDelete} onclick={deleteAllFileThumbnails} class="w-full">
|
<IconEntryButton icon={IconDelete} onclick={deleteAllFileThumbnailCaches} class="w-full">
|
||||||
저장된 썸네일 모두 삭제하기
|
저장된 썸네일 모두 삭제하기
|
||||||
</IconEntryButton>
|
</IconEntryButton>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { error } from "@sveltejs/kit";
|
import { error } from "@sveltejs/kit";
|
||||||
import { callPostApi } from "$lib/hooks";
|
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";
|
import type { PageLoad } from "./$types";
|
||||||
|
|
||||||
export const load: PageLoad = async ({ fetch }) => {
|
export const load: PageLoad = async ({ fetch }) => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { limitFunction } from "p-limit";
|
import { limitFunction } from "p-limit";
|
||||||
import { get, writable, type Writable } from "svelte/store";
|
import { get, writable, type Writable } from "svelte/store";
|
||||||
import { encryptData } from "$lib/modules/crypto";
|
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 type { FileInfo } from "$lib/modules/filesystem";
|
||||||
import { generateImageThumbnail, generateVideoThumbnail } from "$lib/modules/thumbnail";
|
import { generateImageThumbnail, generateVideoThumbnail } from "$lib/modules/thumbnail";
|
||||||
import type { FileThumbnailUploadRequest } from "$lib/server/schemas";
|
import type { FileThumbnailUploadRequest } from "$lib/server/schemas";
|
||||||
@@ -104,7 +104,7 @@ const requestThumbnailUpload = limitFunction(
|
|||||||
workingFiles.delete(fileId);
|
workingFiles.delete(fileId);
|
||||||
persistentStates.files = persistentStates.files.filter(({ id }) => id != fileId);
|
persistentStates.files = persistentStates.files.filter(({ id }) => id != fileId);
|
||||||
|
|
||||||
storeFileThumbnail(fileId, thumbnail.plaintext); // Intended
|
storeFileThumbnailCache(fileId, thumbnail.plaintext); // Intended
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
{ concurrency: 4 },
|
{ concurrency: 4 },
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
import { ActionEntryButton } from "$lib/components/atoms";
|
import { ActionEntryButton } from "$lib/components/atoms";
|
||||||
import { DirectoryEntryLabel } from "$lib/components/molecules";
|
import { DirectoryEntryLabel } from "$lib/components/molecules";
|
||||||
import { getFileThumbnail } from "$lib/modules/file";
|
|
||||||
import type { FileInfo } from "$lib/modules/filesystem";
|
import type { FileInfo } from "$lib/modules/filesystem";
|
||||||
import { formatDateTime } from "$lib/modules/util";
|
import { formatDateTime } from "$lib/modules/util";
|
||||||
import { requestFileThumbnailDownload } from "./service";
|
import { requestFileThumbnailDownload } from "./service";
|
||||||
@@ -36,10 +35,7 @@
|
|||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if ($info?.dataKey) {
|
if ($info?.dataKey) {
|
||||||
getFileThumbnail($info.id)
|
requestFileThumbnailDownload($info.id, $info.dataKey)
|
||||||
.then(
|
|
||||||
(thumbnailUrl) => thumbnailUrl || requestFileThumbnailDownload($info.id, $info.dataKey!),
|
|
||||||
)
|
|
||||||
.then((thumbnailUrl) => {
|
.then((thumbnailUrl) => {
|
||||||
thumbnail = thumbnailUrl ?? undefined;
|
thumbnail = thumbnailUrl ?? undefined;
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import { generateDataKey, wrapDataKey, unwrapHmacSecret, encryptString } from "$
|
|||||||
import {
|
import {
|
||||||
storeFileCache,
|
storeFileCache,
|
||||||
deleteFileCache,
|
deleteFileCache,
|
||||||
storeFileThumbnail,
|
storeFileThumbnailCache,
|
||||||
deleteFileThumbnail,
|
deleteFileThumbnailCache,
|
||||||
uploadFile,
|
uploadFile,
|
||||||
} from "$lib/modules/file";
|
} from "$lib/modules/file";
|
||||||
import type {
|
import type {
|
||||||
@@ -88,7 +88,7 @@ export const requestFileUpload = async (
|
|||||||
|
|
||||||
storeFileCache(res.fileId, res.fileBuffer); // Intended
|
storeFileCache(res.fileId, res.fileBuffer); // Intended
|
||||||
if (res.thumbnailBuffer) {
|
if (res.thumbnailBuffer) {
|
||||||
storeFileThumbnail(res.fileId, res.thumbnailBuffer); // Intended
|
storeFileThumbnailCache(res.fileId, res.thumbnailBuffer); // Intended
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -121,11 +121,11 @@ export const requestEntryDeletion = async (entry: SelectedEntry) => {
|
|||||||
if (entry.type === "directory") {
|
if (entry.type === "directory") {
|
||||||
const { deletedFiles }: DirectoryDeleteResponse = await res.json();
|
const { deletedFiles }: DirectoryDeleteResponse = await res.json();
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
deletedFiles.flatMap((fileId) => [deleteFileCache(fileId), deleteFileThumbnail(fileId)]),
|
deletedFiles.flatMap((fileId) => [deleteFileCache(fileId), deleteFileThumbnailCache(fileId)]),
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
await Promise.all([deleteFileCache(entry.id), deleteFileThumbnail(entry.id)]);
|
await Promise.all([deleteFileCache(entry.id), deleteFileThumbnailCache(entry.id)]);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user