@@ -941,7 +941,10 @@ def normalize_focus(raw) -> Focus | None:
941941
942942
943943def _stored () -> str | None :
944- """いまの定義(JSON の文字列)。まだ 1 度も置いていなければ None。"""
944+ """**まとめて 1 件に入れていた頃**の定義(JSON の文字列)。無ければ None。
945+
946+ いまは 1 収集 = 1 件(`collect/<名前>`)。ここを読むのは移行のためだけ。
947+ """
945948 return machine_store .get (DEFS_KIND , DEFS_KEY )
946949
947950
@@ -1115,47 +1118,101 @@ def edits_what_is_there(prompt: str, only_new: bool = False) -> bool:
11151118 return any (p in (prompt or "" ) for p in (MATERIAL_PLACEHOLDER , RECENT_PLACEHOLDER ))
11161119
11171120
1118- def _to_json (items : list [Collection ]) -> str :
1119- return json .dumps (
1120- {"collections" : [c .__dict__ for c in items ]},
1121- ensure_ascii = False ,
1122- indent = 2 ,
1123- )
1124-
1125-
11261121def load () -> list [Collection ]:
1127- """定義の一覧(並びは配列の順)。
1122+ """定義の一覧(並びは作った順)。**1 収集 = 1 件**(`collect/<名前>`)。
1123+
1124+ **まとめて 1 件に入れない。** 区画の台帳は収集 1 つで MB 単位になりうるので、
1125+ 全部を 1 つの JSON に入れると、**どれか 1 つを直すだけで全部を読み書きする**
1126+ ことになる —— 1 つが壊れれば全部が読めなくなり、1 つが太れば全部が重くなる。
1127+ (**上限を置いて逃げない**ための前提でもある。台帳の天井は、この形にして
1128+ 初めて「1 つの収集の都合」に閉じられる。)
11281129
11291130 **本文が壊れていたら黙って作り直さない** —— 中身ごと消えるので、読めないことを
1130- 見せて人に直させる(プロジェクトと同じ判断)。
1131+ 見せて人に直させる(プロジェクトと同じ判断)。**壊れているのはその 1 件だけ**
1132+ だと分かるように、名前を添えて上げる。
1133+ """
1134+ _split_out_the_old_row ()
1135+ out = []
1136+ for key in machine_store .keys (DEFS_KIND ):
1137+ if key == DEFS_KEY :
1138+ continue
1139+ body = machine_store .get (DEFS_KIND , key )
1140+ if body is None :
1141+ continue
1142+ try :
1143+ raw = json .loads (body )
1144+ if not isinstance (raw , dict ):
1145+ raise TypeError ("object を入れてください" )
1146+ except (ValueError , TypeError ) as e :
1147+ raise HTTPException (400 , {"error" : f"{ DEFS_BROKEN } (「{ key } 」): { e } " }) from None
1148+ out .append (_from_json (raw ))
1149+ # **並びは作った順**。1 件ずつ置くと配列の順を持てないので、作った時刻で並べ直す
1150+ return sorted (out , key = lambda c : (c .created_at , c .name ))
1151+
1152+
1153+ def _split_out_the_old_row () -> None :
1154+ """まとめて 1 件に入れていた頃のものを、1 収集 1 件へ分ける。**1 度だけ**。
1155+
1156+ **古い行が残っているあいだは、そちらが正**(分け終えてから消す)——
1157+ 途中で落ちても、次に読むときにもう一度分け直せる。
11311158 """
11321159 body = _stored ()
11331160 if body is None :
1134- return []
1161+ return
11351162 try :
1136- payload = json .loads (body or "{}" )
1137- raw = payload ["collections" ]
1163+ raw = json .loads (body or "{}" )["collections" ]
11381164 if not isinstance (raw , list ):
1139- raise ValueError ("collections must be a list" )
1165+ raise TypeError ("collections must be a list" )
11401166 except (ValueError , KeyError , TypeError ) as e :
11411167 raise HTTPException (400 , {"error" : f"{ DEFS_BROKEN } : { e } " }) from None
1142- return [_from_json (i ) for i in raw if isinstance (i , dict )]
1168+ for item in raw :
1169+ if isinstance (item , dict ) and (name := str (item .get ("name" ) or "" )):
1170+ machine_store .put (DEFS_KIND , name , json .dumps (item , ensure_ascii = False , indent = 2 ))
1171+ machine_store .drop (DEFS_KIND , DEFS_KEY )
1172+ log .info ("split %d collection definitions into one record each" , len (raw ))
11431173
11441174
11451175def save (items : list [Collection ]) -> None :
1146- machine_store .put (DEFS_KIND , DEFS_KEY , _to_json (items ))
1176+ """一覧をそのまま書き込む。**消えたものはこの場で落とす**。
1177+
1178+ 1 件だけ直すなら `_replace_one` のほうが安い(そちらは 1 件しか書かない)。
1179+ """
1180+ _split_out_the_old_row ()
1181+ keep = {c .name for c in items }
1182+ for item in items :
1183+ _put_one (item )
1184+ for key in machine_store .keys (DEFS_KIND ):
1185+ if key != DEFS_KEY and key not in keep :
1186+ machine_store .drop (DEFS_KIND , key )
1187+
1188+
1189+ def _put_one (item : Collection ) -> None :
1190+ machine_store .put (
1191+ DEFS_KIND , item .name , json .dumps (item .__dict__ , ensure_ascii = False , indent = 2 ),
1192+ )
11471193
11481194
11491195def get (name : str ) -> Collection :
1150- for item in load ():
1151- if item .name == name :
1152- return item
1153- raise HTTPException (404 , {"error" : f"収集「{ name } 」がありません" })
1196+ """名前で 1 つ。**一覧を読まない** —— 台帳を抱えた他の収集まで読む理由が無い。"""
1197+ _split_out_the_old_row ()
1198+ body = machine_store .get (DEFS_KIND , name ) if name and name != DEFS_KEY else None
1199+ if body is None :
1200+ raise HTTPException (404 , {"error" : f"収集「{ name } 」がありません" })
1201+ try :
1202+ raw = json .loads (body )
1203+ if not isinstance (raw , dict ):
1204+ raise TypeError ("object を入れてください" )
1205+ except (ValueError , TypeError ) as e :
1206+ raise HTTPException (400 , {"error" : f"{ DEFS_BROKEN } (「{ name } 」): { e } " }) from None
1207+ return _from_json (raw )
11541208
11551209
11561210def _replace_one (name : str , updated : Collection ) -> None :
1157- items = load ()
1158- save ([updated if c .name == name else c for c in items ])
1211+ """その 1 件だけを書き換える。**他の収集は読みも書きもしない**。"""
1212+ get (name )
1213+ if updated .name != name :
1214+ machine_store .drop (DEFS_KIND , name )
1215+ _put_one (updated )
11591216
11601217
11611218def create (
@@ -1317,10 +1374,8 @@ def remove(name: str) -> None:
13171374 同じ名前で作り直すのは普通に起きる —— 残しておくと、前の収集が足した・消した
13181375 ものが新しい収集の履歴に混ざって見える。
13191376 """
1320- items = load ()
1321- if not any (c .name == name for c in items ):
1322- raise HTTPException (404 , {"error" : f"収集「{ name } 」がありません" })
1323- save ([c for c in items if c .name != name ])
1377+ get (name )
1378+ machine_store .drop (DEFS_KIND , name )
13241379 collect_log .forget (name )
13251380 # **待ち行列からも外す** —— 消えた収集を抱えたままだと、そのワーカーは
13261381 # 起こそうとして 404 を踏み続ける
0 commit comments