2727
2828from src import crops , db , thumbnails , wm_compose
2929from src import sqlite_store as store
30+ from src .constants import WATERMARK_BACKUPS_DIR
3031
3132logger = logging .getLogger (__name__ )
3233
3334# The cleanup categories, in the order the System view lists them.
34- CATEGORIES = ("media" , "captions" , "patches" , "thumbs" )
35+ CATEGORIES = (
36+ "media" ,
37+ "captions" ,
38+ "dataset_captions" ,
39+ "claims" ,
40+ "quality" ,
41+ "embeddings" ,
42+ "index" ,
43+ "crops" ,
44+ "patches" ,
45+ "wm_backups" ,
46+ "thumbs" ,
47+ )
3548
3649
3750def _safe_size (path : Path ) -> int :
@@ -103,12 +116,118 @@ def purge_orphan_media() -> dict:
103116
104117def unused_caption_report () -> dict :
105118 """Return ``{count, bytes}`` for the superseded caption revisions."""
106- return {"count" : store .unused_revision_count (), "bytes" : 0 }
119+ return {
120+ "count" : store .unused_revision_count (),
121+ "bytes" : store .unused_revision_bytes (),
122+ }
107123
108124
109125def purge_unused_captions () -> dict :
110126 """Prune the superseded caption revisions; return ``{purged, bytes}``."""
111- return {"purged" : store .prune_unused_revisions (), "bytes" : 0 }
127+ freed = store .unused_revision_bytes ()
128+ return {"purged" : store .prune_unused_revisions (), "bytes" : freed }
129+
130+
131+ # -- captions of media in no dataset ------------------------------------------
132+
133+
134+ def unlinked_caption_report () -> dict :
135+ """Return ``{count, bytes}`` for captions of media in no dataset."""
136+ return store .unlinked_caption_report ()
137+
138+
139+ def purge_unlinked_captions () -> dict :
140+ """Delete those caption histories; return ``{purged, bytes}``."""
141+ freed = store .unlinked_caption_report ()["bytes" ]
142+ return {"purged" : store .purge_unlinked_captions (), "bytes" : freed }
143+
144+
145+ # -- caption grounding (claims) history ---------------------------------------
146+
147+
148+ def claims_report () -> dict :
149+ """Return ``{count, bytes}`` for the stored caption-claim history."""
150+ return store .grounding_history_report ()
151+
152+
153+ def purge_claims () -> dict :
154+ """Clear the whole grounding history; return ``{purged, bytes}``."""
155+ freed = store .grounding_history_report ()["bytes" ]
156+ return {"purged" : store .purge_grounding_history (), "bytes" : freed }
157+
158+
159+ # -- index data (quality / embeddings / dims+hashes) --------------------------
160+
161+
162+ def quality_report () -> dict :
163+ """Return ``{count, bytes}`` for the stored quality scores."""
164+ return store .quality_scores_report ()
165+
166+
167+ def purge_quality () -> dict :
168+ """Delete every quality score; return ``{purged, bytes}``."""
169+ freed = store .quality_scores_report ()["bytes" ]
170+ return {"purged" : store .purge_quality_scores (), "bytes" : freed }
171+
172+
173+ def embeddings_report () -> dict :
174+ """Return ``{count, bytes}`` for the stored embedding vectors."""
175+ return store .embeddings_report ()
176+
177+
178+ def purge_embeddings () -> dict :
179+ """Delete every embedding vector; return ``{purged, bytes}``."""
180+ freed = store .embeddings_report ()["bytes" ]
181+ return {"purged" : store .purge_embeddings (), "bytes" : freed }
182+
183+
184+ def media_index_report () -> dict :
185+ """Return ``{count, bytes}`` for the per-media index columns."""
186+ return store .media_index_report ()
187+
188+
189+ def purge_media_index () -> dict :
190+ """Reset the per-media index columns; return ``{purged, bytes}``."""
191+ freed = store .media_index_report ()["bytes" ]
192+ return {"purged" : store .purge_media_index (), "bytes" : freed }
193+
194+
195+ # -- rendered-crop cache ------------------------------------------------------
196+
197+
198+ def crops_cache_report () -> dict :
199+ """Return ``{count, bytes}`` for the rendered-crop cache."""
200+ count , total = _tree_size (crops .get_crops_dir ())
201+ return {"count" : count , "bytes" : total }
202+
203+
204+ def purge_crops_cache () -> dict :
205+ """Empty the rendered-crop cache; return ``{purged, bytes}``.
206+
207+ Crops are virtual (a rectangle over the parent media), so the PNGs are
208+ re-materialized on demand the next time something reads them.
209+ """
210+ removed , freed = _empty_tree (crops .get_crops_dir ())
211+ return {"purged" : removed , "bytes" : freed }
212+
213+
214+ # -- watermark backups --------------------------------------------------------
215+
216+
217+ def wm_backup_report () -> dict :
218+ """Return ``{count, bytes}`` for the pre-patch original backups."""
219+ count , total = _tree_size (WATERMARK_BACKUPS_DIR )
220+ return {"count" : count , "bytes" : total }
221+
222+
223+ def purge_wm_backups () -> dict :
224+ """Empty the watermark backups; return ``{purged, bytes}``.
225+
226+ Loses the "restore original" option of already-patched media; the
227+ patched pixels themselves are untouched.
228+ """
229+ removed , freed = _empty_tree (WATERMARK_BACKUPS_DIR )
230+ return {"purged" : removed , "bytes" : freed }
112231
113232
114233# -- orphan patch / composite / crop cache files ----------------------------
@@ -201,20 +320,42 @@ def purge_thumbnail_cache() -> dict:
201320_REPORTS = {
202321 "media" : orphan_media_report ,
203322 "captions" : unused_caption_report ,
323+ "dataset_captions" : unlinked_caption_report ,
324+ "claims" : claims_report ,
325+ "quality" : quality_report ,
326+ "embeddings" : embeddings_report ,
327+ "index" : media_index_report ,
328+ "crops" : crops_cache_report ,
204329 "patches" : orphan_patch_report ,
330+ "wm_backups" : wm_backup_report ,
205331 "thumbs" : thumbnail_report ,
206332}
207333
208334_PURGES = {
209335 "media" : purge_orphan_media ,
210336 "captions" : purge_unused_captions ,
337+ "dataset_captions" : purge_unlinked_captions ,
338+ "claims" : purge_claims ,
339+ "quality" : purge_quality ,
340+ "embeddings" : purge_embeddings ,
341+ "index" : purge_media_index ,
342+ "crops" : purge_crops_cache ,
211343 "patches" : purge_orphan_patches ,
344+ "wm_backups" : purge_wm_backups ,
212345 "thumbs" : purge_thumbnail_cache ,
213346}
214347
215348# Only the categories that delete database rows benefit from a VACUUM; the
216349# file-cache sweeps free their space on unlink.
217- _VACUUM_AFTER = {"media" , "captions" }
350+ _VACUUM_AFTER = {
351+ "media" ,
352+ "captions" ,
353+ "dataset_captions" ,
354+ "claims" ,
355+ "quality" ,
356+ "embeddings" ,
357+ "index" ,
358+ }
218359
219360
220361def vacuum () -> None :
@@ -228,6 +369,19 @@ def cleanup_report() -> dict:
228369 return {category : report () for category , report in _REPORTS .items ()}
229370
230371
372+ def category_report (category : str ) -> dict :
373+ """Return one category's ``{count, bytes}`` (the per-row loader).
374+
375+ The System view fetches each category independently so a slow scan
376+ (patch orphans, big cache trees) never blocks the cheap ones. Raises
377+ ``ValueError`` on an unknown category.
378+ """
379+ report = _REPORTS .get (category )
380+ if report is None :
381+ raise ValueError (f"unknown cleanup category: { category !r} " )
382+ return report ()
383+
384+
231385def run_cleanup (category : str ) -> dict :
232386 """Purge one :data:`CATEGORIES` category.
233387
0 commit comments