네트워크 호출 결과가 IndexedDB에 캐시되지 않던 버그 수정

This commit is contained in:
static
2026-01-01 23:52:47 +09:00
parent d98be331ad
commit 2e3cd4f8a2
4 changed files with 158 additions and 36 deletions

View File

@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { CheckBox, RowVirtualizer } from "$lib/components/atoms"; import { CheckBox, RowVirtualizer } from "$lib/components/atoms";
import { SubCategories, type SelectedCategory } from "$lib/components/molecules"; import { SubCategories, type SelectedCategory } from "$lib/components/molecules";
import { updateCategoryInfo } from "$lib/indexedDB";
import type { CategoryInfo } from "$lib/modules/filesystem"; import type { CategoryInfo } from "$lib/modules/filesystem";
import { sortEntries } from "$lib/utils"; import { sortEntries } from "$lib/utils";
import File from "./File.svelte"; import File from "./File.svelte";
@@ -28,6 +29,9 @@
isFileRecursive = $bindable(), isFileRecursive = $bindable(),
}: Props = $props(); }: Props = $props();
let lastCategoryId = $state<CategoryInfo["id"] | undefined>();
let lastIsFileRecursive = $state<boolean | undefined>();
let files = $derived( let files = $derived(
sortEntries( sortEntries(
info.files info.files
@@ -35,6 +39,19 @@
.filter(({ details }) => isFileRecursive || !details.isRecursive) ?? [], .filter(({ details }) => isFileRecursive || !details.isRecursive) ?? [],
), ),
); );
$effect(() => {
if (info.id === "root" || isFileRecursive === undefined) return;
if (lastCategoryId !== info.id) {
lastCategoryId = info.id;
lastIsFileRecursive = isFileRecursive;
return;
}
if (lastIsFileRecursive === isFileRecursive) return;
lastIsFileRecursive = isFileRecursive;
void updateCategoryInfo(info.id, { isFileRecursive });
});
</script> </script>
<div class="space-y-4"> <div class="space-y-4">

View File

