mirror of
https://github.com/kmc7468/arkvault.git
synced 2026-02-03 23:56:53 +00:00
파일 및 디렉터리 메타데이터 복호화 로직 리팩토링
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import * as IndexedDB from "$lib/indexedDB";
|
||||
import { trpc, isTRPCClientError } from "$trpc/client";
|
||||
import { decryptFileMetadata, decryptCategoryMetadata } from "./common";
|
||||
import { FilesystemCache } from "./FilesystemCache.svelte";
|
||||
import { FilesystemCache, decryptFileMetadata, decryptCategoryMetadata } from "./internal.svelte";
|
||||
import type { CategoryInfo, MaybeCategoryInfo } from "./types";
|
||||
|
||||
const cache = new FilesystemCache<CategoryId, MaybeCategoryInfo>({
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import { unwrapDataKey, decryptString } from "$lib/modules/crypto";
|
||||
|
||||
export const decryptDirectoryMetadata = async (
|
||||
metadata: { dek: string; dekVersion: Date; name: string; nameIv: string },
|
||||
masterKey: CryptoKey,
|
||||
) => {
|
||||
const { dataKey } = await unwrapDataKey(metadata.dek, masterKey);
|
||||
const name = await decryptString(metadata.name, metadata.nameIv, dataKey);
|
||||
|
||||
return {
|
||||
dataKey: { key: dataKey, version: metadata.dekVersion },
|
||||
name,
|
||||
};
|
||||
};
|
||||
|
||||
const decryptDate = async (ciphertext: string, iv: string, dataKey: CryptoKey) => {
|
||||
return new Date(parseInt(await decryptString(ciphertext, iv, dataKey), 10));
|
||||
};
|
||||
|
||||
export const decryptFileMetadata = async (
|
||||
metadata: {
|
||||
dek: string;
|
||||
dekVersion: Date;
|
||||
name: string;
|
||||
nameIv: string;
|
||||
createdAt?: string;
|
||||
createdAtIv?: string;
|
||||
lastModifiedAt: string;
|
||||
lastModifiedAtIv: string;
|
||||
},
|
||||
masterKey: CryptoKey,
|
||||
) => {
|
||||
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 {
|
||||
dataKey: { key: dataKey, version: metadata.dekVersion },
|
||||
name,
|
||||
createdAt,
|
||||
lastModifiedAt,
|
||||
};
|
||||
};
|
||||
|
||||
export const decryptCategoryMetadata = decryptDirectoryMetadata;
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as IndexedDB from "$lib/indexedDB";
|
||||
import { trpc, isTRPCClientError } from "$trpc/client";
|
||||
import { decryptDirectoryMetadata, decryptFileMetadata } from "./common";
|
||||
import { FilesystemCache, type FilesystemCacheOptions } from "./FilesystemCache.svelte";
|
||||
import { FilesystemCache, decryptDirectoryMetadata, decryptFileMetadata } from "./internal.svelte";
|
||||
import type { DirectoryInfo, MaybeDirectoryInfo } from "./types";
|
||||
|
||||
const cache = new FilesystemCache<DirectoryId, MaybeDirectoryInfo>({
|
||||
@@ -106,8 +105,30 @@ export const getDirectoryInfo = (
|
||||
id: DirectoryId,
|
||||
masterKey: CryptoKey,
|
||||
options?: {
|
||||
fetchFromServer?: FilesystemCacheOptions<DirectoryId, MaybeDirectoryInfo>["fetchFromServer"];
|
||||
serverResponse?: {
|
||||
parent: DirectoryId;
|
||||
dek: string;
|
||||
dekVersion: Date;
|
||||
name: string;
|
||||
nameIv: string;
|
||||
isFavorite: boolean;
|
||||
};
|
||||
},
|
||||
) => {
|
||||
return cache.get(id, masterKey, options);
|
||||
return cache.get(id, masterKey, {
|
||||
fetchFromServer:
|
||||
options?.serverResponse &&
|
||||
(async (cachedValue) => {
|
||||
const metadata = await decryptDirectoryMetadata(options!.serverResponse!, masterKey);
|
||||
return storeToIndexedDB({
|
||||
subDirectories: [],
|
||||
files: [],
|
||||
...cachedValue,
|
||||
id: id as number,
|
||||
parentId: options!.serverResponse!.parent,
|
||||
isFavorite: options!.serverResponse!.isFavorite,
|
||||
...metadata,
|
||||
});
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as IndexedDB from "$lib/indexedDB";
|
||||
import { trpc, isTRPCClientError } from "$trpc/client";
|
||||
import { decryptFileMetadata, decryptCategoryMetadata } from "./common";
|
||||
import { FilesystemCache, type FilesystemCacheOptions } from "./FilesystemCache.svelte";
|
||||
import { FilesystemCache, decryptFileMetadata, decryptCategoryMetadata } from "./internal.svelte";
|
||||
import type { FileInfo, MaybeFileInfo } from "./types";
|
||||
|
||||
const cache = new FilesystemCache<number, MaybeFileInfo>({
|
||||
@@ -175,9 +174,38 @@ const bulkStoreToIndexedDB = (infos: FileInfo[]) => {
|
||||
export const getFileInfo = (
|
||||
id: number,
|
||||
masterKey: CryptoKey,
|
||||
options?: { fetchFromServer?: FilesystemCacheOptions<number, MaybeFileInfo>["fetchFromServer"] },
|
||||
options?: {
|
||||
serverResponse?: {
|
||||
parent: DirectoryId;
|
||||
dek: string;
|
||||
dekVersion: Date;
|
||||
contentType: string;
|
||||
name: string;
|
||||
nameIv: string;
|
||||
createdAt?: string;
|
||||
createdAtIv?: string;
|
||||
lastModifiedAt: string;
|
||||
lastModifiedAtIv: string;
|
||||
isFavorite: boolean;
|
||||
};
|
||||
},
|
||||
) => {
|
||||
return cache.get(id, masterKey, options);
|
||||
return cache.get(id, masterKey, {
|
||||
fetchFromServer:
|
||||
options?.serverResponse &&
|
||||
(async (cachedValue) => {
|
||||
const metadata = await decryptFileMetadata(options!.serverResponse!, masterKey);
|
||||
return storeToIndexedDB({
|
||||
categories: [],
|
||||
...cachedValue,
|
||||
id,
|
||||
parentId: options!.serverResponse!.parent,
|
||||
contentType: options!.serverResponse!.contentType,
|
||||
isFavorite: options!.serverResponse!.isFavorite,
|
||||
...metadata,
|
||||
});
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
export const bulkGetFileInfo = (ids: number[], masterKey: CryptoKey) => {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export * from "./category";
|
||||
export * from "./common";
|
||||
export * from "./directory";
|
||||
export * from "./file";
|
||||
export * from "./types";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { untrack } from "svelte";
|
||||
import { unwrapDataKey, decryptString } from "$lib/modules/crypto";
|
||||
|
||||
export interface FilesystemCacheOptions<K, V> {
|
||||
interface FilesystemCacheOptions<K, V> {
|
||||
fetchFromIndexedDB: (key: K) => Promise<V | undefined>;
|
||||
fetchFromServer: (key: K, cachedValue: V | undefined, masterKey: CryptoKey) => Promise<V>;
|
||||
bulkFetchFromIndexedDB?: (keys: Set<K>) => Promise<Map<K, V>>;
|
||||
@@ -18,7 +19,7 @@ export class FilesystemCache<K, V extends object> {
|
||||
get(
|
||||
key: K,
|
||||
masterKey: CryptoKey,
|
||||
options?: { fetchFromServer?: FilesystemCacheOptions<K, V>["fetchFromServer"] },
|
||||
options?: { fetchFromServer?: (cachedValue: V | undefined) => Promise<V> },
|
||||
) {
|
||||
return untrack(() => {
|
||||
let state = this.map.get(key);
|
||||
@@ -42,8 +43,10 @@ export class FilesystemCache<K, V extends object> {
|
||||
return loadedInfo;
|
||||
})
|
||||
)
|
||||
.then((cachedInfo) =>
|
||||
(options?.fetchFromServer ?? this.options.fetchFromServer)(key, cachedInfo, masterKey),
|
||||
.then(
|
||||
(cachedInfo) =>
|
||||
options?.fetchFromServer?.(cachedInfo) ??
|
||||
this.options.fetchFromServer(key, cachedInfo, masterKey),
|
||||
)
|
||||
.then((loadedInfo) => {
|
||||
if (state.value) {
|
||||
@@ -126,3 +129,52 @@ export class FilesystemCache<K, V extends object> {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const decryptDirectoryMetadata = async (
|
||||
metadata: { dek: string; dekVersion: Date; name: string; nameIv: string },
|
||||
masterKey: CryptoKey,
|
||||
) => {
|
||||
const { dataKey } = await unwrapDataKey(metadata.dek, masterKey);
|
||||
const name = await decryptString(metadata.name, metadata.nameIv, dataKey);
|
||||
|
||||
return {
|
||||
dataKey: { key: dataKey, version: metadata.dekVersion },
|
||||
name,
|
||||
};
|
||||
};
|
||||
|
||||
const decryptDate = async (ciphertext: string, iv: string, dataKey: CryptoKey) => {
|
||||
return new Date(parseInt(await decryptString(ciphertext, iv, dataKey), 10));
|
||||
};
|
||||
|
||||
export const decryptFileMetadata = async (
|
||||
metadata: {
|
||||
dek: string;
|
||||
dekVersion: Date;
|
||||
name: string;
|
||||
nameIv: string;
|
||||
createdAt?: string;
|
||||
createdAtIv?: string;
|
||||
lastModifiedAt: string;
|
||||
lastModifiedAtIv: string;
|
||||
},
|
||||
masterKey: CryptoKey,
|
||||
) => {
|
||||
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 {
|
||||
dataKey: { key: dataKey, version: metadata.dekVersion },
|
||||
name,
|
||||
createdAt,
|
||||
lastModifiedAt,
|
||||
};
|
||||
};
|
||||
|
||||
export const decryptCategoryMetadata = decryptDirectoryMetadata;
|
||||
@@ -1,6 +1,4 @@
|
||||
import {
|
||||
decryptDirectoryMetadata,
|
||||
decryptFileMetadata,
|
||||
getDirectoryInfo,
|
||||
getFileInfo,
|
||||
type LocalDirectoryInfo,
|
||||
@@ -30,49 +28,14 @@ export const requestSearch = async (filter: SearchFilter, masterKey: CryptoKey)
|
||||
.filter(({ type }) => type === "exclude")
|
||||
.map(({ info }) => info.id),
|
||||
});
|
||||
|
||||
const [directories, files] = await HybridPromise.all([
|
||||
HybridPromise.all(
|
||||
directoriesRaw.map((directory) =>
|
||||
HybridPromise.resolve(
|
||||
getDirectoryInfo(directory.id, masterKey, {
|
||||
async fetchFromServer(id, cachedInfo, masterKey) {
|
||||
const metadata = await decryptDirectoryMetadata(directory, masterKey);
|
||||
return {
|
||||
subDirectories: [],
|
||||
files: [],
|
||||
...cachedInfo,
|
||||
id: id as number,
|
||||
exists: true,
|
||||
parentId: directory.parent,
|
||||
...metadata,
|
||||
isFavorite: !!directory.isFavorite,
|
||||
};
|
||||
},
|
||||
}),
|
||||
),
|
||||
getDirectoryInfo(directory.id, masterKey, { serverResponse: directory }),
|
||||
),
|
||||
),
|
||||
HybridPromise.all(
|
||||
filesRaw.map((file) =>
|
||||
HybridPromise.resolve(
|
||||
getFileInfo(file.id, masterKey, {
|
||||
async fetchFromServer(id, cachedInfo, masterKey) {
|
||||
const metadata = await decryptFileMetadata(file, masterKey);
|
||||
return {
|
||||
categories: [],
|
||||
...cachedInfo,
|
||||
id: id as number,
|
||||
exists: true,
|
||||
parentId: file.parent,
|
||||
contentType: file.contentType,
|
||||
isFavorite: !!file.isFavorite,
|
||||
...metadata,
|
||||
};
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
filesRaw.map((file) => getFileInfo(file.id, masterKey, { serverResponse: file })),
|
||||
),
|
||||
]);
|
||||
return { directories, files } as SearchResult;
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import { limitFunction } from "p-limit";
|
||||
import { SvelteMap } from "svelte/reactivity";
|
||||
import { CHUNK_SIZE } from "$lib/constants";
|
||||
import {
|
||||
decryptFileMetadata,
|
||||
getFileInfo,
|
||||
type FileInfo,
|
||||
type MaybeFileInfo,
|
||||
} from "$lib/modules/filesystem";
|
||||
import { getFileInfo, type FileInfo } from "$lib/modules/filesystem";
|
||||
import { uploadBlob } from "$lib/modules/upload";
|
||||
import { requestFileDownload } from "$lib/services/file";
|
||||
import { HybridPromise, Scheduler } from "$lib/utils";
|
||||
@@ -35,26 +30,7 @@ export const requestLegacyFiles = async (
|
||||
masterKey: CryptoKey,
|
||||
) => {
|
||||
const files = await HybridPromise.all(
|
||||
filesRaw.map((file) =>
|
||||
HybridPromise.resolve(
|
||||
getFileInfo(file.id, masterKey, {
|
||||
async fetchFromServer(id, cachedInfo, masterKey) {
|
||||
const metadata = await decryptFileMetadata(file, masterKey);
|
||||
return {
|
||||
categories: [],
|
||||
...cachedInfo,
|
||||
id: id as number,
|
||||
exists: true,
|
||||
isLegacy: file.isLegacy,
|
||||
parentId: file.parent,
|
||||
contentType: file.contentType,
|
||||
isFavorite: file.isFavorite,
|
||||
...metadata,
|
||||
};
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
filesRaw.map((file) => getFileInfo(file.id, masterKey, { serverResponse: file })),
|
||||
);
|
||||
return files;
|
||||
};
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import { limitFunction } from "p-limit";
|
||||
import { SvelteMap } from "svelte/reactivity";
|
||||
import { storeFileThumbnailCache } from "$lib/modules/file";
|
||||
import {
|
||||
decryptFileMetadata,
|
||||
getFileInfo,
|
||||
type FileInfo,
|
||||
type MaybeFileInfo,
|
||||
} from "$lib/modules/filesystem";
|
||||
import { getFileInfo, type FileInfo } from "$lib/modules/filesystem";
|
||||
import { generateThumbnail } from "$lib/modules/thumbnail";
|
||||
import { requestFileDownload, requestFileThumbnailUpload } from "$lib/services/file";
|
||||
import { HybridPromise, Scheduler } from "$lib/utils";
|
||||
@@ -40,26 +35,7 @@ export const requestMissingThumbnailFiles = async (
|
||||
masterKey: CryptoKey,
|
||||
) => {
|
||||
const files = await HybridPromise.all(
|
||||
filesRaw.map((file) =>
|
||||
HybridPromise.resolve(
|
||||
getFileInfo(file.id, masterKey, {
|
||||
async fetchFromServer(id, cachedInfo, masterKey) {
|
||||
const metadata = await decryptFileMetadata(file, masterKey);
|
||||
return {
|
||||
categories: [],
|
||||
...cachedInfo,
|
||||
id: id as number,
|
||||
exists: true,
|
||||
isLegacy: file.isLegacy,
|
||||
parentId: file.parent,
|
||||
contentType: file.contentType,
|
||||
isFavorite: file.isFavorite,
|
||||
...metadata,
|
||||
};
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
filesRaw.map((file) => getFileInfo(file.id, masterKey, { serverResponse: file })),
|
||||
);
|
||||
return files;
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {
|
||||
decryptDirectoryMetadata,
|
||||
decryptFileMetadata,
|
||||
getDirectoryInfo,
|
||||
getFileInfo,
|
||||
type SummarizedFileInfo,
|
||||
type SubDirectoryInfo,
|
||||
type LocalDirectoryInfo,
|
||||
} from "$lib/modules/filesystem";
|
||||
import { HybridPromise, sortEntries } from "$lib/utils";
|
||||
import { trpc } from "$trpc/client";
|
||||
@@ -16,59 +16,41 @@ export type FavoriteEntry =
|
||||
export const requestFavoriteEntries = async (
|
||||
favorites: RouterOutputs["favorites"]["get"],
|
||||
masterKey: CryptoKey,
|
||||
): Promise<FavoriteEntry[]> => {
|
||||
const directories: FavoriteEntry[] = await Promise.all(
|
||||
favorites.directories.map(async (dir) => {
|
||||
const metadata = await decryptDirectoryMetadata(dir, masterKey);
|
||||
return {
|
||||
type: "directory" as const,
|
||||
name: metadata.name,
|
||||
details: {
|
||||
id: dir.id,
|
||||
parentId: dir.parent,
|
||||
isFavorite: true,
|
||||
dataKey: metadata.dataKey,
|
||||
name: metadata.name,
|
||||
} as SubDirectoryInfo,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const fileResults = await Promise.all(
|
||||
favorites.files.map(async (file) => {
|
||||
const result = await HybridPromise.resolve(
|
||||
getFileInfo(file.id, masterKey, {
|
||||
async fetchFromServer(id, cachedInfo) {
|
||||
const metadata = await decryptFileMetadata(file, masterKey);
|
||||
return {
|
||||
categories: [],
|
||||
...cachedInfo,
|
||||
id: id as number,
|
||||
exists: true,
|
||||
parentId: file.parent,
|
||||
contentType: file.contentType,
|
||||
isFavorite: true,
|
||||
...metadata,
|
||||
};
|
||||
},
|
||||
) => {
|
||||
const [directories, files] = await HybridPromise.all([
|
||||
HybridPromise.all(
|
||||
favorites.directories.map((directory) =>
|
||||
getDirectoryInfo(directory.id, masterKey, {
|
||||
serverResponse: { ...directory, isFavorite: true },
|
||||
}),
|
||||
);
|
||||
if (result?.exists) {
|
||||
return {
|
||||
type: "file" as const,
|
||||
name: result.name,
|
||||
details: result as SummarizedFileInfo,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
);
|
||||
|
||||
const files = fileResults.filter(
|
||||
(f): f is { type: "file"; name: string; details: SummarizedFileInfo } => f !== null,
|
||||
);
|
||||
|
||||
return [...sortEntries(directories), ...sortEntries(files)];
|
||||
),
|
||||
),
|
||||
HybridPromise.all(
|
||||
favorites.files.map((file) =>
|
||||
getFileInfo(file.id, masterKey, { serverResponse: { ...file, isFavorite: true } }),
|
||||
),
|
||||
),
|
||||
]);
|
||||
return [
|
||||
...sortEntries(
|
||||
directories.map(
|
||||
(directory): FavoriteEntry => ({
|
||||
type: "directory",
|
||||
name: directory.name!,
|
||||
details: directory as LocalDirectoryInfo,
|
||||
}),
|
||||
),
|
||||
),
|
||||
...sortEntries(
|
||||
files.map(
|
||||
(file): FavoriteEntry => ({
|
||||
type: "file",
|
||||
name: file.name!,
|
||||
details: file as SummarizedFileInfo,
|
||||
}),
|
||||
),
|
||||
),
|
||||
];
|
||||
};
|
||||
|
||||
export const requestRemoveFavorite = async (type: "file" | "directory", id: number) => {
|
||||
|
||||
Reference in New Issue
Block a user