Store tag categories in library metadata, unsave from bookmarks

- tag_categories stored as JSON in library_meta table
- Library and Bookmarks info panels show categorized tags
- Bookmarks falls back to library_meta for categories
- Added Unsave from Library to bookmarks right-click menu
This commit is contained in:
pax 2026-04-05 16:46:48 -05:00
parent 96740acb4c
commit 29ffe0be7a
3 changed files with 40 additions and 18 deletions

View File

@ -62,6 +62,7 @@ CREATE TABLE IF NOT EXISTS blacklisted_posts (
CREATE TABLE IF NOT EXISTS library_meta ( CREATE TABLE IF NOT EXISTS library_meta (
post_id INTEGER PRIMARY KEY, post_id INTEGER PRIMARY KEY,
tags TEXT NOT NULL DEFAULT '', tags TEXT NOT NULL DEFAULT '',
tag_categories TEXT DEFAULT '',
score INTEGER DEFAULT 0, score INTEGER DEFAULT 0,
rating TEXT, rating TEXT,
source TEXT, source TEXT,
@ -451,19 +452,30 @@ class Database:
# -- Library Metadata -- # -- Library Metadata --
def save_library_meta(self, post_id: int, tags: str = "", score: int = 0, def save_library_meta(self, post_id: int, tags: str = "", tag_categories: dict = None,
rating: str = None, source: str = None, file_url: str = None) -> None: score: int = 0, rating: str = None, source: str = None,
file_url: str = None) -> None:
import json
from datetime import datetime, timezone from datetime import datetime, timezone
cats_json = json.dumps(tag_categories) if tag_categories else ""
self.conn.execute( self.conn.execute(
"INSERT OR REPLACE INTO library_meta (post_id, tags, score, rating, source, file_url, saved_at) " "INSERT OR REPLACE INTO library_meta "
"VALUES (?, ?, ?, ?, ?, ?, ?)", "(post_id, tags, tag_categories, score, rating, source, file_url, saved_at) "
(post_id, tags, score, rating, source, file_url, datetime.now(timezone.utc).isoformat()), "VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
(post_id, tags, cats_json, score, rating, source, file_url,
datetime.now(timezone.utc).isoformat()),
) )
self.conn.commit() self.conn.commit()
def get_library_meta(self, post_id: int) -> dict | None: def get_library_meta(self, post_id: int) -> dict | None:
import json
row = self.conn.execute("SELECT * FROM library_meta WHERE post_id = ?", (post_id,)).fetchone() row = self.conn.execute("SELECT * FROM library_meta WHERE post_id = ?", (post_id,)).fetchone()
return dict(row) if row else None if not row:
return None
d = dict(row)
cats = d.get("tag_categories", "")
d["tag_categories"] = json.loads(cats) if cats else {}
return d
def search_library_meta(self, query: str) -> set[int]: def search_library_meta(self, query: str) -> set[int]:
"""Search library metadata by tags. Returns matching post IDs.""" """Search library metadata by tags. Returns matching post IDs."""

View File

@ -1127,7 +1127,7 @@ class BooruApp(QMainWindow):
id=int(stem), file_url=meta.get("file_url", ""), id=int(stem), file_url=meta.get("file_url", ""),
preview_url=None, tags=meta.get("tags", ""), preview_url=None, tags=meta.get("tags", ""),
score=meta.get("score", 0), rating=meta.get("rating"), score=meta.get("score", 0), rating=meta.get("rating"),
source=meta.get("source"), tag_categories={}, source=meta.get("source"), tag_categories=meta.get("tag_categories", {}),
) )
self._info_panel.set_post(p) self._info_panel.set_post(p)
@ -1143,7 +1143,7 @@ class BooruApp(QMainWindow):
id=int(stem), file_url=meta.get("file_url", ""), id=int(stem), file_url=meta.get("file_url", ""),
preview_url=None, tags=meta.get("tags", ""), preview_url=None, tags=meta.get("tags", ""),
score=meta.get("score", 0), rating=meta.get("rating"), score=meta.get("score", 0), rating=meta.get("rating"),
source=meta.get("source"), tag_categories={}, source=meta.get("source"), tag_categories=meta.get("tag_categories", {}),
) )
self._info_panel.set_post(p) self._info_panel.set_post(p)
@ -1152,11 +1152,14 @@ class BooruApp(QMainWindow):
# Show bookmark tags in info panel # Show bookmark tags in info panel
if self._info_panel.isVisible(): if self._info_panel.isVisible():
from ..core.api.base import Post from ..core.api.base import Post
# Try library metadata for categories
meta = self._db.get_library_meta(fav.post_id)
cats = meta.get("tag_categories", {}) if meta else {}
p = Post( p = Post(
id=fav.post_id, file_url=fav.file_url or "", id=fav.post_id, file_url=fav.file_url or "",
preview_url=fav.preview_url, tags=fav.tags or "", preview_url=fav.preview_url, tags=fav.tags or "",
score=fav.score or 0, rating=fav.rating, score=fav.score or 0, rating=fav.rating,
source=fav.source, tag_categories={}, source=fav.source, tag_categories=cats,
) )
self._info_panel.set_post(p) self._info_panel.set_post(p)
self._on_bookmark_activated(fav) self._on_bookmark_activated(fav)
@ -1818,8 +1821,10 @@ class BooruApp(QMainWindow):
# Store metadata for library search # Store metadata for library search
self._db.save_library_meta( self._db.save_library_meta(
post_id=post.id, tags=post.tags, score=post.score, post_id=post.id, tags=post.tags,
rating=post.rating, source=post.source, file_url=post.file_url, tag_categories=post.tag_categories,
score=post.score, rating=post.rating,
source=post.source, file_url=post.file_url,
) )
where = folder or "Unsorted" where = folder or "Unsorted"

View File

@ -230,6 +230,7 @@ class BookmarksView(QWidget):
save_lib_menu.addSeparator() save_lib_menu.addSeparator()
save_lib_new = save_lib_menu.addAction("+ New Folder...") save_lib_new = save_lib_menu.addAction("+ New Folder...")
unsave_lib = menu.addAction("Unsave from Library")
copy_file = menu.addAction("Copy File to Clipboard") copy_file = menu.addAction("Copy File to Clipboard")
copy_url = menu.addAction("Copy Image URL") copy_url = menu.addAction("Copy Image URL")
copy_tags = menu.addAction("Copy Tags") copy_tags = menu.addAction("Copy Tags")
@ -277,6 +278,10 @@ class BookmarksView(QWidget):
if dest: if dest:
import shutil import shutil
shutil.copy2(src, dest) shutil.copy2(src, dest)
elif action == unsave_lib:
from ..core.cache import delete_from_library
if delete_from_library(fav.post_id, fav.folder):
self.refresh()
elif action == copy_file: elif action == copy_file:
path = fav.cached_path path = fav.cached_path
if path and Path(path).exists(): if path and Path(path).exists():