Skip to content

Commit 3fbb967

Browse files
committed
move scan utils into fs-sync plugin
1 parent 46f5ff6 commit 3fbb967

File tree

26 files changed

+800
-435
lines changed

26 files changed

+800
-435
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1-
- Persisters define both load/save. The call site decides what to use (e.g., startAutoSave vs startAutoLoad).
1+
## File naming conventions
2+
3+
- `index.ts`: Controls load/save behavior, and should export single hook.
4+
- `persister.ts`: Defines both load and save regardless of actual usage.
5+
- `load.ts`: Reads from filesystem and parses data into store-compatible format (filesystem → store).
6+
- `collect.ts`: Extracts store data and prepares filesystem write operations (store → filesystem).
7+
- `transform.ts`: Bidirectional conversion between file format and store format (used by both load and collect).
8+
9+
## Notes
10+
211
- `local.ts` and `settings.ts` do both save/load.
312
- `folder.ts` is used as load-only (syncs filesystem → store for folders and session folder_id).
4-
- `session/` is the unified persister for session-related data (sessions, transcripts, notes, etc.). It combines load from multiple sources in a single scan and uses separate collectors for save.
513
- Other persisters are used as save-only for now.

apps/desktop/src/store/tinybase/persister/chat/load.ts

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { sep } from "@tauri-apps/api/path";
2-
import { readDir, readTextFile } from "@tauri-apps/plugin-fs";
32

