diff --git a/docs/library-changes.md b/docs/library-changes.md index 6de13d274..1c78aad82 100644 --- a/docs/library-changes.md +++ b/docs/library-changes.md @@ -128,7 +128,15 @@ Migration from the legacy JSON format is provided via a walkthrough when opening | Used From | Format | Location | | ----------------------------------------------------------------------- | ------ | ----------------------------------------------- | -| [#1139](https://github.com/TagStudioDev/TagStudio/pull/1139) | SQLite | ``/.TagStudio/ts_library.sqlite | +| [#1139](https://github.com/TagStudioDev/TagStudio/pull/1139) | SQLite | ``/.TagStudio/ts_library.sqlite | - Adds the `is_hidden` column to the `tags` table (default `0`). Used for excluding entries tagged with hidden tags from library searches. -- Sets the `is_hidden` field on the built-in Archived tag to `1`, to match the Archived tag now being hidden by default. \ No newline at end of file +- Sets the `is_hidden` field on the built-in Archived tag to `1`, to match the Archived tag now being hidden by default. + +#### Version 104 + +| Used From | Format | Location | +| ----------------------------------------------------------------------- | ------ | ----------------------------------------------- | +| [#1298](https://github.com/TagStudioDev/TagStudio/pull/1298) | SQLite | ``/.TagStudio/ts_library.sqlite | + +- Removes the `preferences` table, after migrating the contained extension list to the .ts_ignore file, if necessary. diff --git a/src/tagstudio/core/enums.py b/src/tagstudio/core/enums.py index 4284fe407..2bc337480 100644 --- a/src/tagstudio/core/enums.py +++ b/src/tagstudio/core/enums.py @@ -3,8 +3,6 @@ # Created for TagStudio: https://github.com/CyanVoxel/TagStudio import enum -from typing import Any -from uuid import uuid4 class SettingItems(str, enum.Enum): @@ -57,30 +55,3 @@ class MacroID(enum.Enum): BUILD_URL = "build_url" MATCH = "match" CLEAN_URL = "clean_url" - - -class DefaultEnum(enum.Enum): - """Allow saving multiple identical values in property called .default.""" - - default: Any - - def __new__(cls, value): - # Create the enum instance - obj = object.__new__(cls) - # make value random - obj._value_ = uuid4() - # assign the actual value into .default property - obj.default = value - return obj - - @property - def value(self): - raise AttributeError("access the value via .default property instead") - - -# TODO: Remove DefaultEnum and LibraryPrefs classes once remaining values are removed. -class LibraryPrefs(DefaultEnum): - """Library preferences with default value accessible via .default property.""" - - IS_EXCLUDE_LIST = True - EXTENSION_LIST = [".json", ".xmp", ".aae"] diff --git a/src/tagstudio/core/library/alchemy/constants.py b/src/tagstudio/core/library/alchemy/constants.py index 1e2080249..ffc6ede63 100644 --- a/src/tagstudio/core/library/alchemy/constants.py +++ b/src/tagstudio/core/library/alchemy/constants.py @@ -8,10 +8,9 @@ SQL_FILENAME: str = "ts_library.sqlite" JSON_FILENAME: str = "ts_library.json" -DB_VERSION_LEGACY_KEY: str = "DB_VERSION" DB_VERSION_CURRENT_KEY: str = "CURRENT" DB_VERSION_INITIAL_KEY: str = "INITIAL" -DB_VERSION: int = 103 +DB_VERSION: int = 104 TAG_CHILDREN_QUERY = text(""" WITH RECURSIVE ChildTags AS ( diff --git a/src/tagstudio/core/library/alchemy/library.py b/src/tagstudio/core/library/alchemy/library.py index 1ce4fc85f..006ea908f 100644 --- a/src/tagstudio/core/library/alchemy/library.py +++ b/src/tagstudio/core/library/alchemy/library.py @@ -16,7 +16,7 @@ from datetime import UTC, datetime from os import makedirs from pathlib import Path -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from uuid import uuid4 from warnings import catch_warnings @@ -52,7 +52,6 @@ noload, selectinload, ) -from typing_extensions import deprecated from tagstudio.core.constants import ( BACKUP_FOLDER_NAME, @@ -66,13 +65,11 @@ TAG_META, TS_FOLDER_NAME, ) -from tagstudio.core.enums import LibraryPrefs from tagstudio.core.library.alchemy import default_color_groups from tagstudio.core.library.alchemy.constants import ( DB_VERSION, DB_VERSION_CURRENT_KEY, DB_VERSION_INITIAL_KEY, - DB_VERSION_LEGACY_KEY, JSON_FILENAME, SQL_FILENAME, TAG_CHILDREN_QUERY, @@ -95,7 +92,6 @@ Entry, Folder, Namespace, - Preferences, Tag, TagAlias, TagColorGroup, @@ -103,6 +99,7 @@ Version, ) from tagstudio.core.library.alchemy.visitors import SQLBoolExpressionBuilder +from tagstudio.core.library.ignore import migrate_ext_list from tagstudio.core.library.json.library import Library as JsonLibrary from tagstudio.core.utils.types import unwrap from tagstudio.qt.translations import Translations @@ -217,7 +214,6 @@ class Library: """Class for the Library object, and all CRUD operations made upon it.""" library_dir: Path | None = None - storage_path: Path | str | None = None engine: Engine | None = None folder: Folder | None = None included_files: set[Path] = set() @@ -232,7 +228,6 @@ def close(self): if self.engine: self.engine.dispose() self.library_dir = None - self.storage_path = None self.folder = None self.included_files = set() @@ -319,9 +314,10 @@ def migrate_json_to_sqlite(self, json_lib: JsonLibrary): value=v, ) - # Preferences - self.set_prefs(LibraryPrefs.EXTENSION_LIST, [x.strip(".") for x in json_lib.ext_list]) - self.set_prefs(LibraryPrefs.IS_EXCLUDE_LIST, json_lib.is_exclude_list) + # extension include/exclude list + (unwrap(self.library_dir) / TS_FOLDER_NAME / IGNORE_NAME).write_text( + migrate_ext_list([x.strip(".") for x in json_lib.ext_list], json_lib.is_exclude_list) + ) end_time = time.time() logger.info(f"Library Converted! ({format_timespan(end_time - start_time)})") @@ -348,33 +344,36 @@ def tag_display_name(self, tag: Tag | None) -> str: else: return tag.name - def open_library( - self, library_dir: Path, storage_path: Path | str | None = None - ) -> LibraryStatus: - is_new: bool = True - if storage_path == ":memory:": - self.storage_path = storage_path - is_new = True - return self.open_sqlite_library(library_dir, is_new) - else: - self.storage_path = library_dir / TS_FOLDER_NAME / SQL_FILENAME - assert isinstance(self.storage_path, Path) - if self.verify_ts_folder(library_dir) and (is_new := not self.storage_path.exists()): - json_path = library_dir / TS_FOLDER_NAME / JSON_FILENAME - if json_path.exists(): - return LibraryStatus( - success=False, - library_path=library_dir, - message="[JSON] Legacy v9.4 library requires conversion to v9.5+", - json_migration_req=True, - ) + def open_library(self, library_dir: Path, in_memory: bool = False) -> LibraryStatus: + """Wrapper for open_sqlite_library. + + Handles in-memory storage and checks whether a JSON-migration is necessary. + """ + assert isinstance(library_dir, Path) + + if in_memory: + return self.open_sqlite_library(library_dir, is_new=True, storage_path=":memory:") + + is_new = True + sql_path = library_dir / TS_FOLDER_NAME / SQL_FILENAME + if self.verify_ts_folder(library_dir) and (is_new := not sql_path.exists()): + json_path = library_dir / TS_FOLDER_NAME / JSON_FILENAME + if json_path.exists(): + return LibraryStatus( + success=False, + library_path=library_dir, + message="[JSON] Legacy v9.4 library requires conversion to v9.5+", + json_migration_req=True, + ) - return self.open_sqlite_library(library_dir, is_new) + return self.open_sqlite_library(library_dir, is_new, str(sql_path)) - def open_sqlite_library(self, library_dir: Path, is_new: bool) -> LibraryStatus: + def open_sqlite_library( + self, library_dir: Path, is_new: bool, storage_path: str + ) -> LibraryStatus: connection_string = URL.create( drivername="sqlite", - database=str(self.storage_path), + database=storage_path, ) # NOTE: File-based databases should use NullPool to create new DB connection in order to # keep connections on separate threads, which prevents the DB files from being locked @@ -383,7 +382,7 @@ def open_sqlite_library(self, library_dir: Path, is_new: bool) -> LibraryStatus: # More info can be found on the SQLAlchemy docs: # https://docs.sqlalchemy.org/en/20/changelog/migration_07.html # Under -> sqlite-the-sqlite-dialect-now-uses-nullpool-for-file-based-databases - poolclass = None if self.storage_path == ":memory:" else NullPool + poolclass = None if storage_path == ":memory:" else NullPool loaded_db_version: int = 0 logger.info( @@ -421,8 +420,8 @@ def open_sqlite_library(self, library_dir: Path, is_new: bool) -> LibraryStatus: logger.info(f"[Library] DB_VERSION: {loaded_db_version}") make_tables(self.engine) - # Add default tag color namespaces. if is_new: + # Add default tag color namespaces. namespaces = default_color_groups.namespaces() try: session.add_all(namespaces) @@ -431,8 +430,7 @@ def open_sqlite_library(self, library_dir: Path, is_new: bool) -> LibraryStatus: logger.error("[Library] Couldn't add default tag color namespaces", error=e) session.rollback() - # Add default tag colors. - if is_new: + # Add default tag colors. tag_colors: list[TagColorGroup] = default_color_groups.standard() tag_colors += default_color_groups.pastels() tag_colors += default_color_groups.shades() @@ -447,8 +445,7 @@ def open_sqlite_library(self, library_dir: Path, is_new: bool) -> LibraryStatus: logger.error("[Library] Couldn't add default tag colors", error=e) session.rollback() - # Add default tags. - if is_new: + # Add default tags. tags = get_default_tags() try: session.add_all(tags) @@ -458,15 +455,6 @@ def open_sqlite_library(self, library_dir: Path, is_new: bool) -> LibraryStatus: # Ensure version rows are present with catch_warnings(record=True): - # NOTE: The "Preferences" table is depreciated and will be removed in the future. - # The DB_VERSION is still being set to it in order to remain backwards-compatible - # with existing TagStudio versions until it is removed. - try: - session.add(Preferences(key=DB_VERSION_LEGACY_KEY, value=DB_VERSION)) - session.commit() - except IntegrityError: - session.rollback() - try: initial = DB_VERSION if is_new else 100 session.add(Version(key=DB_VERSION_INITIAL_KEY, value=initial)) @@ -480,15 +468,6 @@ def open_sqlite_library(self, library_dir: Path, is_new: bool) -> LibraryStatus: except IntegrityError: session.rollback() - # TODO: Remove this "Preferences" system. - for pref in LibraryPrefs: - with catch_warnings(record=True): - try: - session.add(Preferences(key=pref.name, value=pref.default)) - session.commit() - except IntegrityError: - session.rollback() - for field in FieldID: try: session.add( @@ -529,36 +508,36 @@ def open_sqlite_library(self, library_dir: Path, is_new: bool) -> LibraryStatus: # Apply any post-SQL migration patches. if not is_new: + assert loaded_db_version >= 6 + # save backup if patches will be applied if loaded_db_version < DB_VERSION: self.library_dir = library_dir self.save_library_backup_to_disk() self.library_dir = None - # NOTE: Depending on the data, some data and schema changes need to be applied in - # different orders. This chain of methods can likely be cleaned up and/or moved. + # migrate DB step by step from one version to the next + if loaded_db_version < 7: + # changes: value_type, tags + self.__apply_db7_migration(session) if loaded_db_version < 8: - self.__apply_db8_schema_changes(session) - if loaded_db_version < 9: - self.__apply_db9_schema_changes(session) - if loaded_db_version < 103: - self.__apply_db103_schema_changes(session) - if loaded_db_version == 6: - self.__apply_repairs_for_db6(session) - - if loaded_db_version >= 6 and loaded_db_version < 8: - self.__apply_db8_default_data(session) + # changes: tag_colors + self.__apply_db8_migration(session) if loaded_db_version < 9: - self.__apply_db9_filename_population(session) + # changes: entries + self.__apply_db9_migration(session) if loaded_db_version < 100: - self.__apply_db100_parent_repairs(session) + # changes: tag_parents + self.__apply_db100_migration(session) if loaded_db_version < 102: - self.__apply_db102_repairs(session) + # changes: tag_parents + self.__apply_db102_migration(session) if loaded_db_version < 103: - self.__apply_db103_default_data(session) - - # Convert file extension list to ts_ignore file, if a .ts_ignore file does not exist - self.migrate_sql_to_ts_ignore(library_dir) + # changes: tags + self.__apply_db103_migration(session) + if loaded_db_version < 104: + # changes: deletes preferences + self.__apply_db104_migrations(session, library_dir) # Update DB_VERSION if loaded_db_version < DB_VERSION: @@ -568,8 +547,8 @@ def open_sqlite_library(self, library_dir: Path, is_new: bool) -> LibraryStatus: self.library_dir = library_dir return LibraryStatus(success=True, library_path=library_dir) - def __apply_repairs_for_db6(self, session: Session): - """Apply database repairs introduced in DB_VERSION 7.""" + def __apply_db7_migration(self, session: Session): + """Migrate DB from DB_VERSION 6 to 7.""" logger.info("[Library][Migration] Applying patches to DB_VERSION: 6 library...") with session: # Repair "Description" fields with a TEXT_LINE key instead of a TEXT_BOX key. @@ -582,7 +561,7 @@ def __apply_repairs_for_db6(self, session: Session): session.flush() # Repair tags that may have a disambiguation_id pointing towards a deleted tag. - all_tag_ids: set[int] = {tag.id for tag in self.tags} + all_tag_ids = session.scalars(text("SELECT DISTINCT id FROM tags")).all() disam_stmt = ( update(Tag) .where(Tag.disambiguation_id.not_in(all_tag_ids)) @@ -591,9 +570,8 @@ def __apply_repairs_for_db6(self, session: Session): session.execute(disam_stmt) session.commit() - def __apply_db8_schema_changes(self, session: Session): - """Apply database schema changes introduced in DB_VERSION 8.""" - # TODO: Use Alembic for this part instead + def __apply_db8_migration(self, session: Session): + """Migrate DB from DB_VERSION 7 to 8.""" # Add the missing color_border column to the TagColorGroups table. color_border_stmt = text( "ALTER TABLE tag_colors ADD COLUMN color_border BOOLEAN DEFAULT FALSE NOT NULL" @@ -609,8 +587,7 @@ def __apply_db8_schema_changes(self, session: Session): ) session.rollback() - def __apply_db8_default_data(self, session: Session): - """Apply default data changes introduced in DB_VERSION 8.""" + # collect new default tag colors tag_colors: list[TagColorGroup] = default_color_groups.standard() tag_colors += default_color_groups.pastels() tag_colors += default_color_groups.shades() @@ -659,8 +636,9 @@ def __apply_db8_default_data(self, session: Session): ) session.rollback() - def __apply_db9_schema_changes(self, session: Session): - """Apply database schema changes introduced in DB_VERSION 9.""" + def __apply_db9_migration(self, session: Session): + """Migrate DB from DB_VERSION 8 to 9.""" + # Apply database schema changes add_filename_column = text( "ALTER TABLE entries ADD COLUMN filename TEXT NOT NULL DEFAULT ''" ) @@ -675,15 +653,14 @@ def __apply_db9_schema_changes(self, session: Session): ) session.rollback() - def __apply_db9_filename_population(self, session: Session): - """Populate the filename column introduced in DB_VERSION 9.""" + # Populate the new filename column. for entry in self.all_entries(): session.merge(entry).filename = entry.path.name session.commit() logger.info("[Library][Migration] Populated filename column in entries table") - def __apply_db100_parent_repairs(self, session: Session): - """Swap the child_id and parent_id values in the TagParent table.""" + def __apply_db100_migration(self, session: Session): + """Migrate DB to DB_VERSION 100.""" with session: # Repair parent-child tag relationships that are the wrong way around. stmt = update(TagParent).values( @@ -694,17 +671,18 @@ def __apply_db100_parent_repairs(self, session: Session): session.commit() logger.info("[Library][Migration] Refactored TagParent table") - def __apply_db102_repairs(self, session: Session): - """Repair tag_parents rows with references to deleted tags.""" + def __apply_db102_migration(self, session: Session): + """Migrate DB to DB_VERSION 102.""" with session: - all_tag_ids: list[int] = [t.id for t in self.tags] + all_tag_ids = session.scalars(text("SELECT DISTINCT id FROM tags")).all() stmt = delete(TagParent).where(TagParent.parent_id.not_in(all_tag_ids)) session.execute(stmt) session.commit() logger.info("[Library][Migration] Verified TagParent table data") - def __apply_db103_schema_changes(self, session: Session): - """Apply database schema changes introduced in DB_VERSION 103.""" + def __apply_db103_migration(self, session: Session): + """Migrate DB from DB_VERSION 102 to 103.""" + # add the new hidden column for tags add_is_hidden_column = text( "ALTER TABLE tags ADD COLUMN is_hidden BOOLEAN NOT NULL DEFAULT 0" ) @@ -719,8 +697,7 @@ def __apply_db103_schema_changes(self, session: Session): ) session.rollback() - def __apply_db103_default_data(self, session: Session): - """Apply default data changes introduced in DB_VERSION 103.""" + # mark the "Archived" tag as hidden try: session.query(Tag).filter(Tag.id == TAG_ARCHIVED).update({"is_hidden": True}) session.commit() @@ -733,33 +710,30 @@ def __apply_db103_default_data(self, session: Session): ) session.rollback() - def migrate_sql_to_ts_ignore(self, library_dir: Path): - # Do not continue if existing '.ts_ignore' file is found - if Path(library_dir / TS_FOLDER_NAME / IGNORE_NAME).exists(): - return + def __apply_db104_migrations(self, session: Session, library_dir: Path): + """Migrate DB from DB_VERSION 103 to 104.""" + # Convert file extension list to ts_ignore file, if a .ts_ignore file does not exist + self.__migrate_sql_to_ts_ignore(library_dir) + session.execute(text("DROP TABLE preferences")) + session.commit() - # Create blank '.ts_ignore' file - ts_ignore_template = ( - Path(__file__).parents[3] / "resources/templates/ts_ignore_template_blank.txt" - ) + def __migrate_sql_to_ts_ignore(self, library_dir: Path): + # Do not continue if existing '.ts_ignore' file is found ts_ignore = library_dir / TS_FOLDER_NAME / IGNORE_NAME - try: - shutil.copy2(ts_ignore_template, ts_ignore) - except Exception as e: - logger.error("[ERROR][Library] Could not generate '.ts_ignore' file!", error=e) + if Path(ts_ignore).exists(): + return # Load legacy extension data - extensions: list[str] = self.prefs(LibraryPrefs.EXTENSION_LIST) # pyright: ignore - is_exclude_list: bool = self.prefs(LibraryPrefs.IS_EXCLUDE_LIST) # pyright: ignore - - # Copy extensions to '.ts_ignore' file - if ts_ignore.exists(): - with open(ts_ignore, "a") as f: - prefix = "" - if not is_exclude_list: - prefix = "!" - f.write("*\n") - f.writelines([f"{prefix}*.{x.lstrip('.')}\n" for x in extensions]) + with Session(self.engine) as session: + extensions: list[str] = unwrap( + session.scalar(text("SELECT value FROM preferences WHERE key = 'EXTENSION_LIST'")) + ) + is_exclude_list: bool = unwrap( + session.scalar(text("SELECT value FROM preferences WHERE key = 'IS_EXCLUDE_LIST'")) + ) + + with open(ts_ignore, "w") as f: + f.write(migrate_ext_list(extensions, is_exclude_list)) @property def default_fields(self) -> list[BaseField]: @@ -1851,19 +1825,20 @@ def get_version(self, key: str) -> int: engine = sqlalchemy.inspect(self.engine) try: # "Version" table added in DB_VERSION 101 - if engine and engine.has_table("Version"): + if engine and engine.has_table("versions"): version = session.scalar(select(Version).where(Version.key == key)) assert version return version.value # NOTE: The "Preferences" table has been depreciated as of TagStudio 9.5.4 # and is set to be removed in a future release. else: - pref_version = session.scalar( - select(Preferences).where(Preferences.key == DB_VERSION_LEGACY_KEY) + return int( + unwrap( + session.scalar( + text("SELECT value FROM preferences WHERE key == 'DB_VERSION'") + ) + ) ) - assert pref_version - assert isinstance(pref_version.value, int) - return pref_version.value except Exception: return 0 @@ -1881,60 +1856,10 @@ def set_version(self, key: str, value: int) -> None: version.value = value session.add(version) session.commit() - - # If a depreciated "Preferences" table is found, update the version value to be read - # by older TagStudio versions. - engine = sqlalchemy.inspect(self.engine) - if engine and engine.has_table("Preferences"): - pref = unwrap( - session.scalar( - select(Preferences).where(Preferences.key == DB_VERSION_LEGACY_KEY) - ) - ) - pref.value = value # pyright: ignore - session.add(pref) - session.commit() except (IntegrityError, AssertionError) as e: logger.error("[Library][ERROR] Couldn't add default tag color namespaces", error=e) session.rollback() - # TODO: Remove this once the 'preferences' table is removed. - @deprecated("Use `get_version() for version and `ts_ignore` system for extension exclusion.") - def prefs(self, key: str | LibraryPrefs): # pyright: ignore[reportUnknownParameterType] - # load given item from Preferences table - with Session(self.engine) as session: - if isinstance(key, LibraryPrefs): - return unwrap( - session.scalar(select(Preferences).where(Preferences.key == key.name)) - ).value # pyright: ignore[reportUnknownVariableType] - else: - return unwrap( - session.scalar(select(Preferences).where(Preferences.key == key)) - ).value # pyright: ignore[reportUnknownVariableType] - - # TODO: Remove this once the 'preferences' table is removed. - @deprecated("Use `get_version() for version and `ts_ignore` system for extension exclusion.") - def set_prefs(self, key: str | LibraryPrefs, value: Any) -> None: # pyright: ignore[reportExplicitAny] - # set given item in Preferences table - with Session(self.engine) as session: - # load existing preference and update value - stuff = session.scalars(select(Preferences)) - logger.info([x.key for x in list(stuff)]) - - pref: Preferences = unwrap( - session.scalar( - select(Preferences).where( - Preferences.key == (key.name if isinstance(key, LibraryPrefs) else key) - ) - ) - ) - - logger.info("loading pref", pref=pref, key=key, value=value) - pref.value = value - session.add(pref) - session.commit() - # TODO - try/except - def mirror_entry_fields(self, *entries: Entry) -> None: """Mirror fields among multiple Entry items.""" fields = {} diff --git a/src/tagstudio/core/library/alchemy/models.py b/src/tagstudio/core/library/alchemy/models.py index f5c315310..170a666b0 100644 --- a/src/tagstudio/core/library/alchemy/models.py +++ b/src/tagstudio/core/library/alchemy/models.py @@ -6,9 +6,8 @@ from pathlib import Path from typing import override -from sqlalchemy import JSON, ForeignKey, ForeignKeyConstraint, Integer, event +from sqlalchemy import ForeignKey, ForeignKeyConstraint, Integer, event from sqlalchemy.orm import Mapped, mapped_column, relationship -from typing_extensions import deprecated from tagstudio.core.constants import TAG_ARCHIVED, TAG_FAVORITE from tagstudio.core.library.alchemy.db import Base, PathType @@ -327,16 +326,6 @@ def slugify_field_key(mapper, connection, target): # pyright: ignore target.key = slugify(target.tag) -# NOTE: The "Preferences" table has been depreciated as of TagStudio 9.5.4 -# and is set to be removed in a future release. -@deprecated("Use `Version` for storing version, and `ts_ignore` system for file exclusion.") -class Preferences(Base): - __tablename__ = "preferences" - - key: Mapped[str] = mapped_column(primary_key=True) - value: Mapped[dict] = mapped_column(JSON, nullable=False) - - class Version(Base): __tablename__ = "versions" diff --git a/src/tagstudio/core/library/ignore.py b/src/tagstudio/core/library/ignore.py index e3eda7ed1..79c70c54e 100644 --- a/src/tagstudio/core/library/ignore.py +++ b/src/tagstudio/core/library/ignore.py @@ -91,6 +91,23 @@ def ignore_to_glob(ignore_patterns: list[str]) -> list[str]: return glob_patterns +def migrate_ext_list(exts: list[str], is_exclude_list: bool) -> str: + # read template + ts_ignore_template = ( + Path(__file__).parents[2] / "resources/templates/ts_ignore_template_blank.txt" + ) + with open(ts_ignore_template) as f: + out = f.read() + + # actual conversion + prefix = "" + if not is_exclude_list: + prefix = "!" + out += "*\n" + out += "\n".join([f"{prefix}*.{x.lstrip('.')}\n" for x in exts]) + return out + + class Ignore(metaclass=Singleton): """Class for processing and managing glob-like file ignore file patterns.""" diff --git a/src/tagstudio/qt/mixed/migration_modal.py b/src/tagstudio/qt/mixed/migration_modal.py index 56fd2bbb3..4508e8b38 100644 --- a/src/tagstudio/qt/mixed/migration_modal.py +++ b/src/tagstudio/qt/mixed/migration_modal.py @@ -8,6 +8,7 @@ from typing import cast import structlog +import wcmatch.fnmatch as fnmatch from PySide6.QtCore import QObject, Qt, QThreadPool, Signal from PySide6.QtWidgets import ( QApplication, @@ -24,18 +25,19 @@ from sqlalchemy.orm import Session from tagstudio.core.constants import ( + IGNORE_NAME, LEGACY_TAG_FIELD_IDS, TAG_ARCHIVED, TAG_FAVORITE, TAG_META, TS_FOLDER_NAME, ) -from tagstudio.core.enums import LibraryPrefs from tagstudio.core.library.alchemy import default_color_groups from tagstudio.core.library.alchemy.constants import SQL_FILENAME from tagstudio.core.library.alchemy.joins import TagParent from tagstudio.core.library.alchemy.library import Library as SqliteLibrary from tagstudio.core.library.alchemy.models import Entry, TagAlias +from tagstudio.core.library.ignore import PATH_GLOB_FLAGS, Ignore, ignore_to_glob from tagstudio.core.library.json.library import Library as JsonLibrary from tagstudio.core.library.json.library import Tag as JsonTag from tagstudio.core.utils.types import unwrap @@ -72,8 +74,6 @@ def __init__(self, path: Path): self.old_entry_count: int = 0 self.old_tag_count: int = 0 - self.old_ext_count: int = 0 - self.old_ext_type: bool = None # pyright: ignore[reportAttributeAccessIssue] self.field_parity: bool = False self.path_parity: bool = False @@ -82,6 +82,7 @@ def __init__(self, path: Path): self.subtag_parity: bool = False self.alias_parity: bool = False self.color_parity: bool = False + self.ext_parity: bool = False self.init_page_info() self.init_page_convert() @@ -129,8 +130,7 @@ def init_page_convert(self) -> None: parent_tags_text: str = tab + Translations["json_migration.heading.parent_tags"] aliases_text: str = tab + Translations["json_migration.heading.aliases"] colors_text: str = tab + Translations["json_migration.heading.colors"] - ext_text: str = Translations["json_migration.heading.file_extension_list"] - ext_type_text: str = Translations["json_migration.heading.extension_list_type"] + ext_parity_text: str = Translations["json_migration.heading.extensions"] desc_text: str = Translations["json_migration.description"] path_parity_text: str = tab + Translations["json_migration.heading.paths"] field_parity_text: str = tab + Translations["library_info.stats.fields"] @@ -145,7 +145,6 @@ def init_page_convert(self) -> None: self.aliases_row: int = 7 self.colors_row: int = 8 self.ext_row: int = 9 - self.ext_type_row: int = 10 old_lib_container: QWidget = QWidget() old_lib_layout: QVBoxLayout = QVBoxLayout(old_lib_container) @@ -166,8 +165,7 @@ def init_page_convert(self) -> None: self.old_content_layout.addWidget(QLabel(parent_tags_text), self.parent_tags_row, 0) self.old_content_layout.addWidget(QLabel(aliases_text), self.aliases_row, 0) self.old_content_layout.addWidget(QLabel(colors_text), self.colors_row, 0) - self.old_content_layout.addWidget(QLabel(ext_text), self.ext_row, 0) - self.old_content_layout.addWidget(QLabel(ext_type_text), self.ext_type_row, 0) + self.old_content_layout.addWidget(QLabel(ext_parity_text), self.ext_row, 0) old_entry_count: QLabel = QLabel() old_entry_count.setAlignment(Qt.AlignmentFlag.AlignRight) @@ -187,10 +185,8 @@ def init_page_convert(self) -> None: old_alias_value.setAlignment(Qt.AlignmentFlag.AlignRight) old_color_value: QLabel = QLabel() old_color_value.setAlignment(Qt.AlignmentFlag.AlignRight) - old_ext_count: QLabel = QLabel() - old_ext_count.setAlignment(Qt.AlignmentFlag.AlignRight) - old_ext_type: QLabel = QLabel() - old_ext_type.setAlignment(Qt.AlignmentFlag.AlignRight) + old_ext_value: QLabel = QLabel() + old_ext_value.setAlignment(Qt.AlignmentFlag.AlignRight) self.old_content_layout.addWidget(old_entry_count, self.entries_row, 1) self.old_content_layout.addWidget(old_path_value, self.path_row, 1) @@ -201,8 +197,7 @@ def init_page_convert(self) -> None: self.old_content_layout.addWidget(old_subtag_value, self.parent_tags_row, 1) self.old_content_layout.addWidget(old_alias_value, self.aliases_row, 1) self.old_content_layout.addWidget(old_color_value, self.colors_row, 1) - self.old_content_layout.addWidget(old_ext_count, self.ext_row, 1) - self.old_content_layout.addWidget(old_ext_type, self.ext_type_row, 1) + self.old_content_layout.addWidget(old_ext_value, self.ext_row, 1) self.old_content_layout.addWidget(QLabel(), self.path_row, 2) self.old_content_layout.addWidget(QLabel(), self.fields_row, 2) @@ -211,6 +206,7 @@ def init_page_convert(self) -> None: self.old_content_layout.addWidget(QLabel(), self.parent_tags_row, 2) self.old_content_layout.addWidget(QLabel(), self.aliases_row, 2) self.old_content_layout.addWidget(QLabel(), self.colors_row, 2) + self.old_content_layout.addWidget(QLabel(), self.ext_row, 2) old_lib_layout.addWidget(old_content_container) @@ -233,8 +229,7 @@ def init_page_convert(self) -> None: self.new_content_layout.addWidget(QLabel(parent_tags_text), self.parent_tags_row, 0) self.new_content_layout.addWidget(QLabel(aliases_text), self.aliases_row, 0) self.new_content_layout.addWidget(QLabel(colors_text), self.colors_row, 0) - self.new_content_layout.addWidget(QLabel(ext_text), self.ext_row, 0) - self.new_content_layout.addWidget(QLabel(ext_type_text), self.ext_type_row, 0) + self.new_content_layout.addWidget(QLabel(ext_parity_text), self.ext_row, 0) new_entry_count: QLabel = QLabel() new_entry_count.setAlignment(Qt.AlignmentFlag.AlignRight) @@ -254,10 +249,8 @@ def init_page_convert(self) -> None: alias_parity_value.setAlignment(Qt.AlignmentFlag.AlignRight) new_color_value: QLabel = QLabel() new_color_value.setAlignment(Qt.AlignmentFlag.AlignRight) - new_ext_count: QLabel = QLabel() - new_ext_count.setAlignment(Qt.AlignmentFlag.AlignRight) - new_ext_type: QLabel = QLabel() - new_ext_type.setAlignment(Qt.AlignmentFlag.AlignRight) + ext_parity_value: QLabel = QLabel() + ext_parity_value.setAlignment(Qt.AlignmentFlag.AlignRight) self.new_content_layout.addWidget(new_entry_count, self.entries_row, 1) self.new_content_layout.addWidget(path_parity_value, self.path_row, 1) @@ -268,8 +261,7 @@ def init_page_convert(self) -> None: self.new_content_layout.addWidget(subtag_parity_value, self.parent_tags_row, 1) self.new_content_layout.addWidget(alias_parity_value, self.aliases_row, 1) self.new_content_layout.addWidget(new_color_value, self.colors_row, 1) - self.new_content_layout.addWidget(new_ext_count, self.ext_row, 1) - self.new_content_layout.addWidget(new_ext_type, self.ext_type_row, 1) + self.new_content_layout.addWidget(ext_parity_value, self.ext_row, 1) self.new_content_layout.addWidget(QLabel(), self.entries_row, 2) self.new_content_layout.addWidget(QLabel(), self.path_row, 2) @@ -281,7 +273,6 @@ def init_page_convert(self) -> None: self.new_content_layout.addWidget(QLabel(), self.aliases_row, 2) self.new_content_layout.addWidget(QLabel(), self.colors_row, 2) self.new_content_layout.addWidget(QLabel(), self.ext_row, 2) - self.new_content_layout.addWidget(QLabel(), self.ext_type_row, 2) new_lib_layout.addWidget(new_content_container) @@ -334,8 +325,6 @@ def migrate(self, skip_ui: bool = False): # Update JSON UI self.update_json_entry_count(len(self.json_lib.entries)) self.update_json_tag_count(len(self.json_lib.tags)) - self.update_json_ext_count(len(self.json_lib.ext_list)) - self.update_json_ext_type(self.json_lib.is_exclude_list) self.migration_progress(skip_ui=skip_ui) self.is_migration_initialized = True @@ -410,11 +399,12 @@ def migration_iterator(self): self.temp_path: Path = ( self.json_lib.library_dir / TS_FOLDER_NAME / "migration_ts_library.sqlite" ) - self.sql_lib.storage_path = self.temp_path if self.temp_path.exists(): logger.info('Temporary migration file "temp_path" already exists. Removing...') self.temp_path.unlink() - self.sql_lib.open_sqlite_library(self.json_lib.library_dir, is_new=True) + self.sql_lib.open_sqlite_library( + self.json_lib.library_dir, is_new=True, storage_path=str(self.temp_path) + ) yield Translations.format( "json_migration.migrating_files_entries", entries=len(self.json_lib.entries) ) @@ -428,6 +418,7 @@ def migration_iterator(self): check_set.add(self.check_subtag_parity()) check_set.add(self.check_alias_parity()) check_set.add(self.check_color_parity()) + check_set.add(self.check_ignore_parity()) if False not in check_set: yield Translations["json_migration.migration_complete"] else: @@ -454,6 +445,7 @@ def update_parity_ui(self): self.update_parity_value(self.parent_tags_row, self.subtag_parity) self.update_parity_value(self.aliases_row, self.alias_parity) self.update_parity_value(self.colors_row, self.color_parity) + self.update_parity_value(self.ext_row, self.ext_parity) self.sql_lib.close() def update_sql_value_ui(self, show_msg_box: bool = True): @@ -468,16 +460,6 @@ def update_sql_value_ui(self, show_msg_box: bool = True): len(self.sql_lib.tags), self.old_tag_count, ) - self.update_sql_value( - self.ext_row, - len(self.sql_lib.prefs(LibraryPrefs.EXTENSION_LIST)), - self.old_ext_count, - ) - self.update_sql_value( - self.ext_type_row, - self.sql_lib.prefs(LibraryPrefs.IS_EXCLUDE_LIST), # pyright: ignore[reportArgumentType] - self.old_ext_type, - ) logger.info("Parity check complete!") if self.discrepancies: logger.warning("Discrepancies found:") @@ -509,16 +491,6 @@ def update_json_tag_count(self, value: int): label: QLabel = self.old_content_layout.itemAtPosition(self.tags_row, 1).widget() # type:ignore label.setText(self.color_value_default(value)) - def update_json_ext_count(self, value: int): - self.old_ext_count = value - label: QLabel = self.old_content_layout.itemAtPosition(self.ext_row, 1).widget() # type:ignore - label.setText(self.color_value_default(value)) - - def update_json_ext_type(self, value: bool): - self.old_ext_type = value - label: QLabel = self.old_content_layout.itemAtPosition(self.ext_type_row, 1).widget() # type:ignore - label.setText(self.color_value_default(value)) - def update_sql_value(self, row: int, value: int | bool, old_value: int | bool): label: QLabel = self.new_content_layout.itemAtPosition(row, 1).widget() # type:ignore warning_icon: QLabel = self.new_content_layout.itemAtPosition(row, 2).widget() # type:ignore @@ -547,6 +519,28 @@ def color_value_conditional(self, old_value: int | str, new_value: int | str) -> color = green if old_value == new_value else red return str(f"{new_value}") + def assert_ignore_parity(self) -> None: + compiled_pats = fnmatch.compile( + ignore_to_glob( + Ignore._load_ignore_file( + unwrap(self.json_lib.library_dir) / TS_FOLDER_NAME / IGNORE_NAME + ) + ), + PATH_GLOB_FLAGS, + ) # copied from Ignore.get_patterns since that method modifies singleton state + path = self.json_lib.library_dir / "filename" + for ext in self.json_lib.ext_list: + assert compiled_pats.match(str(path / ext)) == self.json_lib.is_exclude_list + assert compiled_pats.match(str(path / ".not_a_real_ext")) != self.json_lib.is_exclude_list + + def check_ignore_parity(self) -> bool: + try: + self.assert_ignore_parity() + self.ext_parity = True + except AssertionError: + self.ext_parity = False + return self.ext_parity + def check_field_parity(self) -> bool: """Check if all JSON field and tag data matches the new SQL data.""" @@ -670,9 +664,6 @@ def check_subtag_parity(self) -> bool: self.subtag_parity = True return self.subtag_parity - def check_ext_type(self) -> bool: - return self.json_lib.is_exclude_list == self.sql_lib.prefs(LibraryPrefs.IS_EXCLUDE_LIST) - def check_alias_parity(self) -> bool: """Check if all JSON aliases match the new SQL aliases.""" with Session(self.sql_lib.engine) as session: diff --git a/src/tagstudio/resources/translations/de.json b/src/tagstudio/resources/translations/de.json index 18749c9b5..1b856d5be 100644 --- a/src/tagstudio/resources/translations/de.json +++ b/src/tagstudio/resources/translations/de.json @@ -157,8 +157,6 @@ "json_migration.heading.aliases": "Aliase:", "json_migration.heading.colors": "Farben:", "json_migration.heading.differ": "Diskrepanz", - "json_migration.heading.extension_list_type": "Erweiterungslistentyp:", - "json_migration.heading.file_extension_list": "Liste der Dateiendungen:", "json_migration.heading.match": "Übereinstimmend", "json_migration.heading.names": "Namen:", "json_migration.heading.parent_tags": "Übergeordnete Tags:", diff --git a/src/tagstudio/resources/translations/en.json b/src/tagstudio/resources/translations/en.json index cdd46dc11..d214d1aa4 100644 --- a/src/tagstudio/resources/translations/en.json +++ b/src/tagstudio/resources/translations/en.json @@ -157,8 +157,7 @@ "json_migration.heading.aliases": "Aliases:", "json_migration.heading.colors": "Colors:", "json_migration.heading.differ": "Discrepancy", - "json_migration.heading.extension_list_type": "Extension List Type:", - "json_migration.heading.file_extension_list": "File Extension List:", + "json_migration.heading.extensions": "Extensions:", "json_migration.heading.match": "Matched", "json_migration.heading.names": "Names:", "json_migration.heading.parent_tags": "Parent Tags:", diff --git a/src/tagstudio/resources/translations/es.json b/src/tagstudio/resources/translations/es.json index 30bdd80b9..6c789f2be 100644 --- a/src/tagstudio/resources/translations/es.json +++ b/src/tagstudio/resources/translations/es.json @@ -156,8 +156,6 @@ "json_migration.heading.aliases": "Alias:", "json_migration.heading.colors": "Colores:", "json_migration.heading.differ": "Discrepancia", - "json_migration.heading.extension_list_type": "Tipo de lista de extensión:", - "json_migration.heading.file_extension_list": "Lista de extensiones de archivos:", "json_migration.heading.match": "Igualado", "json_migration.heading.names": "Nombres:", "json_migration.heading.parent_tags": "Etiquetas principales:", diff --git a/src/tagstudio/resources/translations/fil.json b/src/tagstudio/resources/translations/fil.json index 68c2df241..8f846be8a 100644 --- a/src/tagstudio/resources/translations/fil.json +++ b/src/tagstudio/resources/translations/fil.json @@ -140,8 +140,6 @@ "json_migration.heading.aliases": "Mga alyas:", "json_migration.heading.colors": "Mga kulay:", "json_migration.heading.differ": "May pagkakaiba", - "json_migration.heading.extension_list_type": "Uri ng Listahan ng Extension:", - "json_migration.heading.file_extension_list": "Listahan ng Mga File Extension:", "json_migration.heading.match": "Tumutugma", "json_migration.heading.names": "Mga pangalan:", "json_migration.heading.parent_tags": "Mga parent tag:", diff --git a/src/tagstudio/resources/translations/fr.json b/src/tagstudio/resources/translations/fr.json index a42c0d385..a709f1a16 100644 --- a/src/tagstudio/resources/translations/fr.json +++ b/src/tagstudio/resources/translations/fr.json @@ -156,8 +156,6 @@ "json_migration.heading.aliases": "Alias :", "json_migration.heading.colors": "Couleurs :", "json_migration.heading.differ": "Divergence", - "json_migration.heading.extension_list_type": "Type de liste d'extension :", - "json_migration.heading.file_extension_list": "Liste des extensions de fichiers :", "json_migration.heading.match": "Correspondant", "json_migration.heading.names": "Noms :", "json_migration.heading.parent_tags": "Tags Parents :", diff --git a/src/tagstudio/resources/translations/hu.json b/src/tagstudio/resources/translations/hu.json index 6887558f4..af1570841 100644 --- a/src/tagstudio/resources/translations/hu.json +++ b/src/tagstudio/resources/translations/hu.json @@ -157,8 +157,6 @@ "json_migration.heading.aliases": "Áljelek:", "json_migration.heading.colors": "Színek:", "json_migration.heading.differ": "Eltérés", - "json_migration.heading.extension_list_type": "Kiterjesztési lista típusa:", - "json_migration.heading.file_extension_list": "Fájlkiterjesztési lista:", "json_migration.heading.match": "Egységesítve", "json_migration.heading.names": "Nevek:", "json_migration.heading.parent_tags": "Szülőcímkék:", diff --git a/src/tagstudio/resources/translations/it.json b/src/tagstudio/resources/translations/it.json index 854c926f6..d304daa91 100644 --- a/src/tagstudio/resources/translations/it.json +++ b/src/tagstudio/resources/translations/it.json @@ -156,8 +156,6 @@ "json_migration.heading.aliases": "Alias:", "json_migration.heading.colors": "Colori:", "json_migration.heading.differ": "Discrepanze", - "json_migration.heading.extension_list_type": "Tipo di Lista di Entensioni:", - "json_migration.heading.file_extension_list": "Elenco Estensioni dei File:", "json_migration.heading.match": "Abbinato", "json_migration.heading.names": "Nomi:", "json_migration.heading.parent_tags": "Etichette Genitore:", diff --git a/src/tagstudio/resources/translations/ja.json b/src/tagstudio/resources/translations/ja.json index 5f1344c0c..085100885 100644 --- a/src/tagstudio/resources/translations/ja.json +++ b/src/tagstudio/resources/translations/ja.json @@ -157,8 +157,6 @@ "json_migration.heading.aliases": "エイリアス:", "json_migration.heading.colors": "色:", "json_migration.heading.differ": "差異", - "json_migration.heading.extension_list_type": "拡張子リストの種類:", - "json_migration.heading.file_extension_list": "ファイルの拡張子リスト:", "json_migration.heading.match": "一致", "json_migration.heading.names": "名前:", "json_migration.heading.parent_tags": "親タグ:", diff --git a/src/tagstudio/resources/translations/nb_NO.json b/src/tagstudio/resources/translations/nb_NO.json index 35a6f1064..88f1553a7 100644 --- a/src/tagstudio/resources/translations/nb_NO.json +++ b/src/tagstudio/resources/translations/nb_NO.json @@ -148,8 +148,6 @@ "json_migration.heading.aliases": "Alternative navn:", "json_migration.heading.colors": "Farger:", "json_migration.heading.differ": "Avvik", - "json_migration.heading.extension_list_type": "Type av Utvidelsesliste:", - "json_migration.heading.file_extension_list": "Filutvidelse Liste:", "json_migration.heading.match": "Matchet", "json_migration.heading.names": "Navn:", "json_migration.heading.parent_tags": "Overordnede Etiketter:", diff --git a/src/tagstudio/resources/translations/pl.json b/src/tagstudio/resources/translations/pl.json index c83c72eaf..f58aa54b7 100644 --- a/src/tagstudio/resources/translations/pl.json +++ b/src/tagstudio/resources/translations/pl.json @@ -139,8 +139,6 @@ "json_migration.heading.aliases": "Zastępcze nazwy:", "json_migration.heading.colors": "Kolory:", "json_migration.heading.differ": "Niezgodność", - "json_migration.heading.extension_list_type": "Typ listy rozszerzeń:", - "json_migration.heading.file_extension_list": "Lista rozszerzeń plików:", "json_migration.heading.match": "Dopasowane", "json_migration.heading.names": "Nazwy:", "json_migration.heading.parent_tags": "Tagi nadrzędne:", diff --git a/src/tagstudio/resources/translations/pt.json b/src/tagstudio/resources/translations/pt.json index cbd27f3a0..678f3eebc 100644 --- a/src/tagstudio/resources/translations/pt.json +++ b/src/tagstudio/resources/translations/pt.json @@ -136,8 +136,6 @@ "json_migration.heading.aliases": "Pseudônimos:", "json_migration.heading.colors": "Cores:", "json_migration.heading.differ": "Discrepância", - "json_migration.heading.extension_list_type": "Tipo de Lista de Extensão:", - "json_migration.heading.file_extension_list": "Lista de Extensão de Ficheiro:", "json_migration.heading.match": "Combinado", "json_migration.heading.names": "Nomes:", "json_migration.heading.parent_tags": "Tags Pai:", diff --git a/src/tagstudio/resources/translations/pt_BR.json b/src/tagstudio/resources/translations/pt_BR.json index 36d3ac78b..60bb2f1fb 100644 --- a/src/tagstudio/resources/translations/pt_BR.json +++ b/src/tagstudio/resources/translations/pt_BR.json @@ -156,8 +156,6 @@ "json_migration.heading.aliases": "Pseudônimos:", "json_migration.heading.colors": "Cores:", "json_migration.heading.differ": "Discrepância", - "json_migration.heading.extension_list_type": "Lista dos tipos de Extensão:", - "json_migration.heading.file_extension_list": "Lista de Extensão de Arquivo:", "json_migration.heading.match": "Correspondido", "json_migration.heading.names": "Nomes:", "json_migration.heading.parent_tags": "Tags Pai:", diff --git a/src/tagstudio/resources/translations/qpv.json b/src/tagstudio/resources/translations/qpv.json index 5b68f1480..44a7fb4f3 100644 --- a/src/tagstudio/resources/translations/qpv.json +++ b/src/tagstudio/resources/translations/qpv.json @@ -137,8 +137,6 @@ "json_migration.heading.aliases": "Andrnamae:", "json_migration.heading.colors": "Varge:", "json_migration.heading.differ": "Tchigauzma", - "json_migration.heading.extension_list_type": "Fal fu taksanting tumam:", - "json_migration.heading.file_extension_list": "Tumam fu mlafufal:", "json_migration.heading.match": "Finnajena sama", "json_migration.heading.names": "Namae:", "json_migration.heading.parent_tags": "Atama festaretol:", diff --git a/src/tagstudio/resources/translations/ru.json b/src/tagstudio/resources/translations/ru.json index 9fa1e9615..70fff28c7 100644 --- a/src/tagstudio/resources/translations/ru.json +++ b/src/tagstudio/resources/translations/ru.json @@ -141,8 +141,6 @@ "json_migration.heading.aliases": "Псевдонимы:", "json_migration.heading.colors": "Цвета:", "json_migration.heading.differ": "Несоответствие", - "json_migration.heading.extension_list_type": "Тип списка расширений:", - "json_migration.heading.file_extension_list": "Список расширений файлов:", "json_migration.heading.match": "Совпало", "json_migration.heading.names": "Имена:", "json_migration.heading.parent_tags": "Родительские теги:", diff --git a/src/tagstudio/resources/translations/ta.json b/src/tagstudio/resources/translations/ta.json index 03e8da122..2a2e9472d 100644 --- a/src/tagstudio/resources/translations/ta.json +++ b/src/tagstudio/resources/translations/ta.json @@ -156,8 +156,6 @@ "json_migration.heading.aliases": "மாற்றுப்பெயர்கள்:", "json_migration.heading.colors": "நிறங்கள்:", "json_migration.heading.differ": "முரண்பாடு", - "json_migration.heading.extension_list_type": "நீட்டிப்பு பட்டியல் வகை:", - "json_migration.heading.file_extension_list": "கோப்பு நீட்டிப்பு பட்டியல்:", "json_migration.heading.match": "பொருந்தியது", "json_migration.heading.names": "பெயர்கள்:", "json_migration.heading.parent_tags": "பெற்றோர் குறிச்சொற்கள்:", diff --git a/src/tagstudio/resources/translations/tok.json b/src/tagstudio/resources/translations/tok.json index 1cd79fc97..492c4f897 100644 --- a/src/tagstudio/resources/translations/tok.json +++ b/src/tagstudio/resources/translations/tok.json @@ -152,7 +152,6 @@ "json_migration.heading.aliases": "nimi ante:", "json_migration.heading.colors": "kule:", "json_migration.heading.differ": "ike", - "json_migration.heading.file_extension_list": "kulupu pi namako lipu:", "json_migration.heading.match": "sama", "json_migration.heading.names": "nimi:", "json_migration.heading.parent_tags": "poki mama:", diff --git a/src/tagstudio/resources/translations/tr.json b/src/tagstudio/resources/translations/tr.json index 14f1834f5..94f383ddd 100644 --- a/src/tagstudio/resources/translations/tr.json +++ b/src/tagstudio/resources/translations/tr.json @@ -136,8 +136,6 @@ "json_migration.heading.aliases": "Takma Adlar:", "json_migration.heading.colors": "Renkler:", "json_migration.heading.differ": "Uyuşmazlık", - "json_migration.heading.extension_list_type": "Uzantı Listesi Türü:", - "json_migration.heading.file_extension_list": "Dosya Uzantı Listesi:", "json_migration.heading.match": "Eşleşti", "json_migration.heading.names": "Adlar:", "json_migration.heading.parent_tags": "Üst Etiketler:", diff --git a/src/tagstudio/resources/translations/zh_Hans.json b/src/tagstudio/resources/translations/zh_Hans.json index 66cea1092..2da8198dc 100644 --- a/src/tagstudio/resources/translations/zh_Hans.json +++ b/src/tagstudio/resources/translations/zh_Hans.json @@ -152,8 +152,6 @@ "json_migration.heading.aliases": "别名:", "json_migration.heading.colors": "颜色:", "json_migration.heading.differ": "差异", - "json_migration.heading.extension_list_type": "扩展名列表类型:", - "json_migration.heading.file_extension_list": "文件扩展名列表:", "json_migration.heading.match": "已匹配", "json_migration.heading.names": "名字:", "json_migration.heading.parent_tags": "上级标签:", diff --git a/src/tagstudio/resources/translations/zh_Hant.json b/src/tagstudio/resources/translations/zh_Hant.json index 8dd73377c..c49d3816d 100644 --- a/src/tagstudio/resources/translations/zh_Hant.json +++ b/src/tagstudio/resources/translations/zh_Hant.json @@ -156,8 +156,6 @@ "json_migration.heading.aliases": "別名:", "json_migration.heading.colors": "顏色:", "json_migration.heading.differ": "差異", - "json_migration.heading.extension_list_type": "副檔名清單類型:", - "json_migration.heading.file_extension_list": "檔案副檔名清單:", "json_migration.heading.match": "已一致", "json_migration.heading.names": "名稱:", "json_migration.heading.parent_tags": "父標籤:", diff --git a/tests/conftest.py b/tests/conftest.py index 54368a5f0..eea72cbab 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -33,7 +33,7 @@ def cwd(): def file_mediatypes_library(): lib = Library() - status = lib.open_library(Path(""), ":memory:") + status = lib.open_library(Path(""), in_memory=True) assert status.success folder = unwrap(lib.folder) @@ -84,7 +84,7 @@ def library(request, library_dir: Path): # pyright: ignore library_path = Path(request.param) lib = Library() - status = lib.open_library(library_path, ":memory:") + status = lib.open_library(library_path, in_memory=True) assert status.success folder = unwrap(lib.folder) diff --git a/tests/fixtures/search_library/.TagStudio/ts_library.sqlite b/tests/fixtures/search_library/.TagStudio/ts_library.sqlite index 65b059981..6d89b18aa 100644 Binary files a/tests/fixtures/search_library/.TagStudio/ts_library.sqlite and b/tests/fixtures/search_library/.TagStudio/ts_library.sqlite differ diff --git a/tests/macros/test_refresh_dir.py b/tests/macros/test_refresh_dir.py index ebf7292bc..c7a2c2a65 100644 --- a/tests/macros/test_refresh_dir.py +++ b/tests/macros/test_refresh_dir.py @@ -7,7 +7,7 @@ import pytest -from tagstudio.core.enums import LibraryPrefs +from tagstudio.core.constants import IGNORE_NAME from tagstudio.core.library.alchemy.library import Library from tagstudio.core.library.refresh import RefreshTracker from tagstudio.core.utils.types import unwrap @@ -20,12 +20,11 @@ def test_refresh_new_files(library: Library, exclude_mode: bool): library_dir = unwrap(library.library_dir) # Given - library.set_prefs(LibraryPrefs.IS_EXCLUDE_LIST, exclude_mode) - library.set_prefs(LibraryPrefs.EXTENSION_LIST, [".md"]) registry = RefreshTracker(library=library) library.included_files.clear() (library_dir / "FOO.MD").touch() + (library_dir / IGNORE_NAME).write_text("*.md" if exclude_mode else "*\n!*.md") # Test if the single file was added list(registry.refresh_dir(library_dir, force_internal_tools=True)) - assert registry.files_not_in_library == [Path("FOO.MD")] + assert set(registry.files_not_in_library) == set([Path(IGNORE_NAME), Path("FOO.MD")]) diff --git a/tests/test_db_migrations.py b/tests/test_db_migrations.py index 2a1c7c960..d616a6104 100644 --- a/tests/test_db_migrations.py +++ b/tests/test_db_migrations.py @@ -46,9 +46,9 @@ def test_library_migrations(path: str): try: status = library.open_library(library_dir=temp_path) library.close() - shutil.rmtree(temp_path) assert status.success except Exception as e: library.close() - shutil.rmtree(temp_path) raise (e) + finally: + shutil.rmtree(temp_path) diff --git a/tests/test_json_migration.py b/tests/test_json_migration.py index 25ad8fc1b..e44a08d3c 100644 --- a/tests/test_json_migration.py +++ b/tests/test_json_migration.py @@ -6,7 +6,6 @@ from pathlib import Path from time import time -from tagstudio.core.enums import LibraryPrefs from tagstudio.qt.mixed.migration_modal import JsonMigrationModal CWD = Path(__file__) @@ -43,10 +42,4 @@ def test_json_migration(): assert modal.check_color_parity() # Extension Filter List ==================================================== - # Count - assert len(modal.json_lib.ext_list) == len(modal.sql_lib.prefs(LibraryPrefs.EXTENSION_LIST)) - # List Type - assert modal.check_ext_type() - # No Leading Dot - for ext in modal.sql_lib.prefs(LibraryPrefs.EXTENSION_LIST): # pyright: ignore[reportUnknownVariableType] - assert ext[0] != "." + modal.assert_ignore_parity() diff --git a/tests/test_library.py b/tests/test_library.py index 447344512..52526a540 100644 --- a/tests/test_library.py +++ b/tests/test_library.py @@ -10,7 +10,6 @@ import pytest import structlog -from tagstudio.core.enums import DefaultEnum, LibraryPrefs from tagstudio.core.library.alchemy.enums import BrowsingState from tagstudio.core.library.alchemy.fields import ( FieldID, # pyright: ignore[reportPrivateUsage] @@ -199,11 +198,6 @@ def test_search_library_case_insensitive(library: Library): assert results[0] == entry.id -def test_preferences(library: Library): - for pref in LibraryPrefs: - assert library.prefs(pref) == pref.default - - def test_remove_entry_field(library: Library, entry_full: Entry): title_field = entry_full.text_fields[0] @@ -393,24 +387,6 @@ def test_update_field_order(library: Library, entry_full: Entry): assert entry.text_fields[1].value == "second" -def test_library_prefs_multiple_identical_vals(): - # check the preferences are inherited from DefaultEnum - assert issubclass(LibraryPrefs, DefaultEnum) - - # create custom settings with identical values - class TestPrefs(DefaultEnum): - FOO = 1 - BAR = 1 - - assert TestPrefs.FOO.default == 1 - assert TestPrefs.BAR.default == 1 - assert TestPrefs.BAR.name == "BAR" - - # accessing .value should raise exception - with pytest.raises(AttributeError): - assert TestPrefs.BAR.value - - def test_path_search_ilike(library: Library): results = library.search_library(BrowsingState.from_path("bar.md"), page_size=500) assert results.total_count == 1