diff --git a/src/lib/components/atoms/buttons/FileThumbnailButton.svelte b/src/lib/components/atoms/buttons/FileThumbnailButton.svelte
index c18101c..c71ff6b 100644
--- a/src/lib/components/atoms/buttons/FileThumbnailButton.svelte
+++ b/src/lib/components/atoms/buttons/FileThumbnailButton.svelte
@@ -1,42 +1,34 @@
-{#if $info}
-
diff --git a/src/lib/components/organisms/Gallery.svelte b/src/lib/components/organisms/Gallery.svelte
index 1fcb4ff..fb96775 100644
--- a/src/lib/components/organisms/Gallery.svelte
+++ b/src/lib/components/organisms/Gallery.svelte
@@ -1,98 +1,48 @@
@@ -101,8 +51,8 @@
itemHeight={(index) =>
rows[index]!.type === "header"
? 28
- : Math.ceil(rows[index]!.items.length / 4) * 181 +
- (Math.ceil(rows[index]!.items.length / 4) - 1) * 4 +
+ : Math.ceil(rows[index]!.files.length / 4) * 181 +
+ (Math.ceil(rows[index]!.files.length / 4) - 1) * 4 +
16}
class="flex flex-grow flex-col"
>
@@ -112,8 +62,8 @@
{row.label}
{:else}
- {#each row.items as { info }}
-
+ {#each row.files as file}
+
{/each}
{/if}
@@ -123,8 +73,6 @@
{#if files.length === 0}
업로드된 파일이 없어요.
- {:else if filesWithDate.length === 0}
- 파일 목록을 불러오고 있어요.
{:else}
사진 또는 동영상이 없어요.
{/if}
diff --git a/src/lib/modules/file/download.svelte.ts b/src/lib/modules/file/download.svelte.ts
new file mode 100644
index 0000000..4c53ed0
--- /dev/null
+++ b/src/lib/modules/file/download.svelte.ts
@@ -0,0 +1,97 @@
+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;
+}
+
+export 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): file is LiveFileDownloadState =>
+ isFileDownloading(file.status),
+ );
+};
+
+export const clearDownloadedFiles = () => {
+ downloadingFiles = downloadingFiles.filter((file) => isFileDownloading(file.status));
+};
+
+const requestFileDownload = limitFunction(
+ async (state: FileDownloadState, id: number) => {
+ state.status = "download-pending";
+
+ 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, id: number) => {
- status.update((value) => {
- value.status = "downloading";
- return value;
- });
-
- const res = await axios.get(`/api/file/${id}/download`, {
- responseType: "arraybuffer",
- onDownloadProgress: ({ progress, rate, estimated }) => {
- status.update((value) => {
- value.progress = progress;
- value.rate = rate;
- value.estimated = estimated;
- return value;
- });
- },
- });
- const fileEncrypted: ArrayBuffer = res.data;
-
- status.update((value) => {
- value.status = "decryption-pending";
- return value;
- });
- return fileEncrypted;
- },
- { concurrency: 1 },
-);
-
-const decryptFile = limitFunction(
- async (
- status: Writable,
- fileEncrypted: ArrayBuffer,
- fileEncryptedIv: string,
- dataKey: CryptoKey,
- ) => {
- status.update((value) => {
- value.status = "decrypting";
- return value;
- });
-
- const fileBuffer = await decryptData(fileEncrypted, fileEncryptedIv, dataKey);
-
- status.update((value) => {
- value.status = "decrypted";
- value.result = fileBuffer;
- return value;
- });
- return fileBuffer;
- },
- { concurrency: 4 },
-);
-
-export const downloadFile = async (id: number, fileEncryptedIv: string, dataKey: CryptoKey) => {
- const status = writable({
- id,
- status: "download-pending",
- });
- fileDownloadStatusStore.update((value) => {
- value.push(status);
- return value;
- });
-
- try {
- return await decryptFile(
- status,
- await requestFileDownload(status, id),
- fileEncryptedIv,
- dataKey,
- );
- } catch (e) {
- status.update((value) => {
- value.status = "error";
- return value;
- });
- throw e;
- }
-};
diff --git a/src/lib/modules/file/index.ts b/src/lib/modules/file/index.ts
index 3b99989..871d299 100644
--- a/src/lib/modules/file/index.ts
+++ b/src/lib/modules/file/index.ts
@@ -1,3 +1,3 @@
export * from "./cache";
-export * from "./download";
+export * from "./download.svelte";
export * from "./upload.svelte";
diff --git a/src/lib/modules/filesystem.ts b/src/lib/modules/filesystem.ts
deleted file mode 100644
index 5020793..0000000
--- a/src/lib/modules/filesystem.ts
+++ /dev/null
@@ -1,106 +0,0 @@
-import { get, writable, type Writable } from "svelte/store";
-import {
- getFileInfo as getFileInfoFromIndexedDB,
- storeFileInfo,
- deleteFileInfo,
-} from "$lib/indexedDB";
-import { unwrapDataKey, decryptString } from "$lib/modules/crypto";
-import { trpc, isTRPCClientError } from "$trpc/client";
-
-export interface FileInfo {
- id: number;
- parentId: DirectoryId;
- dataKey?: CryptoKey;
- dataKeyVersion?: Date;
- contentType: string;
- contentIv?: string;
- name: string;
- createdAt?: Date;
- lastModifiedAt: Date;
- categoryIds: number[];
-}
-
-const fileInfoStore = new Map>();
-
-const fetchFileInfoFromIndexedDB = async (id: number, info: Writable) => {
- if (get(info)) return;
-
- const file = await getFileInfoFromIndexedDB(id);
- if (!file) return;
-
- info.set(file);
-};
-
-const decryptDate = async (ciphertext: string, iv: string, dataKey: CryptoKey) => {
- return new Date(parseInt(await decryptString(ciphertext, iv, dataKey), 10));
-};
-
-const fetchFileInfoFromServer = async (
- id: number,
- info: Writable,
- masterKey: CryptoKey,
-) => {
- let metadata;
- try {
- metadata = await trpc().file.get.query({ id });
- } catch (e) {
- if (isTRPCClientError(e) && e.data?.code === "NOT_FOUND") {
- info.set(null);
- await deleteFileInfo(id);
- return;
- }
- throw new Error("Failed to fetch file information");
- }
- const { dataKey } = await unwrapDataKey(metadata.dek, masterKey);
-
- const name = await decryptString(metadata.name, metadata.nameIv, dataKey);
- const createdAt =
- metadata.createdAt && metadata.createdAtIv
- ? await decryptDate(metadata.createdAt, metadata.createdAtIv, dataKey)
- : undefined;
- const lastModifiedAt = await decryptDate(
- metadata.lastModifiedAt,
- metadata.lastModifiedAtIv,
- dataKey,
- );
-
- info.set({
- id,
- parentId: metadata.parent,
- dataKey,
- dataKeyVersion: new Date(metadata.dekVersion),
- contentType: metadata.contentType,
- contentIv: metadata.contentIv,
- name,
- createdAt,
- lastModifiedAt,
- categoryIds: metadata.categories,
- });
- await storeFileInfo({
- id,
- parentId: metadata.parent,
- name,
- contentType: metadata.contentType,
- createdAt,
- lastModifiedAt,
- categoryIds: metadata.categories,
- });
-};
-
-const fetchFileInfo = async (id: number, info: Writable, masterKey: CryptoKey) => {
- await fetchFileInfoFromIndexedDB(id, info);
- await fetchFileInfoFromServer(id, info, masterKey);
-};
-
-export const getFileInfo = (fileId: number, masterKey: CryptoKey) => {
- // TODO: MEK rotation
-
- let info = fileInfoStore.get(fileId);
- if (!info) {
- info = writable(null);
- fileInfoStore.set(fileId, info);
- }
-
- fetchFileInfo(fileId, info, masterKey); // Intended
- return info;
-};
diff --git a/src/lib/modules/filesystem2.svelte.ts b/src/lib/modules/filesystem2.svelte.ts
index 01514cb..eeb3766 100644
--- a/src/lib/modules/filesystem2.svelte.ts
+++ b/src/lib/modules/filesystem2.svelte.ts
@@ -41,12 +41,12 @@ interface RootDirectoryInfo {
export type DirectoryInfo = LocalDirectoryInfo | RootDirectoryInfo;
export type SubDirectoryInfo = Omit;
-interface FileInfo {
+export interface FileInfo {
id: number;
parentId: DirectoryId;
dataKey?: DataKey;
contentType: string;
- contentIv: string | undefined;
+ contentIv?: string;
name: string;
createdAt?: Date;
lastModifiedAt: Date;
@@ -81,6 +81,7 @@ export type SubCategoryInfo = Omit<
>;
const directoryInfoCache = new Map>();
+const fileInfoCache = new Map>();
const categoryInfoCache = new Map>();
export const getDirectoryInfo = async (id: DirectoryId, masterKey: CryptoKey) => {
@@ -197,6 +198,100 @@ const decryptDate = async (ciphertext: string, iv: string, dataKey: CryptoKey) =
return new Date(parseInt(await decryptString(ciphertext, iv, dataKey), 10));
};
+export const getFileInfo = async (id: number, masterKey: CryptoKey) => {
+ const info = fileInfoCache.get(id);
+ if (info instanceof Promise) {
+ return info;
+ }
+
+ const { promise, resolve } = Promise.withResolvers();
+ if (!info) {
+ fileInfoCache.set(id, promise);
+ }
+
+ monotonicResolve(
+ [!info && fetchFileInfoFromIndexedDB(id), fetchFileInfoFromServer(id, masterKey)],
+ (fileInfo) => {
+ let info = fileInfoCache.get(id);
+ if (info instanceof Promise) {
+ const state = $state(fileInfo);
+ fileInfoCache.set(id, state);
+ resolve(state);
+ } else {
+ Object.assign(info!, fileInfo);
+ resolve(info!);
+ }
+ },
+ );
+ return info ?? promise;
+};
+
+const fetchFileInfoFromIndexedDB = async (id: number): Promise => {
+ const file = await getFileInfoFromIndexedDB(id);
+ const categories = await Promise.all(
+ file?.categoryIds.map(async (categoryId) => {
+ const categoryInfo = await getCategoryInfoFromIndexedDB(categoryId);
+ return categoryInfo ? { id: categoryId, name: categoryInfo.name } : undefined;
+ }) ?? [],
+ );
+
+ if (file) {
+ return {
+ id,
+ parentId: file.parentId,
+ contentType: file.contentType,
+ name: file.name,
+ createdAt: file.createdAt,
+ lastModifiedAt: file.lastModifiedAt,
+ categories: categories.filter((category) => !!category),
+ };
+ }
+};
+
+const fetchFileInfoFromServer = async (
+ id: number,
+ masterKey: CryptoKey,
+): Promise => {
+ try {
+ const { categories: categoriesRaw, ...metadata } = await trpc().file.get.query({ id });
+ const categories = await Promise.all(
+ categoriesRaw.map(async (category) => {
+ const { dataKey } = await unwrapDataKey(category.dek, masterKey);
+ const name = await decryptString(category.name, category.nameIv, dataKey);
+ return { id: category.id, name };
+ }),
+ );
+
+ const { dataKey } = await unwrapDataKey(metadata.dek, masterKey);
+ const [name, createdAt, lastModifiedAt] = await Promise.all([
+ decryptString(metadata.name, metadata.nameIv, dataKey),
+ metadata.createdAt
+ ? decryptDate(metadata.createdAt, metadata.createdAtIv!, dataKey)
+ : undefined,
+ decryptDate(metadata.lastModifiedAt, metadata.lastModifiedAtIv, dataKey),
+ ]);
+
+ return {
+ id,
+ parentId: metadata.parent,
+ dataKey: { key: dataKey, version: new Date(metadata.dekVersion) },
+ contentType: metadata.contentType,
+ contentIv: metadata.contentIv,
+ name,
+ createdAt,
+ lastModifiedAt,
+ categories,
+ };
+ } catch (e) {
+ if (isTRPCClientError(e) && e.data?.code === "NOT_FOUND") {
+ fileInfoCache.delete(id);
+ await deleteFileInfo(id);
+ return;
+ }
+ throw new Error("Failed to fetch file information");
+ }
+};
+
export const getCategoryInfo = async (id: CategoryId, masterKey: CryptoKey) => {
const info = categoryInfoCache.get(id);
if (info instanceof Promise) {
diff --git a/src/lib/server/db/file.ts b/src/lib/server/db/file.ts
index 7bea6db..45ae0f4 100644
--- a/src/lib/server/db/file.ts
+++ b/src/lib/server/db/file.ts
@@ -1,4 +1,4 @@
-import { sql, type NotNull } from "kysely";
+import { sql } from "kysely";
import pg from "pg";
import { IntegrityError } from "./error";
import db from "./kysely";
@@ -486,10 +486,17 @@ export const addFileToCategory = async (fileId: number, categoryId: number) => {
export const getAllFileCategories = async (fileId: number) => {
const categories = await db
.selectFrom("file_category")
- .select("category_id")
+ .innerJoin("category", "file_category.category_id", "category.id")
+ .selectAll("category")
.where("file_id", "=", fileId)
.execute();
- return categories.map(({ category_id }) => ({ id: category_id }));
+ return categories.map((category) => ({
+ id: category.id,
+ mekVersion: category.master_encryption_key_version,
+ encDek: category.encrypted_data_encryption_key,
+ dekVersion: category.data_encryption_key_version,
+ encName: category.encrypted_name,
+ }));
};
export const removeFileFromCategory = async (fileId: number, categoryId: number) => {
diff --git a/src/lib/stores/file.ts b/src/lib/stores/file.ts
deleted file mode 100644
index 0aab6d1..0000000
--- a/src/lib/stores/file.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { writable, type Writable } from "svelte/store";
-
-export interface FileDownloadStatus {
- id: number;
- status:
- | "download-pending"
- | "downloading"
- | "decryption-pending"
- | "decrypting"
- | "decrypted"
- | "canceled"
- | "error";
- progress?: number;
- rate?: number;
- estimated?: number;
- result?: ArrayBuffer;
-}
-
-export const fileDownloadStatusStore = writable[]>([]);
-
-export const isFileDownloading = (
- status: FileDownloadStatus["status"],
-): status is "download-pending" | "downloading" | "decryption-pending" | "decrypting" => {
- return ["download-pending", "downloading", "decryption-pending", "decrypting"].includes(status);
-};
diff --git a/src/lib/stores/index.ts b/src/lib/stores/index.ts
index 537209a..668f46f 100644
--- a/src/lib/stores/index.ts
+++ b/src/lib/stores/index.ts
@@ -1,2 +1 @@
-export * from "./file";
export * from "./key";
diff --git a/src/routes/(fullscreen)/file/[id]/+page.svelte b/src/routes/(fullscreen)/file/[id]/+page.svelte
index ab85dc7..d5ba57c 100644
--- a/src/routes/(fullscreen)/file/[id]/+page.svelte
+++ b/src/routes/(fullscreen)/file/[id]/+page.svelte
@@ -1,14 +1,14 @@
-{#if $status && isFileDownloading($status.status)}
+{#if isFileDownloading(state.status)}
- {#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}
diff --git a/src/routes/(fullscreen)/file/downloads/+page.svelte b/src/routes/(fullscreen)/file/downloads/+page.svelte
index e1bad0e..f9bfa84 100644
--- a/src/routes/(fullscreen)/file/downloads/+page.svelte
+++ b/src/routes/(fullscreen)/file/downloads/+page.svelte
@@ -1,19 +1,21 @@
@@ -22,9 +24,9 @@
-
- {#each downloadingFiles as status}
-
+ {#await downloadingFilesPromise then downloadingFiles}
+ {#each downloadingFiles as { state, fileInfo }}
+
{/each}
-
+ {/await}
diff --git a/src/routes/(fullscreen)/file/downloads/File.svelte b/src/routes/(fullscreen)/file/downloads/File.svelte
index 3bfe292..6a2a0ca 100644
--- a/src/routes/(fullscreen)/file/downloads/File.svelte
+++ b/src/routes/(fullscreen)/file/downloads/File.svelte
@@ -1,7 +1,6 @@
-{#if $fileInfo}
-
-
- {#if $status.status === "download-pending"}
-
- {:else if $status.status === "downloading"}
-
- {:else if $status.status === "decryption-pending"}
-
- {:else if $status.status === "decrypting"}
-
- {:else if $status.status === "decrypted"}
-
- {:else if $status.status === "error"}
-
- {/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}
-
-
+
+
+ {#if state.status === "download-pending"}
+
+ {:else if state.status === "downloading"}
+
+ {:else if state.status === "decryption-pending"}
+
+ {:else if state.status === "decrypting"}
+
+ {:else if state.status === "decrypted"}
+
+ {:else if state.status === "error"}
+
+ {/if}
-{/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}
+
+
+
diff --git a/src/routes/(fullscreen)/gallery/+page.svelte b/src/routes/(fullscreen)/gallery/+page.svelte
index b6f8239..ab73c70 100644
--- a/src/routes/(fullscreen)/gallery/+page.svelte
+++ b/src/routes/(fullscreen)/gallery/+page.svelte
@@ -1,18 +1,20 @@
@@ -22,5 +24,8 @@
- goto(`/file/${id}?from=gallery`)} />
+ !!file)}
+ 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 cf8192d..271ae96 100644
--- a/src/routes/(fullscreen)/settings/cache/+page.svelte
+++ b/src/routes/(fullscreen)/settings/cache/+page.svelte
@@ -1,18 +1,17 @@
- {#if $info}
+ {#if info}
@@ -28,8 +27,8 @@
{/if}
- {#if $info}
-
{$info.name}
+ {#if info}
+
{info.name}
{:else}
삭제된 파일
{/if}
diff --git a/src/routes/(fullscreen)/settings/thumbnail/+page.svelte b/src/routes/(fullscreen)/settings/thumbnail/+page.svelte
index d9cd692..8830133 100644
--- a/src/routes/(fullscreen)/settings/thumbnail/+page.svelte
+++ b/src/routes/(fullscreen)/settings/thumbnail/+page.svelte
@@ -5,7 +5,7 @@
import { BottomDiv, Button, FullscreenDiv } from "$lib/components/atoms";
import { IconEntryButton, TopBar } from "$lib/components/molecules";
import { deleteAllFileThumbnailCaches } from "$lib/modules/file";
- import { getFileInfo } from "$lib/modules/filesystem";
+ import { getFileInfo } from "$lib/modules/filesystem2.svelte";
import { masterKeyStore } from "$lib/stores";
import File from "./File.svelte";
import {
@@ -20,19 +20,20 @@
const generateAllThumbnails = () => {
persistentStates.files.forEach(({ info }) => {
- const fileInfo = get(info);
- if (fileInfo) {
- requestThumbnailGeneration(fileInfo);
+ if (info) {
+ requestThumbnailGeneration(info);
}
});
};
- onMount(() => {
- persistentStates.files = data.files.map((fileId) => ({
- id: fileId,
- info: getFileInfo(fileId, $masterKeyStore?.get(1)?.key!),
- status: getGenerationStatus(fileId),
- }));
+ onMount(async () => {
+ persistentStates.files = await Promise.all(
+ data.files.map(async (fileId) => ({
+ id: fileId,
+ info: await getFileInfo(fileId, $masterKeyStore?.get(1)?.key!),
+ status: getGenerationStatus(fileId),
+ })),
+ );
});
diff --git a/src/routes/(fullscreen)/settings/thumbnail/File.svelte b/src/routes/(fullscreen)/settings/thumbnail/File.svelte
index 93c23ad..e5699e2 100644
--- a/src/routes/(fullscreen)/settings/thumbnail/File.svelte
+++ b/src/routes/(fullscreen)/settings/thumbnail/File.svelte
@@ -13,34 +13,32 @@
import type { Writable } from "svelte/store";
import { ActionEntryButton } from "$lib/components/atoms";
import { DirectoryEntryLabel } from "$lib/components/molecules";
- import type { FileInfo } from "$lib/modules/filesystem";
+ import type { FileInfo } from "$lib/modules/filesystem2.svelte";
import { formatDateTime } from "$lib/utils";
import type { GenerationStatus } from "./service.svelte";
import IconCamera from "~icons/material-symbols/camera";
interface Props {
- info: Writable
;
- onclick: (selectedFile: FileInfo) => void;
- onGenerateThumbnailClick: (selectedFile: FileInfo) => void;
+ info: FileInfo;
+ onclick: (file: FileInfo) => void;
+ onGenerateThumbnailClick: (file: FileInfo) => void;
generationStatus?: Writable;
}
let { info, onclick, onGenerateThumbnailClick, generationStatus }: Props = $props();
-{#if $info}
- onclick($info)}
- actionButtonIcon={!$generationStatus || $generationStatus === "error" ? IconCamera : undefined}
- onActionButtonClick={() => onGenerateThumbnailClick($info)}
- actionButtonClass="text-gray-800"
- >
- {@const subtext =
- $generationStatus && $generationStatus !== "uploaded"
- ? subtexts[$generationStatus]
- : formatDateTime($info.createdAt ?? $info.lastModifiedAt)}
-
-
-{/if}
+ onclick(info)}
+ actionButtonIcon={!$generationStatus || $generationStatus === "error" ? IconCamera : undefined}
+ onActionButtonClick={() => onGenerateThumbnailClick(info)}
+ actionButtonClass="text-gray-800"
+>
+ {@const subtext =
+ $generationStatus && $generationStatus !== "uploaded"
+ ? subtexts[$generationStatus]
+ : formatDateTime(info.createdAt ?? info.lastModifiedAt)}
+
+
diff --git a/src/routes/(fullscreen)/settings/thumbnail/service.svelte.ts b/src/routes/(fullscreen)/settings/thumbnail/service.svelte.ts
index d8f288c..6b0acb2 100644
--- a/src/routes/(fullscreen)/settings/thumbnail/service.svelte.ts
+++ b/src/routes/(fullscreen)/settings/thumbnail/service.svelte.ts
@@ -2,7 +2,7 @@ import { limitFunction } from "p-limit";
import { get, writable, type Writable } from "svelte/store";
import { encryptData } from "$lib/modules/crypto";
import { storeFileThumbnailCache } from "$lib/modules/file";
-import type { FileInfo } from "$lib/modules/filesystem";
+import type { FileInfo } from "$lib/modules/filesystem2.svelte";
import { generateThumbnail as doGenerateThumbnail } from "$lib/modules/thumbnail";
import { requestFileDownload, requestFileThumbnailUpload } from "$lib/services/file";
@@ -17,7 +17,7 @@ export type GenerationStatus =
interface File {
id: number;
- info: Writable;
+ info: FileInfo;
status?: Writable;
}
@@ -129,7 +129,11 @@ export const requestThumbnailGeneration = async (fileInfo: FileInfo) => {
let fileSize = 0;
try {
- const file = await requestFileDownload(fileInfo.id, fileInfo.contentIv!, fileInfo.dataKey!);
+ const file = await requestFileDownload(
+ fileInfo.id,
+ fileInfo.contentIv!,
+ fileInfo.dataKey?.key!,
+ );
fileSize = file.byteLength;
memoryUsage += fileSize;
@@ -141,11 +145,11 @@ export const requestThumbnailGeneration = async (fileInfo: FileInfo) => {
status,
file,
fileInfo.contentType,
- fileInfo.dataKey!,
+ fileInfo.dataKey?.key!,
);
if (
!thumbnail ||
- !(await requestThumbnailUpload(status, fileInfo.id, fileInfo.dataKeyVersion!, thumbnail))
+ !(await requestThumbnailUpload(status, fileInfo.id, fileInfo.dataKey?.version!, thumbnail))
) {
status.set("error");
}
diff --git a/src/routes/(main)/directory/[[id]]/DownloadStatusCard.svelte b/src/routes/(main)/directory/[[id]]/DownloadStatusCard.svelte
index 18bb159..590cb8f 100644
--- a/src/routes/(main)/directory/[[id]]/DownloadStatusCard.svelte
+++ b/src/routes/(main)/directory/[[id]]/DownloadStatusCard.svelte
@@ -1,7 +1,5 @@
{#if downloadingFiles.length > 0}
diff --git a/src/routes/(main)/home/+page.svelte b/src/routes/(main)/home/+page.svelte
index 0ace1ab..21c3695 100644
--- a/src/routes/(main)/home/+page.svelte
+++ b/src/routes/(main)/home/+page.svelte
@@ -1,17 +1,18 @@
@@ -28,7 +29,9 @@
{#if mediaFiles.length > 0}
{#each mediaFiles as file}
- goto(`/file/${id}`)} />
+ {#if file}
+ goto(`/file/${id}`)} />
+ {/if}
{/each}
{/if}
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index 612cfe4..9aadffd 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -1,23 +1,14 @@