66import shutil
77import tempfile
88from pathlib import Path
9- from typing import Protocol
9+ from typing import Any , Callable , Protocol
10+
11+ from app .foundry_import_state_ledger import (
12+ load_imported_keys ,
13+ load_projection_state_dict ,
14+ save_imported_keys ,
15+ state_path_for_inbox ,
16+ )
1017
1118try :
1219 import plyvel # type: ignore
@@ -21,7 +28,6 @@ async def put_note(self, path: str, content: str) -> None: ...
2128_TAG_RE = re .compile (r"<[^>]+>" )
2229_WS_RE = re .compile (r"\s+" )
2330_IMPORTED_SUFFIX_RE = re .compile (r"_imported(?:_\d+)?$" )
24- _DEDUPE_STATE_FILE = ".foundry_chat_import_state.json"
2531
2632
2733def _strip_html (text : str ) -> str :
@@ -182,21 +188,11 @@ def _mark_leveldb_dir_imported(inbox: Path) -> list[str]:
182188
183189
184190def _dedupe_state_path (inbox : Path ) -> Path :
185- return inbox / _DEDUPE_STATE_FILE
191+ return state_path_for_inbox ( inbox )
186192
187193
188194def _load_dedupe_keys (inbox : Path ) -> set [str ]:
189- path = _dedupe_state_path (inbox )
190- if not path .exists ():
191- return set ()
192- try :
193- data = json .loads (path .read_text (encoding = "utf-8" ))
194- except Exception :
195- return set ()
196- keys = data .get ("imported_keys" ) if isinstance (data , dict ) else None
197- if not isinstance (keys , list ):
198- return set ()
199- return {str (k ) for k in keys }
195+ return load_imported_keys (inbox )
200196
201197
202198def _load_projection_state (path : Path ) -> dict [str , set [str ]]:
@@ -214,67 +210,16 @@ def _load_projection_state(path: Path) -> dict[str, set[str]]:
214210 Pre-Phase-37 state files (only `imported_keys`) load cleanly with empty
215211 projection sets — no exceptions, no KeyError.
216212 """
217- out : dict [str , set [str ]] = {
218- "imported_keys" : set (),
219- "player_projection_keys" : set (),
220- "npc_projection_keys" : set (),
221- }
222- if not path .exists ():
223- return out
224- try :
225- data = json .loads (path .read_text (encoding = "utf-8" ))
226- except Exception :
227- return out
228- if not isinstance (data , dict ):
229- return out
230- for key in (
231- "imported_keys" ,
232- "player_projection_keys" ,
233- "npc_projection_keys" ,
234- ):
235- val = data .get (key )
236- if isinstance (val , list ):
237- out [key ] = {str (k ) for k in val }
238- return out
239-
240-
241- def _save_state (
242- path : Path ,
243- * ,
244- imported_keys : set [str ],
245- player_keys : set [str ] | None = None ,
246- npc_keys : set [str ] | None = None ,
247- ) -> None :
248- """Write state file. If projection keys are None, preserve legacy single-array shape.
249-
250- When player_keys/npc_keys are provided, all three arrays are emitted. This
251- matches the foundry_memory_projection write shape exactly.
252- """
253- if player_keys is None and npc_keys is None :
254- payload : dict [str , Any ] = {"imported_keys" : sorted (imported_keys )}
255- else :
256- payload = {
257- "imported_keys" : sorted (imported_keys ),
258- "player_projection_keys" : sorted (player_keys or set ()),
259- "npc_projection_keys" : sorted (npc_keys or set ()),
260- }
261- path .write_text (json .dumps (payload , indent = 2 ), encoding = "utf-8" )
213+ return load_projection_state_dict (path )
262214
263215
264216def _save_dedupe_keys (inbox : Path , keys : set [str ]) -> None :
265- """Legacy single-array writer — preserved as a thin wrapper over _save_state .
217+ """Legacy single-array writer — preserved as a thin wrapper over the ledger .
266218
267219 When projection state is also tracked on disk, the importer must NOT trample
268220 the projection arrays. Read-then-merge keeps all three buckets intact.
269221 """
270- path = _dedupe_state_path (inbox )
271- existing = _load_projection_state (path )
272- player = existing ["player_projection_keys" ]
273- npc = existing ["npc_projection_keys" ]
274- if player or npc :
275- _save_state (path , imported_keys = keys , player_keys = player , npc_keys = npc )
276- else :
277- _save_state (path , imported_keys = keys )
222+ save_imported_keys (inbox , keys )
278223
279224
280225def _message_key (record : dict ) -> str :
0 commit comments