Skip to content

Commit 69b4729

Browse files
committed
fix folder persistence stuff
1 parent fc82378 commit 69b4729

File tree

12 files changed

+262
-206
lines changed

12 files changed

+262
-206
lines changed

apps/desktop/src/store/tinybase/persister/folder.ts

Lines changed: 33 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,131 +1,59 @@
1-
import { exists, readDir } from "@tauri-apps/plugin-fs";
21
import { createCustomPersister } from "tinybase/persisters/with-schemas";
3-
import type {
4-
Content,
5-
MergeableStore,
6-
OptionalSchemas,
7-
} from "tinybase/with-schemas";
2+
import type { MergeableStore, OptionalSchemas } from "tinybase/with-schemas";
83

4+
import { commands as folderCommands } from "@hypr/plugin-folder";
95
import {
106
commands as notifyCommands,
117
events as notifyEvents,
128
} from "@hypr/plugin-notify";
13-
import { commands as path2Commands } from "@hypr/plugin-path2";
14-
import type { FolderStorage } from "@hypr/store";
159

1610
import { DEFAULT_USER_ID } from "../../../utils";
1711
import { StoreOrMergeableStore } from "../store/shared";
18-
import { getParentFolderPath, type PersisterMode } from "./utils";
19-
20-
type FoldersJson = Record<string, FolderStorage>;
21-
22-
interface ScanResult {
23-
folders: FoldersJson;
24-
sessionFolderMap: Map<string, string>;
25-
}
26-
27-
async function getSessionsDir(): Promise<string> {
28-
const base = await path2Commands.base();
29-
return `${base}/sessions`;
30-
}
31-
32-
async function scanDirectoryRecursively(
33-
sessionsDir: string,
34-
currentPath: string = "",
35-
): Promise<ScanResult> {
36-
const folders: FoldersJson = {};
37-
const sessionFolderMap = new Map<string, string>();
38-
39-
const fullPath = currentPath ? `${sessionsDir}/${currentPath}` : sessionsDir;
40-
41-
try {
42-
const entries = await readDir(fullPath);
43-
44-
for (const entry of entries) {
45-
if (!entry.isDirectory) {
46-
continue;
47-
}
48-
49-
const entryPath = currentPath
50-
? `${currentPath}/${entry.name}`
51-
: entry.name;
52-
53-
const hasMemoMd = await exists(`${sessionsDir}/${entryPath}/_memo.md`);
54-
55-
if (hasMemoMd) {
56-
const folderPath = currentPath === "_default" ? "" : currentPath;
57-
sessionFolderMap.set(entry.name, folderPath);
58-
} else {
59-
if (entry.name !== "_default") {
60-
folders[entryPath] = {
61-
user_id: DEFAULT_USER_ID,
62-
created_at: new Date().toISOString(),
63-
name: entry.name,
64-
parent_folder_id: getParentFolderPath(entryPath),
65-
};
66-
}
67-
68-
const subResult = await scanDirectoryRecursively(
69-
sessionsDir,
70-
entryPath,
71-
);
72-
73-
for (const [id, folder] of Object.entries(subResult.folders)) {
74-
folders[id] = folder;
75-
}
76-
for (const [sessionId, folderPath] of subResult.sessionFolderMap) {
77-
sessionFolderMap.set(sessionId, folderPath);
78-
}
79-
}
80-
}
81-
} catch (error) {
82-
const errorStr = String(error);
83-
if (
84-
!errorStr.includes("No such file or directory") &&
85-
!errorStr.includes("ENOENT") &&
86-
!errorStr.includes("not found")
87-
) {
88-
console.error("[FolderPersister] scan error:", error);
89-
}
90-
}
91-
92-
return { folders, sessionFolderMap };
93-
}
94-
95-
export function jsonToContent<Schemas extends OptionalSchemas>(
96-
data: FoldersJson,
97-
): Content<Schemas> {
98-
return [{ folders: data }, {}] as unknown as Content<Schemas>;
99-
}
12+
import type { PersisterMode } from "./utils";
10013

