diff --git a/tagstudio/src/core/library/alchemy/library.py b/tagstudio/src/core/library/alchemy/library.py index 717a8f9a..b51d5523 100644 --- a/tagstudio/src/core/library/alchemy/library.py +++ b/tagstudio/src/core/library/alchemy/library.py @@ -42,6 +42,8 @@ from src.core.library.json.library import Library as JsonLibrary # type: ignore from ...constants import ( BACKUP_FOLDER_NAME, LEGACY_TAG_FIELD_IDS, + RESERVED_TAG_END, + RESERVED_TAG_START, TAG_ARCHIVED, TAG_FAVORITE, TAG_META, @@ -105,7 +107,7 @@ def get_default_tags() -> tuple[Tag, ...]: # The difference in the number of default JSON tags vs default tags in the current version. -DEFAULT_TAG_DIFF: int = len(get_default_tags()) - 2 +DEFAULT_TAG_DIFF: int = len(get_default_tags()) - len([TAG_ARCHIVED, TAG_FAVORITE]) @dataclass(frozen=True) @@ -174,25 +176,30 @@ class Library: # Tags for tag in json_lib.tags: - if tag.id == TAG_ARCHIVED or tag.id == TAG_FAVORITE: - # Update built-in - pass + new_tag = Tag( + id=tag.id, + name=tag.name, + shorthand=tag.shorthand, + color=TagColor.get_color_from_str(tag.color), + ) + if tag.id in range(RESERVED_TAG_START, RESERVED_TAG_END + 1): + self.update_tag(new_tag) # NOTE: This just calls add_tag?? else: - self.add_tag( - Tag( - id=tag.id, - name=tag.name, - shorthand=tag.shorthand, - color=TagColor.get_color_from_str(tag.color), - ) - ) + self.add_tag(new_tag) # Tag Aliases for tag in json_lib.tags: for alias in tag.aliases: if not alias: break - self.add_alias(name=alias, tag_id=tag.id) + # Only add new (user-created) aliases to the default tags. + # This prevents pre-existing built-in aliases from being added as duplicates. + if tag.id in range(RESERVED_TAG_START, RESERVED_TAG_END + 1): + for dt in get_default_tags(): + if dt.id == tag.id and alias not in dt.alias_strings: + self.add_alias(name=alias, tag_id=tag.id) + else: + self.add_alias(name=alias, tag_id=tag.id) # Parent Tags (Previously known as "Subtags" in JSON) for tag in json_lib.tags: @@ -279,13 +286,14 @@ class Library: with Session(self.engine) as session: make_tables(self.engine) - tags = get_default_tags() - try: - session.add_all(tags) - session.commit() - except IntegrityError: - # default tags may exist already - session.rollback() + # Add default tags to new libraries only. + if is_new: + tags = get_default_tags() + try: + session.add_all(tags) + session.commit() + except IntegrityError: + session.rollback() # dont check db version when creating new library if not is_new: diff --git a/tagstudio/src/qt/modals/build_tag.py b/tagstudio/src/qt/modals/build_tag.py index 1bea954a..f9957b30 100644 --- a/tagstudio/src/qt/modals/build_tag.py +++ b/tagstudio/src/qt/modals/build_tag.py @@ -58,6 +58,7 @@ class BuildTagPanel(PanelWidget): def __init__(self, library: Library, tag: Tag | None = None): super().__init__() self.lib = library + self.tag: Tag # NOTE: This gets set at the end of the init. self.setMinimumSize(300, 400) self.root_layout = QVBoxLayout(self) diff --git a/tagstudio/src/qt/modals/tag_database.py b/tagstudio/src/qt/modals/tag_database.py index ac022156..d567e899 100644 --- a/tagstudio/src/qt/modals/tag_database.py +++ b/tagstudio/src/qt/modals/tag_database.py @@ -121,7 +121,7 @@ class TagDatabasePanel(PanelWidget): row.setSpacing(3) if tag.id in range(RESERVED_TAG_START, RESERVED_TAG_END): - tag_widget = TagWidget(tag, has_edit=False, has_remove=False) + tag_widget = TagWidget(tag, has_edit=True, has_remove=False) else: tag_widget = TagWidget(tag, has_edit=True, has_remove=True) @@ -151,9 +151,6 @@ class TagDatabasePanel(PanelWidget): self.update_tags() def edit_tag(self, tag: Tag): - if tag.id in range(RESERVED_TAG_START, RESERVED_TAG_END): - return - build_tag_panel = BuildTagPanel(self.lib, tag=tag) self.edit_modal = PanelModal( diff --git a/tagstudio/src/qt/widgets/migration_modal.py b/tagstudio/src/qt/widgets/migration_modal.py index 2a4896fd..79143d00 100644 --- a/tagstudio/src/qt/widgets/migration_modal.py +++ b/tagstudio/src/qt/widgets/migration_modal.py @@ -19,14 +19,15 @@ from PySide6.QtWidgets import ( ) from sqlalchemy import select from sqlalchemy.orm import Session -from src.core.constants import LEGACY_TAG_FIELD_IDS, RESERVED_TAG_END, TAG_META, TS_FOLDER_NAME +from src.core.constants import LEGACY_TAG_FIELD_IDS, TS_FOLDER_NAME from src.core.enums import LibraryPrefs from src.core.library.alchemy.enums import TagColor from src.core.library.alchemy.joins import TagParent -from src.core.library.alchemy.library import DEFAULT_TAG_DIFF +from src.core.library.alchemy.library import TAG_ARCHIVED, TAG_FAVORITE, TAG_META from src.core.library.alchemy.library import Library as SqliteLibrary from src.core.library.alchemy.models import Entry, TagAlias from src.core.library.json.library import Library as JsonLibrary # type: ignore +from src.core.library.json.library import Tag as JsonTag # type: ignore from src.qt.helpers.custom_runnable import CustomRunnable from src.qt.helpers.function_iterator import FunctionIterator from src.qt.helpers.qbutton_wrapper import QPushButtonWrapper @@ -310,6 +311,7 @@ class JsonMigrationModal(QObject): # Open the JSON Library self.json_lib = JsonLibrary() self.json_lib.open_library(self.path) + self.update_json_builtins() # Update JSON UI self.update_json_entry_count(len(self.json_lib.entries)) @@ -320,6 +322,28 @@ class JsonMigrationModal(QObject): self.migration_progress(skip_ui=skip_ui) self.is_migration_initialized = True + def update_json_builtins(self): + """Updates the built-in JSON values to include any future changes or additions. + + Used to preserve user-modified built-in tags and to + match values between JSON and SQL during parity checking. + """ + # v9.5.0: Add "Meta Tags" tag and parent that to "Archived" and "Favorite". + meta_tags: JsonTag = JsonTag(TAG_META, "Meta Tags", "", ["Meta", "Meta Tag"], [], "") + logger.warning(self.json_lib.tags) + # self.json_lib.add_tag_to_library(meta_tags) + self.json_lib.tags.append(meta_tags) + self.json_lib._map_tag_id_to_index(meta_tags, len(self.json_lib.tags) - 1) + logger.warning(self.json_lib.tags) + + archived_tag: JsonTag = self.json_lib.get_tag(TAG_ARCHIVED) + archived_tag.subtag_ids.append(TAG_META) + self.json_lib.update_tag(archived_tag) + + favorite_tag: JsonTag = self.json_lib.get_tag(TAG_FAVORITE) + favorite_tag.subtag_ids.append(TAG_META) + self.json_lib.update_tag(favorite_tag) + def migration_progress(self, skip_ui: bool = False): """Initialize the progress bar and iterator for the library migration.""" pb = QProgressDialog( @@ -415,7 +439,7 @@ class JsonMigrationModal(QObject): ) self.update_sql_value( self.tags_row, - (len(self.sql_lib.tags) - DEFAULT_TAG_DIFF), + len(self.sql_lib.tags), self.old_tag_count, ) self.update_sql_value( @@ -595,9 +619,6 @@ class JsonMigrationModal(QObject): with Session(self.sql_lib.engine) as session: for tag in self.sql_lib.tags: - # NOTE: Don't check subtag parity for built-in tags. - if tag.id in range(0, RESERVED_TAG_END + 1): - break tag_id = tag.id # Tag IDs start at 0 sql_parent_tags = set( session.scalars(select(TagParent.child_id).where(TagParent.parent_id == tag.id)) @@ -639,10 +660,6 @@ class JsonMigrationModal(QObject): with Session(self.sql_lib.engine) as session: for tag in self.sql_lib.tags: - # NOTE: Do not check alias parity for built-in tags added - # after "Favorite" and "Archived". - if tag.id in range(TAG_META, RESERVED_TAG_END + 1): - break tag_id = tag.id # Tag IDs start at 0 sql_aliases = set( session.scalars(select(TagAlias.name).where(TagAlias.tag_id == tag.id)) @@ -675,13 +692,14 @@ class JsonMigrationModal(QObject): sql_shorthand: str = None json_shorthand: str = None + def sanitize(value): + """Return value or convert a "not" value into None.""" + return value if value else None + for tag in self.sql_lib.tags: - # NOTE: Don't check shorthand parity for built-in tags. - if tag.id in range(0, RESERVED_TAG_END + 1): - break tag_id = tag.id # Tag IDs start at 0 - sql_shorthand = tag.shorthand - json_shorthand = self.json_lib.get_tag(tag_id).shorthand + sql_shorthand = sanitize(tag.shorthand) + json_shorthand = sanitize(self.json_lib.get_tag(tag_id).shorthand) logger.info( "[Shorthand Parity]", @@ -690,11 +708,7 @@ class JsonMigrationModal(QObject): sql_shorthand=sql_shorthand, ) - if not ( - sql_shorthand is not None - and json_shorthand is not None - and (sql_shorthand == json_shorthand) - ): + if sql_shorthand != json_shorthand: self.discrepancies.append( f"[Shorthand Parity][Tag ID: {tag_id}]:" f"\nOLD (JSON):{json_shorthand}\nNEW (SQL):{sql_shorthand}" @@ -711,9 +725,6 @@ class JsonMigrationModal(QObject): json_color: str = None for tag in self.sql_lib.tags: - # NOTE: Don't check tag color parity for built-in tags. - if tag.id in range(0, RESERVED_TAG_END + 1): - break tag_id = tag.id # Tag IDs start at 0 sql_color = tag.color.name json_color = (