bookmarks: route library copy through save_post_file

Fifth Phase 2 site migration. _copy_to_library_unsorted and
_copy_to_library now both delegate to a private
_save_bookmark_to_library helper that walks through save_post_file.
A small _bookmark_to_post adapter constructs a Post from a Bookmark
for the renderer — Bookmark already carries every field the renderer
reads, this is just one place to maintain if Post's shape drifts.

Fixes the latent v0.2.3 bug where bookmark→library copies wrote
files but never registered library_meta rows — those files were on
disk but invisible to Library tag-search until you also re-saved
from the browse side.

Picks up filename templates and sequential collision suffixes for
bookmark→library saves for free, same as the browse-side migrations.

Net add (+32 lines) is from the new helper docstrings + the explicit
_bookmark_to_post adapter; the actual save logic shrinks to a one-
liner per public method.
This commit is contained in:
pax 2026-04-09 17:06:01 -05:00
parent f6c5c6780d
commit d05a9cd368

View File

@ -22,6 +22,7 @@ from PySide6.QtWidgets import (
) )
from ..core.db import Database, Bookmark from ..core.db import Database, Bookmark
from ..core.api.base import Post
from ..core.cache import download_thumbnail from ..core.cache import download_thumbnail
from ..core.concurrency import run_on_app_loop from ..core.concurrency import run_on_app_loop
from .grid import ThumbnailGrid from .grid import ThumbnailGrid
@ -243,25 +244,56 @@ class BookmarksView(QWidget):
if 0 <= index < len(self._bookmarks): if 0 <= index < len(self._bookmarks):
self.bookmark_activated.emit(self._bookmarks[index]) self.bookmark_activated.emit(self._bookmarks[index])
def _bookmark_to_post(self, fav: Bookmark) -> Post:
"""Adapt a Bookmark into a Post for the renderer / save flow.
The unified save_post_file flow takes a Post (because it's
called from the browse side too), so bookmarks borrow Post
shape just for the duration of the save call. Bookmark already
carries every field the renderer reads this adapter is the
one place to update if Post's field set drifts later.
"""
return Post(
id=fav.post_id,
file_url=fav.file_url,
preview_url=fav.preview_url,
tags=fav.tags,
score=fav.score or 0,
rating=fav.rating,
source=fav.source,
tag_categories=fav.tag_categories or {},
)
def _save_bookmark_to_library(self, fav: Bookmark, folder: str | None) -> None:
"""Copy a bookmarked image into the library, optionally inside
a subfolder, routing through the unified save_post_file flow.
Fixes the latent v0.2.3 bug where bookmarklibrary copies
wrote files but never registered library_meta rows those
files were on disk but invisible to Library tag-search."""
from ..core.config import saved_dir, saved_folder_dir
from ..core.library_save import save_post_file
if not (fav.cached_path and Path(fav.cached_path).exists()):
return
try:
dest_dir = saved_folder_dir(folder) if folder else saved_dir()
except ValueError:
return
src = Path(fav.cached_path)
post = self._bookmark_to_post(fav)
try:
save_post_file(src, post, dest_dir, self._db)
except Exception as e:
log.warning(f"Bookmark→library save #{fav.post_id} failed: {e}")
def _copy_to_library_unsorted(self, fav: Bookmark) -> None: def _copy_to_library_unsorted(self, fav: Bookmark) -> None:
"""Copy a bookmarked image to the unsorted library folder.""" """Copy a bookmarked image to the unsorted library folder."""
from ..core.config import saved_dir self._save_bookmark_to_library(fav, None)
if fav.cached_path and Path(fav.cached_path).exists():
import shutil
src = Path(fav.cached_path)
dest = saved_dir() / f"{fav.post_id}{src.suffix}"
if not dest.exists():
shutil.copy2(src, dest)
def _copy_to_library(self, fav: Bookmark, folder: str) -> None: def _copy_to_library(self, fav: Bookmark, folder: str) -> None:
"""Copy a bookmarked image to the library folder on disk.""" """Copy a bookmarked image to the named library subfolder."""
from ..core.config import saved_folder_dir self._save_bookmark_to_library(fav, folder)
if fav.cached_path and Path(fav.cached_path).exists():
import shutil
src = Path(fav.cached_path)
dest = saved_folder_dir(folder) / f"{fav.post_id}{src.suffix}"
if not dest.exists():
shutil.copy2(src, dest)
def _new_folder(self) -> None: def _new_folder(self) -> None:
name, ok = QInputDialog.getText(self, "New Folder", "Folder name:") name, ok = QInputDialog.getText(self, "New Folder", "Folder name:")