10114
export function createFolderPersister<Schemas extends OptionalSchemas>(
10215
store: MergeableStore<Schemas>,
10316
config: { mode: PersisterMode } = { mode: "load-only" },
10417
) {
10518
const loadFn =
10619
config.mode === "save-only"
107-
? async (): Promise<Content<Schemas> | undefined> => undefined
108-
: async (): Promise<Content<Schemas> | undefined> => {
20+
? async () => undefined
21+
: async () => {
10922
try {
110-
const sessionsDir = await getSessionsDir();
111-
const dirExists = await exists(sessionsDir);
112-
113-
if (!dirExists) {
114-
return jsonToContent<Schemas>({});
23+
const result = await folderCommands.listFolders();
24+
if (result.status === "error") {
25+
console.error("[FolderPersister] list error:", result.error);
26+
return undefined;
11527
}
11628

117-
const { folders, sessionFolderMap } =
118-
await scanDirectoryRecursively(sessionsDir);
29+
const { folders, session_folder_map } = result.data;
30+
const now = new Date().toISOString();
11931

120-
for (const [sessionId, folderPath] of sessionFolderMap) {
121-
// @ts-ignore - we're setting cells on the sessions table
122-
if (store.hasRow("sessions", sessionId)) {
32+
// @ts-ignore - directly update store to avoid wiping other tables
33+
store.transaction(() => {
34+
for (const [folderId, folder] of Object.entries(folders)) {
35+
if (!folder) continue;
12336
// @ts-ignore
124-
store.setCell("sessions", sessionId, "folder_id", folderPath);
37+
store.setRow("folders", folderId, {
38+
user_id: DEFAULT_USER_ID,
39+
created_at: now,
40+
name: folder.name,
41+
parent_folder_id: folder.parent_folder_id ?? "",
42+
} as any);
12543
}
126-
}
12744

128-
return jsonToContent<Schemas>(folders);
45+
for (const [sessionId, folderPath] of Object.entries(
46+
session_folder_map,
47+
)) {
48+
// @ts-ignore
49+
if (store.hasRow("sessions", sessionId)) {
50+
// @ts-ignore
51+
store.setCell("sessions", sessionId, "folder_id", folderPath);
52+
}
53+
}
54+
});
55+
56+
return undefined;
12957
} catch (error) {
13058
console.error("[FolderPersister] load error:", error);
13159
return undefined;

apps/desktop/src/store/tinybase/persister/session.ts

Lines changed: 80 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { readDir, readTextFile, writeTextFile } from "@tauri-apps/plugin-fs";
1+
import {
2+
exists,
3+
readDir,
4+
readTextFile,
5+
writeTextFile,
6+
} from "@tauri-apps/plugin-fs";
27
import { createCustomPersister } from "tinybase/persisters/with-schemas";
38
import type {
49
Content,
@@ -41,78 +46,94 @@ type LoadedData = {
4146
mapping_tag_session: Record<string, MappingTagSession>;
4247
};
4348

44-
async function loadAllSessionMeta(dataDir: string): Promise<LoadedData> {
45-
const result: LoadedData = {
46-
sessions: {},
47-
mapping_session_participant: {},
48-
tags: {},
49-
mapping_tag_session: {},
50-
};
51-
52-
const sessionsDir = `${dataDir}/sessions`;
49+
async function loadSessionMetaRecursively(
50+
sessionsDir: string,
51+
currentPath: string,
52+
result: LoadedData,
53+
now: string,
54+
): Promise<void> {
55+
const fullPath = currentPath ? `${sessionsDir}/${currentPath}` : sessionsDir;
5356

5457
let entries: { name: string; isDirectory: boolean }[];
5558
try {
56-
entries = await readDir(sessionsDir);
59+
entries = await readDir(fullPath);
5760
} catch {
58-
return result;
61+
return;
5962
}
6063

61-
const now = new Date().toISOString();
62-
6364
for (const entry of entries) {
6465
if (!entry.isDirectory) continue;
6566

66-
const sessionId = entry.name;
67-
const metaPath = `${sessionsDir}/${sessionId}/_meta.json`;
68-
69-
try {
70-
const content = await readTextFile(metaPath);
71-
const meta = JSON.parse(content) as SessionMetaJson;
72-
73-
result.sessions[sessionId] = {
74-
user_id: meta.user_id,
75-
created_at: meta.created_at,
76-
title: meta.title,
77-
folder_id: meta.folder_id,
78-
event_id: meta.event_id,
79-
raw_md: "",
80-
enhanced_md: "",
81-
};
82-
83-
for (const participant of meta.participants) {
84-
result.mapping_session_participant[participant.id] = {
85-
user_id: participant.user_id,
86-
created_at: participant.created_at,
87-
session_id: sessionId,
88-
human_id: participant.human_id,
89-
source: participant.source,
67+
const entryPath = currentPath ? `${currentPath}/${entry.name}` : entry.name;
68+
const metaPath = `${sessionsDir}/${entryPath}/_meta.json`;
69+
const hasMetaJson = await exists(metaPath);
70+
71+
if (hasMetaJson) {
72+
try {
73+
const content = await readTextFile(metaPath);
74+
const meta = JSON.parse(content) as SessionMetaJson;
75+
const sessionId = entry.name;
76+
77+
result.sessions[sessionId] = {
78+
user_id: meta.user_id,
79+
created_at: meta.created_at,
80+
title: meta.title,
81+
folder_id: meta.folder_id,
82+
event_id: meta.event_id,
83+
raw_md: "",
84+
enhanced_md: "",
9085
};
91-
}
9286

93-
if (meta.tags) {
94-
for (const tagName of meta.tags) {
95-
if (!result.tags[tagName]) {
96-
result.tags[tagName] = {
87+
for (const participant of meta.participants) {
88+
result.mapping_session_participant[participant.id] = {
89+
user_id: participant.user_id,
90+
created_at: participant.created_at,
91+
session_id: sessionId,
92+
human_id: participant.human_id,
93+
source: participant.source,
94+
};
95+
}
96+
97+
if (meta.tags) {
98+
for (const tagName of meta.tags) {
99+
if (!result.tags[tagName]) {
100+
result.tags[tagName] = {
101+
user_id: meta.user_id,
102+
created_at: now,
103+
name: tagName,
104+
};
105+
}
106+
107+
const mappingId = `${sessionId}:${tagName}`;
108+
result.mapping_tag_session[mappingId] = {
97109
user_id: meta.user_id,
98110
created_at: now,
99-
name: tagName,
111+
tag_id: tagName,
112+
session_id: sessionId,
100113
};
101114
}
102-
103-
const mappingId = `${sessionId}:${tagName}`;
104-
result.mapping_tag_session[mappingId] = {
105-
user_id: meta.user_id,
106-
created_at: now,
107-
tag_id: tagName,
108-
session_id: sessionId,
109-
};
110115
}
116+
} catch {
117+
continue;
111118
}
112-
} catch {
113-
continue;
119+
} else {
120+
await loadSessionMetaRecursively(sessionsDir, entryPath, result, now);
114121
}
115122
}
123+
}
124+
125+
async function loadAllSessionMeta(dataDir: string): Promise<LoadedData> {
126+
const result: LoadedData = {
127+
sessions: {},
128+
mapping_session_participant: {},
129+
tags: {},
130+
mapping_tag_session: {},
131+
};
132+
133+
const sessionsDir = `${dataDir}/sessions`;
134+
const now = new Date().toISOString();
135+
136+
await loadSessionMetaRecursively(sessionsDir, "", result, now);
116137

117138
return result;
118139
}
@@ -218,7 +239,11 @@ export function createSessionPersister<Schemas extends OptionalSchemas>(
218239
[];
219240

220241
for (const [sessionId, meta] of sessionMetas) {
221-
const sessionDir = getSessionDir(dataDir, sessionId);
242+
const sessionDir = getSessionDir(
243+
dataDir,
244+
sessionId,
245+
meta.folder_id,
246+
);
222247
dirs.add(sessionDir);
223248

224249
writeOperations.push({

plugins/folder/build.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const COMMANDS: &[&str] = &[
2-
"ping",
2+
"list_folders",
33
"move_session",
44
"create_folder",
55
"rename_folder",

plugins/folder/js/bindings.gen.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66

77

88
export const commands = {
9-
async ping() : Promise<Result<string, string>> {
9+
async listFolders() : Promise<Result<ListFoldersResult, string>> {
1010
try {
11-
return { status: "ok", data: await TAURI_INVOKE("plugin:folder|ping") };
11+
return { status: "ok", data: await TAURI_INVOKE("plugin:folder|list_folders") };
1212
} catch (e) {
1313
if(e instanceof Error) throw e;
1414
else return { status: "error", error: e as any };
@@ -58,7 +58,8 @@ async deleteFolder(folderPath: string) : Promise<Result<null, string>> {
5858

5959
/** user-defined types **/
6060

61-
61+
export type FolderInfo = { name: string; parent_folder_id: string | null }
62+
export type ListFoldersResult = { folders: Partial<{ [key in string]: FolderInfo }>; session_folder_map: Partial<{ [key in string]: string }> }
6263

6364
/** tauri-specta globals **/
6465

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Automatically generated - DO NOT EDIT!
2+
3+
"$schema" = "../../schemas/schema.json"
4+
5+
[[permission]]
6+
identifier = "allow-list-folders"
7+
description = "Enables the list_folders command without any pre-configured scope."
8+
commands.allow = ["list_folders"]
9+
10+
[[permission]]
11+
identifier = "deny-list-folders"
12+
description = "Denies the list_folders command without any pre-configured scope."
13+
commands.deny = ["list_folders"]

plugins/folder/permissions/autogenerated/commands/ping.toml

Lines changed: 0 additions & 13 deletions
This file was deleted.

0 commit comments

Comments
 (0)