From 5f9a243fc6f38423f9d32a3bd17b848187a551f6 Mon Sep 17 00:00:00 2001 From: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com> Date: Thu, 3 Sep 2026 16:46:23 -0700 Subject: [PATCH] chore: cleanup after rebase --- docs/relinking.md | 13 ++- src/tagstudio/core/library/alchemy/library.py | 6 +- src/tagstudio/core/macro_parser.py | 110 +++++++++--------- src/tagstudio/qt/controllers/inspector.py | 4 + src/tagstudio/qt/qt_driver.py | 13 +-- 5 files changed, 72 insertions(+), 74 deletions(-) diff --git a/docs/relinking.md b/docs/relinking.md index 75bb3974..8ee5bc2e 100644 --- a/docs/relinking.md +++ b/docs/relinking.md @@ -3,6 +3,9 @@ title: Entry Relinking icon: material/link-variant --- + + + # :material-link-variant: Entry Relinking ### Fix Unlinked Entries @@ -11,15 +14,15 @@ This tool displays the number of unlinked [entries](entries.md), and some option Refresh -- Scans through the library and updates the unlinked entry count. +- Scans through the library and updates the unlinked entry count. Search & Relink -- Attempts to automatically find and reassign missing files. +- Attempts to automatically find and reassign missing files. Delete Unlinked Entries -- Displays a confirmation prompt containing the list of all missing files to be deleted before committing to or cancelling the operation. +- Displays a confirmation prompt containing the list of all missing files to be deleted before committing to or cancelling the operation. ### Fix Duplicate Files @@ -27,8 +30,8 @@ This tool allows for management of duplicate files in the library using a [DupeG Load DupeGuru File -- load the "results" file created from a DupeGuru scan +- load the "results" file created from a DupeGuru scan Mirror Entries -- Duplicate entries will have their contents mirrored across all instances. This allows for duplicate files to then be deleted with DupeGuru as desired, without losing the [field](fields.md) data that has been assigned to either. (Once deleted, the "Fix Unlinked Entries" tool can be used to clean up the duplicates) +- Duplicate entries will have their contents mirrored across all instances. This allows for duplicate files to then be deleted with DupeGuru as desired, without losing the [field](fields.md) data that has been assigned to either. (Once deleted, the "Fix Unlinked Entries" tool can be used to clean up the duplicates) diff --git a/src/tagstudio/core/library/alchemy/library.py b/src/tagstudio/core/library/alchemy/library.py index 9f9dae8a..4a88f357 100644 --- a/src/tagstudio/core/library/alchemy/library.py +++ b/src/tagstudio/core/library/alchemy/library.py @@ -877,8 +877,8 @@ class Library: if search_query: query = query.where( or_( - Tag.name.istartswith(search_query), - Tag.shorthand.istartswith(search_query), + Tag.name.contains(search_query), + Tag.shorthand.contains(search_query), ) ) @@ -886,7 +886,7 @@ class Library: if search_query: query = select(TagAlias.tag_id, TagAlias.name).where( - TagAlias.name.istartswith(search_query) + TagAlias.name.contains(search_query) ) tags.extend(session.execute(query)) diff --git a/src/tagstudio/core/macro_parser.py b/src/tagstudio/core/macro_parser.py index f2d5b748..13efd0f2 100644 --- a/src/tagstudio/core/macro_parser.py +++ b/src/tagstudio/core/macro_parser.py @@ -1,6 +1,5 @@ -# Copyright (C) 2025 Travis Abendshien (CyanVoxel). -# Licensed under the GPL-3.0 License. -# Created for TagStudio: https://github.com/CyanVoxel/TagStudio +# SPDX-FileCopyrightText: (c) TagStudio Contributors +# SPDX-License-Identifier: MIT import json from copy import deepcopy @@ -12,8 +11,6 @@ import structlog import toml from wcmatch import glob -from tagstudio.core.library.alchemy.fields import FieldID - if TYPE_CHECKING: from tagstudio.core.library.alchemy.library import Library from tagstudio.core.library.alchemy.models import Tag @@ -74,16 +71,17 @@ class Instruction: pass -class AddFieldInstruction(Instruction): - def __init__(self, content, name: FieldID, field_type: str) -> None: - super().__init__() - self.content = content - self.name = name - self.type = field_type +# TODO: Reimplement adding fields form macros +# class AddFieldInstruction(Instruction): +# def __init__(self, content, name: FieldID, field_type: str) -> None: +# super().__init__() +# self.content = content +# self.name = name +# self.type = field_type - @override - def __str__(self) -> str: - return str(self.content) +# @override +# def __str__(self) -> str: +# return str(self.content) class AddTagInstruction(Instruction): @@ -329,16 +327,17 @@ def _import_data(table: dict[str, Any], table_key: str, filepath: Path) -> list[ else: continue - # TODO: Determine if the source_type is even really ever needed - # source_type: str = str(tab_value.get(SOURCE_TYPE, "")) + # TODO: Reimplement adding fields form macros + # # TODO: Determine if the source_type is even really ever needed + # # source_type: str = str(tab_value.get(SOURCE_TYPE, "")) - str_name: str = str(obj.get(NAME, FieldID.NOTES.name)) - name: FieldID = FieldID.NOTES - for fid in FieldID: - field_id = str_name.upper().replace(" ", "_") - if field_id == fid.name: - name = fid - continue + # str_name: str = str(obj.get(NAME, FieldID.NOTES.name)) + # name: FieldID = FieldID.NOTES + # for fid in FieldID: + # field_id = str_name.upper().replace(" ", "_") + # if field_id == fid.name: + # name = fid + # continue if ts_type == TAGS: use_context: bool = bool(obj.get(USE_CONTEXT, False)) @@ -395,10 +394,11 @@ def _import_data(table: dict[str, Any], table_key: str, filepath: Path) -> list[ ) ) - elif ts_type in (TEXT_LINE, TEXT_BOX, DATETIME): - results.append( - AddFieldInstruction(content=content_value, name=name, field_type=ts_type) - ) + # TODO: Reimplement adding fields form macros + # elif ts_type in (TEXT_LINE, TEXT_BOX, DATETIME): + # results.append( + # AddFieldInstruction(content=content_value, name=name, field_type=ts_type) + # ) else: logger.error('[MacroParser] [{table_key}] Unknown "{TS_TYPE}"', type=ts_type) @@ -425,25 +425,22 @@ def _add_data(table: dict[str, Any]) -> list[Instruction]: if ts_type == TAGS: tag_strings: list[str] = obj.get(VALUE, []) logger.error(tag_strings) - results.append( - AddTagInstruction( - tag_strings=tag_strings, - use_context=False, - ) - ) - elif ts_type in (TEXT_LINE, TEXT_BOX, DATETIME): - str_name: str = str(obj.get(NAME, FieldID.NOTES.name)) - name: FieldID = FieldID.NOTES - for fid in FieldID: - field_id = str_name.upper().replace(" ", "_") - if field_id == fid.name: - name = fid - continue + results.append(AddTagInstruction(tag_strings=tag_strings, use_context=False)) - content_value: str = str(obj.get(VALUE, "")) - results.append( - AddFieldInstruction(content=content_value, name=name, field_type=ts_type) - ) + # TODO: Reimplement adding fields form macros + # elif ts_type in (TEXT_LINE, TEXT_BOX, DATETIME): + # str_name: str = str(obj.get(NAME, FieldID.NOTES.name)) + # name: FieldID = FieldID.NOTES + # for fid in FieldID: + # field_id = str_name.upper().replace(" ", "_") + # if field_id == fid.name: + # name = fid + # continue + + # content_value: str = str(obj.get(VALUE, "")) + # results.append( + # AddFieldInstruction(content=content_value, name=name, field_type=ts_type) + # ) return results @@ -477,15 +474,16 @@ def _fill_template( return template.replace(f"{{{key}}}", f"{value}") -def exec_instructions(library: "Library", entry_id: int, results: list[Instruction]) -> None: +def exec_instructions(library: Library, entry_id: int, results: list[Instruction]) -> None: for result in results: if isinstance(result, AddTagInstruction): _exec_add_tag(library, entry_id, result) - elif isinstance(result, AddFieldInstruction): - _exec_add_field(library, entry_id, result) + # TODO: Reimplement adding fields form macros + # elif isinstance(result, AddFieldInstruction): + # _exec_add_field(library, entry_id, result) -def _exec_add_tag(library: "Library", entry_id: int, result: AddTagInstruction): +def _exec_add_tag(library: Library, entry_id: int, result: AddTagInstruction): tag_ids: set[int] = set() for string in result.tag_strings: if not string.strip(): @@ -497,12 +495,12 @@ def _exec_add_tag(library: "Library", entry_id: int, result: AddTagInstruction): parent_results: list[int] = [] if len(base_and_parent) > 1: parent = base_and_parent[1].split(")")[0] - r: list[set[Tag]] = library.search_tags(name=parent, limit=-1) + r: tuple[list[Tag], list[Tag]] = library.search_tags(name=parent, limit=-1) if len(r) > 0: parent_results = [t.id for t in r[0]] # NOTE: The following code overlaps with update_tags() in tag_search.py # Sort and prioritize the results - tag_results: list[set[Tag]] = library.search_tags(name=base, limit=-1) + tag_results: tuple[list[Tag], list[Tag]] = library.search_tags(name=base, limit=-1) results_0 = list(tag_results[0]) results_0.sort(key=lambda tag: tag.name.lower()) results_1 = list(tag_results[1]) @@ -538,7 +536,9 @@ def _exec_add_tag(library: "Library", entry_id: int, result: AddTagInstruction): library.add_tags_to_entries(entry_id, tag_ids) -def _exec_add_field(library: "Library", entry_id: int, result: AddFieldInstruction): - library.add_field_to_entry( - entry_id, field_id=result.name, value=result.content, skip_on_exists=True - ) +# TODO: Reimplement adding fields form macros +# def _exec_add_field(library: Library, entry_id: int, result: AddFieldInstruction): +# pass +# library.add_field_to_entry( +# entry_id, field_id=result.name, value=result.content, skip_on_exists=True +# ) diff --git a/src/tagstudio/qt/controllers/inspector.py b/src/tagstudio/qt/controllers/inspector.py index 4e98c546..70a11b23 100644 --- a/src/tagstudio/qt/controllers/inspector.py +++ b/src/tagstudio/qt/controllers/inspector.py @@ -275,6 +275,10 @@ class Inspector(QWidget): except Exception as e: logger.error("[Preview Panel] Error updating selection", error=e) + def refresh_selection(self, update_preview: bool = True): + """Refresh the panel's widgets to use current library data.""" + self.set_selection(self._selected, update_preview) + def stop_media_playback(self) -> None: """Stop any media playback in the inspector.""" self.layout().preview_thumb.media_player.stop() diff --git a/src/tagstudio/qt/qt_driver.py b/src/tagstudio/qt/qt_driver.py index fa5058af..15d51436 100644 --- a/src/tagstudio/qt/qt_driver.py +++ b/src/tagstudio/qt/qt_driver.py @@ -20,8 +20,7 @@ from collections import OrderedDict from functools import partial from pathlib import Path from queue import Queue -from shutil import which -from typing import Generic, TypeVar +from typing import TypeVar from unittest.mock import Mock from warnings import catch_warnings @@ -41,11 +40,6 @@ from PySide6.QtGui import ( QPalette, ) from PySide6.QtWidgets import QApplication, QFileDialog, QMessageBox, QPushButton, QScrollArea -from tagstudio.qt.controllers.ffmpeg_missing_message_box import FfmpegMissingMessageBox -from tagstudio.qt.controllers.fix_ignored_modal_controller import FixIgnoredEntriesModal -from tagstudio.qt.controllers.ignore_modal_controller import IgnoreModal -from tagstudio.qt.controllers.library_info_window_controller import LibraryInfoWindow -from tagstudio.qt.global_settings import DEFAULT_GLOBAL_SETTINGS_PATH, GlobalSettings, Theme import tagstudio.qt.resources_rc # noqa: F401 # pyright: ignore[reportUnusedImport] from tagstudio.core.constants import ( @@ -64,10 +58,7 @@ from tagstudio.core.library.alchemy.models import Entry from tagstudio.core.library.ignore import Ignore from tagstudio.core.library.refresh import RefreshTracker from tagstudio.core.macro_parser import ( - DataResult, - FieldResult, Instruction, - TagResult, exec_instructions, get_macro_name, parse_macro_file, @@ -1821,7 +1812,7 @@ class QtDriver(DriverMixin, QObject): self.main_window.menu_bar.folders_to_tags_action.setEnabled(True) self.main_window.menu_bar.library_info_action.setEnabled(True) - self.main_window.preview_panel.set_selection() + self.main_window.preview_panel.set_selection(self.selected) # page (re)rendering, extract eventually initial_state = BrowsingState(