feat(images): duplicate cleanup keeps images' own categories (#332)

The kept copy's own category wins; a kept copy without one receives the
removed copy's, keyed by the deepest output source holding each file.
The removed copy's row is never deleted, so a give-back or a failed
delete loses nothing. The job log reports how many were kept.
This commit is contained in:
Vito Sansevero
2026-10-27 18:17:28 -07:00
parent cd573ccf1a
commit 0237fba77a
4 changed files with 177 additions and 3 deletions
+13 -2
View File
@@ -11,6 +11,8 @@ IMMEDIATE`` transaction, so no row ever names a deleted file:
- its favorite;
- its image tags (``media_image_tags`` of its byte record), joined onto the
kept copy's byte record as their union, in order;
- its own image category (#332), copied to the kept copy when that copy has
none; the removed copy's row is kept, so a give-back loses nothing;
- its pending Needs review items, settled: only an item still ``pending`` at
revision 0 with no decision row is removed (``review._SETTLE_ITEM``), so a
decided item is never touched.
@@ -22,6 +24,8 @@ import os
from .annotation_schema import BYTES_TABLE, FILES_TABLE
from .annotations import _BUMP_INDEX
from .image_categories import COPY_SQL as COPY_CATEGORY_SQL
from .image_categories import copy_image_category
from .models import normalize_image_path, utc_now_iso
from .review import _BUMP_REVIEW_INDEX, _PENDING_OF_LOCATOR, _SETTLE_ITEM
from .review_changes import notify_review_change
@@ -76,6 +80,7 @@ FILE_LINK_SQL = (
COPY_BYTE_SQL,
COPY_TAG_COUNT_SQL,
PENDING_COUNT_SQL,
COPY_CATEGORY_SQL,
)
@@ -137,8 +142,10 @@ class DuplicateCopyOperations:
removed key, kept root key, kept key)`` per listed root holding the
file), ``removed_locator``,
``kept_locator``, ``sha256`` and the kept copy's ``kept_size`` and
``kept_mtime_ns``. Returns ``{moved, merged, favorites, tags,
review}``.
``kept_mtime_ns``; ``categories``: ``(removed file key, kept file
key)`` pairs of the image categories (#332). Returns ``{moved,
merged, favorites, tags, review, categories}`` (``categories``: the
image categories copied to the kept copy).
"""
counts = dict.fromkeys(("moved", "merged", "favorites", "tags", "review"), 0)
kept_keys = [normalize_image_path(path) for path in move["kept"]]
@@ -174,6 +181,10 @@ class DuplicateCopyOperations:
for favorite in move.get("favorites", ()):
counts["favorites"] += _move_favorite(conn, *favorite)
counts["tags"] = self._move_copy_tags(conn, move)
counts["categories"] = sum(
copy_image_category(conn, removed, kept)
for removed, kept in move.get("categories", ())
)
for (item_id,) in conn.execute(
_PENDING_OF_LOCATOR, (move["removed_locator"],)
).fetchall():