@@ -52,25 +52,76 @@ const fetchFromServer = async (id: CategoryId, masterKey: CryptoKey) => {
metadata, metadata,
subCategories: subCategoriesRaw, subCategories: subCategoriesRaw,
files: filesRaw, files: filesRaw,
} = await trpc().category.get.query({ id }); } = await trpc().category.get.query({ id, recurse: true });
const [subCategories, files] = await Promise.all([ const subCategories = await Promise.all(
Promise.all( subCategoriesRaw.map(async (category) => {
subCategoriesRaw.map(async (category) => ({ const decrypted = await decryptCategoryMetadata(category, masterKey);
const existing = await IndexedDB.getCategoryInfo(category.id);
await IndexedDB.storeCategoryInfo({
id: category.id, id: category.id,
...(await decryptCategoryMetadata(category, masterKey)), parentId: id,
})), name: decrypted.name,
), files: existing?.files ?? [],
filesRaw isFileRecursive: existing?.isFileRecursive ?? false,
? Promise.all( });
filesRaw.map(async (file) => ({ return {
id: category.id,
...decrypted,
};
}),
);
const existingFiles = filesRaw
? await IndexedDB.bulkGetFileInfos(filesRaw.map((file) => file.id))
: [];
const files = filesRaw
? await Promise.all(
filesRaw.map(async (file, index) => {
const decrypted = await decryptFileMetadata(file, masterKey);
const existing = existingFiles[index];
if (existing) {
const categoryIds = file.isRecursive
? existing.categoryIds
: Array.from(new Set([...existing.categoryIds, id as number]));
await IndexedDB.storeFileInfo({
id: file.id,
parentId: existing.parentId,
contentType: file.contentType,
name: decrypted.name,
createdAt: decrypted.createdAt,
lastModifiedAt: decrypted.lastModifiedAt,
categoryIds,
});
}
return {
id: file.id, id: file.id,
contentType: file.contentType, contentType: file.contentType,
isRecursive: file.isRecursive, isRecursive: file.isRecursive,
...(await decryptFileMetadata(file, masterKey)), ...decrypted,
})), };
) }),
: undefined, )
]); : undefined;
const decryptedMetadata = metadata
? await decryptCategoryMetadata(metadata, masterKey)
: undefined;
if (id !== "root" && metadata && decryptedMetadata) {
const existingCategory = await IndexedDB.getCategoryInfo(id);
await IndexedDB.storeCategoryInfo({
id: id as number,
parentId: metadata.parent,
name: decryptedMetadata.name,
files:
files?.map((file) => ({
id: file.id,
isRecursive: file.isRecursive,
})) ??
existingCategory?.files ??
[],
isFileRecursive: existingCategory?.isFileRecursive ?? false,
});
}
if (id === "root") { if (id === "root") {
return { return {
@@ -84,7 +135,7 @@ const fetchFromServer = async (id: CategoryId, masterKey: CryptoKey) => {
exists: true as const, exists: true as const,
subCategories, subCategories,
files, files,
...(await decryptCategoryMetadata(metadata!, masterKey)), ...decryptedMetadata!,
}; };
} }
} catch (e) { } catch (e) {

View File

@@ -39,22 +39,52 @@ const fetchFromServer = async (id: DirectoryId, masterKey: CryptoKey) => {
subDirectories: subDirectoriesRaw, subDirectories: subDirectoriesRaw,
files: filesRaw, files: filesRaw,
} = await trpc().directory.get.query({ id }); } = await trpc().directory.get.query({ id });
const [subDirectories, files] = await Promise.all([ const existingFiles = await IndexedDB.bulkGetFileInfos(filesRaw.map((file) => file.id));
const [subDirectories, files, decryptedMetadata] = await Promise.all([
Promise.all( Promise.all(
subDirectoriesRaw.map(async (directory) => ({ subDirectoriesRaw.map(async (directory) => {
id: directory.id, const decrypted = await decryptDirectoryMetadata(directory, masterKey);
...(await decryptDirectoryMetadata(directory, masterKey)), await IndexedDB.storeDirectoryInfo({
})), id: directory.id,
parentId: id,
name: decrypted.name,
});
return {
id: directory.id,
...decrypted,
};
}),
), ),
Promise.all( Promise.all(
filesRaw.map(async (file) => ({ filesRaw.map(async (file, index) => {
id: file.id, const decrypted = await decryptFileMetadata(file, masterKey);
contentType: file.contentType, await IndexedDB.storeFileInfo({
...(await decryptFileMetadata(file, masterKey)), id: file.id,
})), parentId: id,
contentType: file.contentType,
name: decrypted.name,
createdAt: decrypted.createdAt,
lastModifiedAt: decrypted.lastModifiedAt,
categoryIds: existingFiles[index]?.categoryIds ?? [],
});
return {
id: file.id,
contentType: file.contentType,
...decrypted,
};
}),
), ),
metadata ? decryptDirectoryMetadata(metadata, masterKey) : undefined,
]); ]);
if (id !== "root" && metadata && decryptedMetadata) {
await IndexedDB.storeDirectoryInfo({
id,
parentId: metadata.parent,
name: decryptedMetadata.name,
});
}
if (id === "root") { if (id === "root") {
return { return {
id, id,
@@ -69,7 +99,7 @@ const fetchFromServer = async (id: DirectoryId, masterKey: CryptoKey) => {
parentId: metadata!.parent, parentId: metadata!.parent,
subDirectories, subDirectories,
files, files,
...(await decryptDirectoryMetadata(metadata!, masterKey)), ...decryptedMetadata!,
}; };
} }
} catch (e) { } catch (e) {

View File

@@ -66,15 +66,26 @@ const bulkFetchFromIndexedDB = async (ids: number[]) => {
const fetchFromServer = async (id: number, masterKey: CryptoKey) => { const fetchFromServer = async (id: number, masterKey: CryptoKey) => {
try { try {
const { categories: categoriesRaw, ...metadata } = await trpc().file.get.query({ id }); const { categories: categoriesRaw, ...metadata } = await trpc().file.get.query({ id });
const [categories] = await Promise.all([ const [categories, decryptedMetadata] = await Promise.all([
Promise.all( Promise.all(
categoriesRaw.map(async (category) => ({ categoriesRaw.map(async (category) => ({
id: category.id, id: category.id,
...(await decryptCategoryMetadata(category, masterKey)), ...(await decryptCategoryMetadata(category, masterKey)),
})), })),
), ),
decryptFileMetadata(metadata, masterKey),
]); ]);
await IndexedDB.storeFileInfo({
id,
parentId: metadata.parent,
contentType: metadata.contentType,
name: decryptedMetadata.name,
createdAt: decryptedMetadata.createdAt,
lastModifiedAt: decryptedMetadata.lastModifiedAt,
categoryIds: categories.map((category) => category.id),
});
return { return {
id, id,
exists: true as const, exists: true as const,
@@ -82,7 +93,7 @@ const fetchFromServer = async (id: number, masterKey: CryptoKey) => {
contentType: metadata.contentType, contentType: metadata.contentType,
contentIv: metadata.contentIv, contentIv: metadata.contentIv,
categories, categories,
...(await decryptFileMetadata(metadata, masterKey)), ...decryptedMetadata,
}; };
} catch (e) { } catch (e) {
if (isTRPCClientError(e) && e.data?.code === "NOT_FOUND") { if (isTRPCClientError(e) && e.data?.code === "NOT_FOUND") {
@@ -97,12 +108,25 @@ const bulkFetchFromServer = async (ids: number[], masterKey: CryptoKey) => {
const filesRaw = await trpc().file.bulkGet.query({ ids }); const filesRaw = await trpc().file.bulkGet.query({ ids });
const files = await Promise.all( const files = await Promise.all(
filesRaw.map(async (file) => { filesRaw.map(async (file) => {
const categories = await Promise.all( const [categories, decryptedMetadata] = await Promise.all([
file.categories.map(async (category) => ({ Promise.all(
id: category.id, file.categories.map(async (category) => ({
...(await decryptCategoryMetadata(category, masterKey)), id: category.id,
})), ...(await decryptCategoryMetadata(category, masterKey)),
); })),
),
decryptFileMetadata(file, masterKey),
]);
await IndexedDB.storeFileInfo({
id: file.id,
parentId: file.parent,
contentType: file.contentType,
name: decryptedMetadata.name,
createdAt: decryptedMetadata.createdAt,
lastModifiedAt: decryptedMetadata.lastModifiedAt,
categoryIds: categories.map((category) => category.id),
});
return { return {
id: file.id, id: file.id,
exists: true as const, exists: true as const,
@@ -110,7 +134,7 @@ const bulkFetchFromServer = async (ids: number[], masterKey: CryptoKey) => {
contentType: file.contentType, contentType: file.contentType,
contentIv: file.contentIv, contentIv: file.contentIv,
categories, categories,
...(await decryptFileMetadata(file, masterKey)), ...decryptedMetadata,
}; };
}), }),
); );