mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-07-19 03:56:19 +02:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 582ed06be4 | |||
| aff0eb823f |
+2
-10
@@ -128,15 +128,7 @@ 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 | `<Library Folder>`/.TagStudio/ts_library.sqlite |
|
||||
| [#1139](https://github.com/TagStudioDev/TagStudio/pull/1139) | SQLite | `<Library Folder>`/.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.
|
||||
|
||||
#### Version 104
|
||||
|
||||
| Used From | Format | Location |
|
||||
| ----------------------------------------------------------------------- | ------ | ----------------------------------------------- |
|
||||
| [#1298](https://github.com/TagStudioDev/TagStudio/pull/1298) | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
|
||||
|
||||
- Removes the `preferences` table, after migrating the contained extension list to the .ts_ignore file, if necessary.
|
||||
- Sets the `is_hidden` field on the built-in Archived tag to `1`, to match the Archived tag now being hidden by default.
|
||||
@@ -46,7 +46,6 @@ pyinstaller = ["Pyinstaller~=6.13"]
|
||||
pytest = [
|
||||
"pytest==8.3.5",
|
||||
"pytest-cov==6.1.1",
|
||||
"pytest-mock==3.15.1",
|
||||
"pytest-qt==4.4.0",
|
||||
"syrupy==4.9.1",
|
||||
]
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
import enum
|
||||
from typing import Any
|
||||
from uuid import uuid4
|
||||
|
||||
|
||||
class SettingItems(str, enum.Enum):
|
||||
@@ -55,3 +57,30 @@ 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"]
|
||||
|
||||
@@ -8,9 +8,10 @@ from sqlalchemy import text
|
||||
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 = 104
|
||||
DB_VERSION: int = 103
|
||||
|
||||
TAG_CHILDREN_QUERY = text("""
|
||||
WITH RECURSIVE ChildTags AS (
|
||||
|
||||
@@ -16,7 +16,7 @@ from dataclasses import dataclass
|
||||
from datetime import UTC, datetime
|
||||
from os import makedirs
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from uuid import uuid4
|
||||
from warnings import catch_warnings
|
||||
|
||||
@@ -53,6 +53,7 @@ from sqlalchemy.orm import (
|
||||
noload,
|
||||
selectinload,
|
||||
)
|
||||
from typing_extensions import deprecated
|
||||
|
||||
from tagstudio.core.constants import (
|
||||
BACKUP_FOLDER_NAME,
|
||||
@@ -66,11 +67,13 @@ from tagstudio.core.constants import (
|
||||
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,
|
||||
@@ -93,6 +96,7 @@ from tagstudio.core.library.alchemy.models import (
|
||||
Entry,
|
||||
Folder,
|
||||
Namespace,
|
||||
Preferences,
|
||||
Tag,
|
||||
TagAlias,
|
||||
TagColorGroup,
|
||||
@@ -100,7 +104,6 @@ from tagstudio.core.library.alchemy.models import (
|
||||
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
|
||||
@@ -315,10 +318,9 @@ class Library:
|
||||
value=v,
|
||||
)
|
||||
|
||||
# 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)
|
||||
)
|
||||
# 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)
|
||||
|
||||
end_time = time.time()
|
||||
logger.info(f"Library Converted! ({format_timespan(end_time - start_time)})")
|
||||
@@ -456,6 +458,15 @@ class Library:
|
||||
|
||||
# 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))
|
||||
@@ -469,6 +480,15 @@ class Library:
|
||||
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(
|
||||
@@ -536,9 +556,10 @@ class Library:
|
||||
if loaded_db_version < 103:
|
||||
# changes: tags
|
||||
self.__apply_db103_migration(session)
|
||||
if loaded_db_version < 104:
|
||||
# changes: deletes preferences
|
||||
self.__apply_db104_migrations(session, library_dir)
|
||||
|
||||
# Convert file extension list to ts_ignore file, if a .ts_ignore file does not exist
|
||||
# TODO: do this in the migration step that will remove the preferences table
|
||||
self.migrate_sql_to_ts_ignore(library_dir)
|
||||
|
||||
# Update DB_VERSION
|
||||
if loaded_db_version < DB_VERSION:
|
||||
@@ -711,30 +732,33 @@ class Library:
|
||||
)
|
||||
session.rollback()
|
||||
|
||||
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()
|
||||
|
||||
def __migrate_sql_to_ts_ignore(self, library_dir: Path):
|
||||
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
|
||||
if Path(ts_ignore).exists():
|
||||
if Path(library_dir / TS_FOLDER_NAME / IGNORE_NAME).exists():
|
||||
return
|
||||
|
||||
# Load legacy extension data
|
||||
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'"))
|
||||
)
|
||||
# Create blank '.ts_ignore' file
|
||||
ts_ignore_template = (
|
||||
Path(__file__).parents[3] / "resources/templates/ts_ignore_template_blank.txt"
|
||||
)
|
||||
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)
|
||||
|
||||
with open(ts_ignore, "w") as f:
|
||||
f.write(migrate_ext_list(extensions, is_exclude_list))
|
||||
# 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])
|
||||
|
||||
@property
|
||||
def default_fields(self) -> list[BaseField]:
|
||||
@@ -1832,20 +1856,19 @@ class Library:
|
||||
engine = sqlalchemy.inspect(self.engine)
|
||||
try:
|
||||
# "Version" table added in DB_VERSION 101
|
||||
if engine and engine.has_table("versions"):
|
||||
if engine and engine.has_table("Version"):
|
||||
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:
|
||||
return int(
|
||||
unwrap(
|
||||
session.scalar(
|
||||
text("SELECT value FROM preferences WHERE key == 'DB_VERSION'")
|
||||
)
|
||||
)
|
||||
pref_version = session.scalar(
|
||||
select(Preferences).where(Preferences.key == DB_VERSION_LEGACY_KEY)
|
||||
)
|
||||
assert pref_version
|
||||
assert isinstance(pref_version.value, int)
|
||||
return pref_version.value
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
@@ -1863,10 +1886,60 @@ class Library:
|
||||
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 = {}
|
||||
|
||||
@@ -6,8 +6,9 @@ from datetime import datetime as dt
|
||||
from pathlib import Path
|
||||
from typing import override
|
||||
|
||||
from sqlalchemy import ForeignKey, ForeignKeyConstraint, Integer, event
|
||||
from sqlalchemy import JSON, 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
|
||||
@@ -326,6 +327,16 @@ 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"
|
||||
|
||||
|
||||
@@ -91,23 +91,6 @@ 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."""
|
||||
|
||||
|
||||
@@ -193,31 +193,18 @@ class TagStudioCore:
|
||||
|
||||
@staticmethod
|
||||
@lru_cache(maxsize=1)
|
||||
def get_most_recent_release_version() -> str | None:
|
||||
"""Get the version of the most recent GitHub release."""
|
||||
try:
|
||||
resp = requests.get(
|
||||
"https://api.github.com/repos/TagStudioDev/TagStudio/releases/latest"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("Error getting most recent GitHub release.", error=e)
|
||||
return None
|
||||
|
||||
if resp.status_code != 200:
|
||||
logger.error("Error getting most recent GitHub release.", status_code=resp.status_code)
|
||||
return None
|
||||
def get_most_recent_release_version() -> str:
|
||||
"""Get the version of the most recent Github release."""
|
||||
resp = requests.get("https://api.github.com/repos/TagStudioDev/TagStudio/releases/latest")
|
||||
assert resp.status_code == 200, "Could not fetch information on latest release."
|
||||
|
||||
data = resp.json()
|
||||
tag: str = data["tag_name"]
|
||||
if not tag.startswith("v"):
|
||||
logger.error("Unexpected tag format.", tag=tag)
|
||||
return None
|
||||
assert tag.startswith("v")
|
||||
|
||||
version = tag[1:]
|
||||
# the assertion does not allow for prerelease/build,
|
||||
# the assert does not allow for prerelease/build,
|
||||
# because the latest release should never have them
|
||||
if re.match(r"^\d+\.\d+\.\d+$", version) is None:
|
||||
logger.error("Invalid version format.", version=version)
|
||||
return None
|
||||
assert re.match(r"^\d+\.\d+\.\d+$", version) is not None, "Invalid version format."
|
||||
|
||||
return version
|
||||
|
||||
@@ -4,7 +4,6 @@ from PySide6.QtWidgets import QMessageBox
|
||||
|
||||
from tagstudio.core.constants import VERSION
|
||||
from tagstudio.core.ts_core import TagStudioCore
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
from tagstudio.qt.models.palette import ColorType, UiColor, get_ui_color
|
||||
from tagstudio.qt.translations import Translations
|
||||
|
||||
@@ -31,7 +30,7 @@ class OutOfDateMessageBox(QMessageBox):
|
||||
|
||||
red = get_ui_color(ColorType.PRIMARY, UiColor.RED)
|
||||
green = get_ui_color(ColorType.PRIMARY, UiColor.GREEN)
|
||||
latest_release_version = unwrap(TagStudioCore.get_most_recent_release_version())
|
||||
latest_release_version = TagStudioCore.get_most_recent_release_version()
|
||||
status = Translations.format(
|
||||
"version_modal.status",
|
||||
installed_version=f"<span style='color:{red}'>{VERSION}</span>",
|
||||
|
||||
@@ -21,7 +21,6 @@ from PySide6.QtWidgets import (
|
||||
from tagstudio.core.constants import VERSION, VERSION_BRANCH
|
||||
from tagstudio.core.enums import Theme
|
||||
from tagstudio.core.ts_core import TagStudioCore
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
from tagstudio.qt.models.palette import ColorType, UiColor, get_ui_color
|
||||
from tagstudio.qt.previews.vendored import ffmpeg
|
||||
from tagstudio.qt.resource_manager import ResourceManager
|
||||
@@ -107,7 +106,7 @@ class AboutModal(QWidget):
|
||||
|
||||
# Version
|
||||
version_title = QLabel("Version")
|
||||
most_recent_release = unwrap(TagStudioCore.get_most_recent_release_version(), "UNKNOWN")
|
||||
most_recent_release = TagStudioCore.get_most_recent_release_version()
|
||||
version_content_style = self.form_content_style
|
||||
if most_recent_release == VERSION:
|
||||
version_content = QLabel(f"{VERSION}")
|
||||
|
||||
@@ -8,7 +8,6 @@ from pathlib import Path
|
||||
from typing import cast
|
||||
|
||||
import structlog
|
||||
import wcmatch.fnmatch as fnmatch
|
||||
from PySide6.QtCore import QObject, Qt, QThreadPool, Signal
|
||||
from PySide6.QtWidgets import (
|
||||
QApplication,
|
||||
@@ -25,19 +24,18 @@ from sqlalchemy import select
|
||||
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
|
||||
@@ -74,6 +72,8 @@ class JsonMigrationModal(QObject):
|
||||
|
||||
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,7 +82,6 @@ class JsonMigrationModal(QObject):
|
||||
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()
|
||||
@@ -130,7 +129,8 @@ class JsonMigrationModal(QObject):
|
||||
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_parity_text: str = Translations["json_migration.heading.extensions"]
|
||||
ext_text: str = Translations["json_migration.heading.file_extension_list"]
|
||||
ext_type_text: str = Translations["json_migration.heading.extension_list_type"]
|
||||
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,6 +145,7 @@ class JsonMigrationModal(QObject):
|
||||
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)
|
||||
@@ -165,7 +166,8 @@ class JsonMigrationModal(QObject):
|
||||
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_parity_text), self.ext_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)
|
||||
|
||||
old_entry_count: QLabel = QLabel()
|
||||
old_entry_count.setAlignment(Qt.AlignmentFlag.AlignRight)
|
||||
@@ -185,8 +187,10 @@ class JsonMigrationModal(QObject):
|
||||
old_alias_value.setAlignment(Qt.AlignmentFlag.AlignRight)
|
||||
old_color_value: QLabel = QLabel()
|
||||
old_color_value.setAlignment(Qt.AlignmentFlag.AlignRight)
|
||||
old_ext_value: QLabel = QLabel()
|
||||
old_ext_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)
|
||||
|
||||
self.old_content_layout.addWidget(old_entry_count, self.entries_row, 1)
|
||||
self.old_content_layout.addWidget(old_path_value, self.path_row, 1)
|
||||
@@ -197,7 +201,8 @@ class JsonMigrationModal(QObject):
|
||||
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_value, self.ext_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(QLabel(), self.path_row, 2)
|
||||
self.old_content_layout.addWidget(QLabel(), self.fields_row, 2)
|
||||
@@ -206,7 +211,6 @@ class JsonMigrationModal(QObject):
|
||||
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)
|
||||
|
||||
@@ -229,7 +233,8 @@ class JsonMigrationModal(QObject):
|
||||
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_parity_text), self.ext_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)
|
||||
|
||||
new_entry_count: QLabel = QLabel()
|
||||
new_entry_count.setAlignment(Qt.AlignmentFlag.AlignRight)
|
||||
@@ -249,8 +254,10 @@ class JsonMigrationModal(QObject):
|
||||
alias_parity_value.setAlignment(Qt.AlignmentFlag.AlignRight)
|
||||
new_color_value: QLabel = QLabel()
|
||||
new_color_value.setAlignment(Qt.AlignmentFlag.AlignRight)
|
||||
ext_parity_value: QLabel = QLabel()
|
||||
ext_parity_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)
|
||||
|
||||
self.new_content_layout.addWidget(new_entry_count, self.entries_row, 1)
|
||||
self.new_content_layout.addWidget(path_parity_value, self.path_row, 1)
|
||||
@@ -261,7 +268,8 @@ class JsonMigrationModal(QObject):
|
||||
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(ext_parity_value, self.ext_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(QLabel(), self.entries_row, 2)
|
||||
self.new_content_layout.addWidget(QLabel(), self.path_row, 2)
|
||||
@@ -273,6 +281,7 @@ class JsonMigrationModal(QObject):
|
||||
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)
|
||||
|
||||
@@ -325,6 +334,8 @@ class JsonMigrationModal(QObject):
|
||||
# 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
|
||||
@@ -418,7 +429,6 @@ class JsonMigrationModal(QObject):
|
||||
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:
|
||||
@@ -445,7 +455,6 @@ class JsonMigrationModal(QObject):
|
||||
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):
|
||||
@@ -460,6 +469,16 @@ class JsonMigrationModal(QObject):
|
||||
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:")
|
||||
@@ -491,6 +510,16 @@ class JsonMigrationModal(QObject):
|
||||
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
|
||||
@@ -519,28 +548,6 @@ class JsonMigrationModal(QObject):
|
||||
color = green if old_value == new_value else red
|
||||
return str(f"<b><a style='color: {color}'>{new_value}</a></b>")
|
||||
|
||||
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."""
|
||||
|
||||
@@ -664,6 +671,9 @@ class JsonMigrationModal(QObject):
|
||||
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:
|
||||
|
||||
@@ -613,8 +613,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
if not which(FFMPEG_CMD) or not which(FFPROBE_CMD):
|
||||
FfmpegMissingMessageBox().show()
|
||||
|
||||
latest_version = TagStudioCore.get_most_recent_release_version()
|
||||
if latest_version and is_version_outdated(VERSION, latest_version):
|
||||
if is_version_outdated(VERSION, TagStudioCore.get_most_recent_release_version()):
|
||||
OutOfDateMessageBox().exec()
|
||||
|
||||
self.app.exec()
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -157,6 +157,8 @@
|
||||
"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:",
|
||||
|
||||
@@ -157,6 +157,8 @@
|
||||
"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": "Όνομα:"
|
||||
}
|
||||
|
||||
@@ -157,7 +157,8 @@
|
||||
"json_migration.heading.aliases": "Aliases:",
|
||||
"json_migration.heading.colors": "Colors:",
|
||||
"json_migration.heading.differ": "Discrepancy",
|
||||
"json_migration.heading.extensions": "Extensions:",
|
||||
"json_migration.heading.extension_list_type": "Extension List Type:",
|
||||
"json_migration.heading.file_extension_list": "File Extension List:",
|
||||
"json_migration.heading.match": "Matched",
|
||||
"json_migration.heading.names": "Names:",
|
||||
"json_migration.heading.parent_tags": "Parent Tags:",
|
||||
|
||||
@@ -157,6 +157,8 @@
|
||||
"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:",
|
||||
|
||||
@@ -157,6 +157,8 @@
|
||||
"json_migration.heading.aliases": "Aliaksia:",
|
||||
"json_migration.heading.colors": "Värit:",
|
||||
"json_migration.heading.differ": "Poikkeavuus",
|
||||
"json_migration.heading.extension_list_type": "Laajennusluettelon tyyppi:",
|
||||
"json_migration.heading.file_extension_list": "Tiedostopääte lista:",
|
||||
"json_migration.heading.match": "Yhdistetty",
|
||||
"json_migration.heading.names": "Nimet:",
|
||||
"json_migration.heading.parent_tags": "Päätunnukset:",
|
||||
|
||||
@@ -140,6 +140,8 @@
|
||||
"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:",
|
||||
|
||||
@@ -157,6 +157,8 @@
|
||||
"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 :",
|
||||
|
||||
@@ -157,6 +157,8 @@
|
||||
"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:",
|
||||
|
||||
@@ -157,6 +157,8 @@
|
||||
"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:",
|
||||
|
||||
@@ -157,6 +157,8 @@
|
||||
"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": "親タグ:",
|
||||
|
||||
@@ -148,6 +148,8 @@
|
||||
"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:",
|
||||
|
||||
@@ -139,6 +139,8 @@
|
||||
"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:",
|
||||
|
||||
@@ -136,6 +136,8 @@
|
||||
"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:",
|
||||
|
||||
@@ -157,6 +157,8 @@
|
||||
"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:",
|
||||
|
||||
@@ -137,6 +137,8 @@
|
||||
"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:",
|
||||
|
||||
@@ -148,6 +148,8 @@
|
||||
"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": "Родительские теги:",
|
||||
|
||||
@@ -157,6 +157,8 @@
|
||||
"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": "பெற்றோர் குறிச்சொற்கள்:",
|
||||
|
||||
@@ -154,6 +154,7 @@
|
||||
"json_migration.heading.aliases": "nimi ante:",
|
||||
"json_migration.heading.colors": "kule:",
|
||||
"json_migration.heading.differ": "ante 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:",
|
||||
|
||||
@@ -136,6 +136,8 @@
|
||||
"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:",
|
||||
|
||||
@@ -152,6 +152,8 @@
|
||||
"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": "上级标签:",
|
||||
|
||||
@@ -156,6 +156,8 @@
|
||||
"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": "父標籤:",
|
||||
|
||||
Binary file not shown.
@@ -7,7 +7,7 @@ from tempfile import TemporaryDirectory
|
||||
|
||||
import pytest
|
||||
|
||||
from tagstudio.core.constants import IGNORE_NAME
|
||||
from tagstudio.core.enums import LibraryPrefs
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.library.refresh import RefreshTracker
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
@@ -20,14 +20,15 @@ CWD = Path(__file__).parent
|
||||
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 set(registry.files_not_in_library) == set([Path(IGNORE_NAME), Path("FOO.MD")])
|
||||
assert registry.files_not_in_library == [Path("FOO.MD")]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("library", [TemporaryDirectory()], indirect=True)
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
from pytestqt.qtbot import QtBot
|
||||
|
||||
from tagstudio.qt.mixed.about_modal import AboutModal
|
||||
|
||||
|
||||
def test_github_api_unavailable(qtbot: QtBot, mocker) -> None:
|
||||
mocker.patch(
|
||||
"requests.get",
|
||||
side_effect=ConnectionError(
|
||||
"Failed to resolve 'api.github.com' ([Errno -3] Temporary failure in name resolution)"
|
||||
),
|
||||
)
|
||||
modal = AboutModal("/tmp")
|
||||
qtbot.addWidget(modal)
|
||||
@@ -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()
|
||||
raise (e)
|
||||
finally:
|
||||
shutil.rmtree(temp_path)
|
||||
raise (e)
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
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__)
|
||||
@@ -42,4 +43,10 @@ def test_json_migration():
|
||||
assert modal.check_color_parity()
|
||||
|
||||
# Extension Filter List ====================================================
|
||||
modal.assert_ignore_parity()
|
||||
# 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] != "."
|
||||
|
||||
@@ -10,6 +10,7 @@ from tempfile import TemporaryDirectory
|
||||
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]
|
||||
@@ -198,6 +199,11 @@ 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]
|
||||
|
||||
@@ -387,6 +393,24 @@ 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
|
||||
|
||||
Reference in New Issue
Block a user