4-
import { isFileNotFoundError, isUUID } from "../utils";
3+
import { commands as fsSyncCommands } from "@hypr/plugin-fs-sync";
4+
55
import {
66
type ChatJson,
77
chatJsonToData,
@@ -19,37 +19,27 @@ export async function loadAllChatData(
1919
): Promise<LoadedChatData> {
2020
const chatsDir = [dataDir, "chats"].join(sep());
2121

22-
let entries: { name: string; isDirectory: boolean }[];
23-
try {
24-
entries = await readDir(chatsDir);
25-
} catch (error) {
26-
if (!isFileNotFoundError(error)) {
27-
console.error(`[${LABEL}] load error:`, error);
28-
}
22+
const scanResult = await fsSyncCommands.scanAndRead(
23+
chatsDir,
24+
["_messages.json"],
25+
false,
26+
);
27+
28+
if (scanResult.status === "error") {
29+
console.error(`[${LABEL}] scan error:`, scanResult.error);
2930
return createEmptyLoadedData();
3031
}
3132

33+
const { files } = scanResult.data;
3234
const items: LoadedChatData[] = [];
3335

34-
for (const entry of entries) {
35-
if (!entry.isDirectory) continue;
36-
if (!isUUID(entry.name)) continue;
37-
38-
const chatGroupId = entry.name;
39-
const messagesPath = [chatsDir, chatGroupId, "_messages.json"].join(sep());
40-
36+
for (const [, content] of Object.entries(files)) {
37+
if (!content) continue;
4138
try {
42-
const content = await readTextFile(messagesPath);
4339
const json = JSON.parse(content) as ChatJson;
4440
items.push(chatJsonToData(json));
4541
} catch (error) {
46-
if (!isFileNotFoundError(error)) {
47-
console.error(
48-
`[${LABEL}] Failed to load chat from ${messagesPath}:`,
49-
error,
50-
);
51-
}
52-
continue;
42+
console.error(`[${LABEL}] Failed to parse chat JSON:`, error);
5343
}
5444
}
5545

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

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@ import type {
55
MappingSessionParticipantStorage,
66
MappingTagSession,
77
SessionStorage,
8+
SpeakerHintStorage,
89
Tag,
10+
TranscriptStorage,
11+
WordStorage,
912
} from "@hypr/store";
1013

1114
import {
1215
type CollectorResult,
1316
getSessionDir,
17+
iterateTableRows,
1418
type TablesContent,
1519
} from "../utils";
1620

@@ -118,3 +122,79 @@ export function collectSessionWriteOps<Schemas extends OptionalSchemas>(
118122

119123
return { dirs, operations, validSessionIds: new Set(sessionMetas.keys()) };
120124
}
125+
126+
type TranscriptWithData = TranscriptStorage & {
127+
id: string;
128+
words: Array<WordStorage & { id: string }>;
129+
speaker_hints: Array<SpeakerHintStorage & { id: string }>;
130+
};
131+
132+
type TranscriptJson = {
133+
transcripts: TranscriptWithData[];
134+
};
135+
136+
export function collectTranscriptWriteOps(
137+
_store: unknown,
138+
tables: TablesContent,
139+
dataDir: string,
140+
): CollectorResult {
141+
const dirs = new Set<string>();
142+
const operations: CollectorResult["operations"] = [];
143+
144+
const transcripts = iterateTableRows(tables, "transcripts");
145+
const words = iterateTableRows(tables, "words");
146+
const speakerHints = iterateTableRows(tables, "speaker_hints");
147+
148+
const wordsByTranscript = new Map<
149+
string,
150+
Array<WordStorage & { id: string }>
151+
>();
152+
for (const word of words) {
153+
if (!word.transcript_id) continue;
154+
const list = wordsByTranscript.get(word.transcript_id) ?? [];
155+
list.push(word);
156+
wordsByTranscript.set(word.transcript_id, list);
157+
}
158+
159+
const hintsByTranscript = new Map<
160+
string,
161+
Array<SpeakerHintStorage & { id: string }>
162+
>();
163+
for (const hint of speakerHints) {
164+
if (!hint.transcript_id) continue;
165+
const list = hintsByTranscript.get(hint.transcript_id) ?? [];
166+
list.push(hint);
167+
hintsByTranscript.set(hint.transcript_id, list);
168+
}
169+
170+
const transcriptsBySession = new Map<string, TranscriptWithData[]>();
171+
for (const transcript of transcripts) {
172+
const sessionId = transcript.session_id;
173+
if (!sessionId) continue;
174+
const transcriptData: TranscriptWithData = {
175+
...transcript,
176+
words: wordsByTranscript.get(transcript.id) ?? [],
177+
speaker_hints: hintsByTranscript.get(transcript.id) ?? [],
178+
};
179+
180+
const list = transcriptsBySession.get(sessionId) ?? [];
181+
list.push(transcriptData);
182+
transcriptsBySession.set(sessionId, list);
183+
}
184+
185+
for (const [sessionId, sessionTranscripts] of transcriptsBySession) {
186+
const session = tables.sessions?.[sessionId];
187+
const folderPath = session?.folder_id ?? "";
188+
const sessionDir = getSessionDir(dataDir, sessionId, folderPath);
189+
dirs.add(sessionDir);
190+
191+
const content: TranscriptJson = { transcripts: sessionTranscripts };
192+
operations.push({
193+
type: "json",
194+
path: [sessionDir, "_transcript.json"].join(sep()),
195+
content,
196+
});
197+
}
198+
199+
return { dirs, operations };
200+
}
Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,20 @@
1-
import { BaseDirectory, exists, mkdir } from "@tauri-apps/plugin-fs";
21
import * as _UI from "tinybase/ui-react/with-schemas";
32

43
import { type Schemas } from "@hypr/store";
54

65
import type { Store } from "../../store/main";
76
import { createSessionPersister } from "./persister";
87

9-
export { createSessionPersister } from "./persister";
10-
export type { SessionDataLoad } from "./load";
11-
128
const { useCreatePersister } = _UI as _UI.WithSchemas<Schemas>;
139

1410
export function useSessionPersister(store: Store) {
1511
return useCreatePersister(
1612
store,
1713
async (store) => {
18-
try {
19-
const dirExists = await exists("hyprnote/sessions", {
20-
baseDir: BaseDirectory.Data,
21-
});
22-
if (!dirExists) {
23-
await mkdir("hyprnote/sessions", {
24-
baseDir: BaseDirectory.Data,
25-
recursive: true,
26-
});
27-
}
28-
} catch (error) {
29-
console.error("Failed to create sessions directory:", error);
30-
throw error;
31-
}
32-
3314
const persister = createSessionPersister<Schemas>(store as Store);
3415
await persister.startAutoSave();
3516
return persister;
3617
},
3718
[],
3819
);
3920
}
40-

0 commit comments

Comments
 (0)