main_window: route _bulk_save through save_post_file

Second Phase 2 site migration. Hoists destination resolution out of
the per-iteration loop, uses a shared in_flight set so collision-prone
templates (%artist% on a page of same-artist posts) get sequential
suffixes instead of clobbering each other, and finally calls
_copy_library_thumb so multi-select bulk saves get library thumbnails
just like single-post saves do.

Drops the dead site_id assignment that nothing read.

Fixes the latent bug where _bulk_save left library thumbnails uncopied
even though _save_to_library always copied them — multi-select saves
were missing thumbnails in the Library tab until you re-saved one at
a time.
This commit is contained in:
pax 2026-04-09 17:03:37 -05:00
parent 38937528ef
commit b72f3a54c0

View File

@ -2589,31 +2589,35 @@ class BooruApp(QMainWindow):
self._run_async(_do) self._run_async(_do)
def _bulk_save(self, indices: list[int], posts: list[Post], folder: str | None) -> None: def _bulk_save(self, indices: list[int], posts: list[Post], folder: str | None) -> None:
site_id = self._site_combo.currentData() """Bulk-save the selected posts into the library, optionally inside a subfolder.
Each iteration routes through save_post_file with a shared
in_flight set so template-collision-prone batches (e.g.
%artist% on a page that has many posts by the same artist) get
sequential _1, _2, _3 suffixes instead of clobbering each other.
"""
from ..core.config import saved_dir, saved_folder_dir
from ..core.library_save import save_post_file
where = folder or "Unfiled" where = folder or "Unfiled"
self._status.showMessage(f"Saving {len(posts)} to {where}...") self._status.showMessage(f"Saving {len(posts)} to {where}...")
try:
dest_dir = saved_folder_dir(folder) if folder else saved_dir()
except ValueError as e:
self._status.showMessage(f"Invalid folder name: {e}")
return
in_flight: set[str] = set()
async def _do(): async def _do():
from ..core.config import saved_dir, saved_folder_dir
import shutil
for i, (idx, post) in enumerate(zip(indices, posts)): for i, (idx, post) in enumerate(zip(indices, posts)):
try: try:
path = await download_image(post.file_url) src = Path(await download_image(post.file_url))
ext = Path(path).suffix save_post_file(src, post, dest_dir, self._db, in_flight)
dest_dir = saved_folder_dir(folder) if folder else saved_dir() self._copy_library_thumb(post)
dest = dest_dir / f"{post.id}{ext}"
if not dest.exists():
shutil.copy2(path, dest)
# Store metadata for library search
self._db.save_library_meta(
post_id=post.id, tags=post.tags,
tag_categories=post.tag_categories,
score=post.score, rating=post.rating,
source=post.source, file_url=post.file_url,
)
self._signals.bookmark_done.emit(idx, f"Saved {i+1}/{len(posts)} to {where}") self._signals.bookmark_done.emit(idx, f"Saved {i+1}/{len(posts)} to {where}")
except Exception as e: except Exception as e:
log.warning(f"Operation failed: {e}") log.warning(f"Bulk save #{post.id} failed: {e}")
self._signals.batch_done.emit(f"Saved {len(posts)} to {where}") self._signals.batch_done.emit(f"Saved {len(posts)} to {where}")
self._run_async(_do) self._run_async(_do)