diff --git a/src/lib/components/atoms/RowVirtualizer.svelte b/src/lib/components/atoms/RowVirtualizer.svelte index e821c5f..444b9c7 100644 --- a/src/lib/components/atoms/RowVirtualizer.svelte +++ b/src/lib/components/atoms/RowVirtualizer.svelte @@ -1,6 +1,6 @@ -
하위 카테고리의 파일
이 카테고리에 추가된 파일이 없어요.
- {/snippet} -이 카테고리에 추가된 파일이 없어요.
+ {/snippet} +{row.label}
{:else}
{#if files.length === 0}
업로드된 파일이 없어요.
- {:else if filesWithDate.length === 0}
- 파일 목록을 불러오고 있어요.
{:else}
사진 또는 동영상이 없어요.
{/if}
diff --git a/src/lib/indexedDB/filesystem.ts b/src/lib/indexedDB/filesystem.ts
index cf60b93..7be44c7 100644
--- a/src/lib/indexedDB/filesystem.ts
+++ b/src/lib/indexedDB/filesystem.ts
@@ -1,7 +1,5 @@
import { Dexie, type EntityTable } from "dexie";
-export type DirectoryId = "root" | number;
-
interface DirectoryInfo {
id: number;
parentId: DirectoryId;
@@ -18,8 +16,6 @@ interface FileInfo {
categoryIds: number[];
}
-export type CategoryId = "root" | number;
-
interface CategoryInfo {
id: number;
parentId: CategoryId;
@@ -78,6 +74,10 @@ export const getFileInfo = async (id: number) => {
return await filesystem.file.get(id);
};
+export const bulkGetFileInfos = async (ids: number[]) => {
+ return await filesystem.file.bulkGet(ids);
+};
+
export const storeFileInfo = async (fileInfo: FileInfo) => {
await filesystem.file.put(fileInfo);
};
diff --git a/src/lib/modules/file/download.svelte.ts b/src/lib/modules/file/download.svelte.ts
new file mode 100644
index 0000000..bea8316
--- /dev/null
+++ b/src/lib/modules/file/download.svelte.ts
@@ -0,0 +1,95 @@
+import axios from "axios";
+import { limitFunction } from "p-limit";
+import { decryptData } from "$lib/modules/crypto";
+
+export interface FileDownloadState {
+ id: number;
+ status:
+ | "download-pending"
+ | "downloading"
+ | "decryption-pending"
+ | "decrypting"
+ | "decrypted"
+ | "canceled"
+ | "error";
+ progress?: number;
+ rate?: number;
+ estimated?: number;
+ result?: ArrayBuffer;
+}
+
+type LiveFileDownloadState = FileDownloadState & {
+ status: "download-pending" | "downloading" | "decryption-pending" | "decrypting";
+};
+
+let downloadingFiles: FileDownloadState[] = $state([]);
+
+export const isFileDownloading = (
+ status: FileDownloadState["status"],
+): status is LiveFileDownloadState["status"] =>
+ ["download-pending", "downloading", "decryption-pending", "decrypting"].includes(status);
+
+export const getFileDownloadState = (fileId: number) => {
+ return downloadingFiles.find((file) => file.id === fileId && isFileDownloading(file.status));
+};
+
+export const getDownloadingFiles = () => {
+ return downloadingFiles.filter((file) => isFileDownloading(file.status));
+};
+
+export const clearDownloadedFiles = () => {
+ downloadingFiles = downloadingFiles.filter((file) => isFileDownloading(file.status));
+};
+
+const requestFileDownload = limitFunction(
+ async (state: FileDownloadState, id: number) => {
+ state.status = "downloading";
+
+ const res = await axios.get(`/api/file/${id}/download`, {
+ responseType: "arraybuffer",
+ onDownloadProgress: ({ progress, rate, estimated }) => {
+ state.progress = progress;
+ state.rate = rate;
+ state.estimated = estimated;
+ },
+ });
+ const fileEncrypted: ArrayBuffer = res.data;
+
+ state.status = "decryption-pending";
+ return fileEncrypted;
+ },
+ { concurrency: 1 },
+);
+
+const decryptFile = limitFunction(
+ async (
+ state: FileDownloadState,
+ fileEncrypted: ArrayBuffer,
+ fileEncryptedIv: string,
+ dataKey: CryptoKey,
+ ) => {
+ state.status = "decrypting";
+
+ const fileBuffer = await decryptData(fileEncrypted, fileEncryptedIv, dataKey);
+
+ state.status = "decrypted";
+ state.result = fileBuffer;
+ return fileBuffer;
+ },
+ { concurrency: 4 },
+);
+
+export const downloadFile = async (id: number, fileEncryptedIv: string, dataKey: CryptoKey) => {
+ downloadingFiles.push({
+ id,
+ status: "download-pending",
+ });
+ const state = downloadingFiles.at(-1)!;
+
+ try {
+ return await decryptFile(state, await requestFileDownload(state, id), fileEncryptedIv, dataKey);
+ } catch (e) {
+ state.status = "error";
+ throw e;
+ }
+};
diff --git a/src/lib/modules/file/download.ts b/src/lib/modules/file/download.ts
deleted file mode 100644
index b0efb30..0000000
--- a/src/lib/modules/file/download.ts
+++ /dev/null
@@ -1,84 +0,0 @@
-import axios from "axios";
-import { limitFunction } from "p-limit";
-import { writable, type Writable } from "svelte/store";
-import { decryptData } from "$lib/modules/crypto";
-import { fileDownloadStatusStore, type FileDownloadStatus } from "$lib/stores";
-
-const requestFileDownload = limitFunction(
- async (status: Writable
- {#if $status.status === "download-pending"}
+ {#if state.status === "download-pending"}
다운로드를 기다리는 중
- {:else if $status.status === "downloading"}
+ {:else if state.status === "downloading"}
다운로드하는 중
- {:else if $status.status === "decryption-pending"}
+ {:else if state.status === "decryption-pending"}
복호화를 기다리는 중
- {:else if $status.status === "decrypting"}
+ {:else if state.status === "decrypting"}
복호화하는 중
{/if}
- {#if $status.status === "downloading"}
+ {#if state.status === "downloading"}
전송됨
- {Math.floor(($status.progress ?? 0) * 100)}% · {formatNetworkSpeed(($status.rate ?? 0) * 8)}
+ {Math.floor((state.progress ?? 0) * 100)}% · {formatNetworkSpeed((state.rate ?? 0) * 8)}
{/if}
- {$fileInfo.name}
-
- {#if $status.status === "download-pending"}
- 다운로드를 기다리는 중
- {:else if $status.status === "downloading"}
- 전송됨
- {Math.floor(($status.progress ?? 0) * 100)}% ·
- {formatNetworkSpeed(($status.rate ?? 0) * 8)}
- {:else if $status.status === "decryption-pending"}
- 복호화를 기다리는 중
- {:else if $status.status === "decrypting"}
- 복호화하는 중
- {:else if $status.status === "decrypted"}
- 다운로드 완료
- {:else if $status.status === "error"}
- 다운로드 실패
- {/if}
-
+ {info.name}
+
+ {#if state.status === "download-pending"}
+ 다운로드를 기다리는 중
+ {:else if state.status === "downloading"}
+ 전송됨
+ {Math.floor((state.progress ?? 0) * 100)}% ·
+ {formatNetworkSpeed((state.rate ?? 0) * 8)}
+ {:else if state.status === "decryption-pending"}
+ 복호화를 기다리는 중
+ {:else if state.status === "decrypting"}
+ 복호화하는 중
+ {:else if state.status === "decrypted"}
+ 다운로드 완료
+ {:else if state.status === "error"}
+ 다운로드 실패
+ {/if}
+
- {$status.name}
+
+ {state.name}
- {#if $status.status === "encryption-pending"}
+ {#if state.status === "encryption-pending"}
준비 중
- {:else if $status.status === "encrypting"}
+ {:else if state.status === "encrypting"}
암호화하는 중
- {:else if $status.status === "upload-pending"}
+ {:else if state.status === "upload-pending"}
업로드를 기다리는 중
- {:else if $status.status === "uploading"}
+ {:else if state.status === "uploading"}
전송됨
- {Math.floor(($status.progress ?? 0) * 100)}% · {formatNetworkSpeed(($status.rate ?? 0) * 8)}
- {:else if $status.status === "uploaded"}
+ {Math.floor((state.progress ?? 0) * 100)}% · {formatNetworkSpeed((state.rate ?? 0) * 8)}
+ {:else if state.status === "uploaded"}
업로드 완료
- {:else if $status.status === "error"}
+ {:else if state.status === "error"}
업로드 실패
{/if}
캐시를 삭제하더라도 원본 파일은 삭제되지 않아요. {$info.name} {info.name} 삭제된 파일 폴더가 비어 있어요.
- {$status.name}
-
- {#if $status.status === "encryption-pending"}
- 준비 중
- {:else if $status.status === "encrypting"}
- 암호화하는 중
- {:else if $status.status === "upload-pending"}
- 업로드를 기다리는 중
- {:else if $status.status === "uploading"}
- 전송됨 {Math.floor(($status.progress ?? 0) * 100)}% ·
- {formatNetworkSpeed(($status.rate ?? 0) * 8)}
- {/if}
-
+ {state.name}
+
+ {#if state.status === "encryption-pending"}
+ 준비 중
+ {:else if state.status === "encrypting"}
+ 암호화하는 중
+ {:else if state.status === "upload-pending"}
+ 업로드를 기다리는 중
+ {:else if state.status === "uploading"}
+ 전송됨 {Math.floor((state.progress ?? 0) * 100)}% ·
+ {formatNetworkSpeed((state.rate ?? 0) * 8)}
+ {/if}
+