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