diff --git a/src/tagstudio/core/library/alchemy/constants.py b/src/tagstudio/core/library/alchemy/constants.py index 845de2a87..73493c9af 100644 --- a/src/tagstudio/core/library/alchemy/constants.py +++ b/src/tagstudio/core/library/alchemy/constants.py @@ -9,7 +9,7 @@ DB_VERSION_CURRENT_KEY: str = "CURRENT" DB_VERSION_INITIAL_KEY: str = "INITIAL" -DB_VERSION: int = 202 +DB_VERSION: int = 300 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 ea9f20595..33b6ffb63 100644 --- a/src/tagstudio/core/library/alchemy/library.py +++ b/src/tagstudio/core/library/alchemy/library.py @@ -18,7 +18,6 @@ from os import makedirs from pathlib import Path from typing import TYPE_CHECKING -from uuid import uuid4 import sqlalchemy import structlog @@ -95,7 +94,6 @@ from tagstudio.core.library.alchemy.joins import TagEntry, TagParent from tagstudio.core.library.alchemy.models import ( Entry, - Folder, Namespace, Tag, TagAlias, @@ -234,7 +232,6 @@ class Library: library_dir: Path | None = None engine: Engine | None = None - folder: Folder | None = None included_files: set[Path] = set() def __init__(self) -> None: @@ -259,7 +256,6 @@ def migrate_json_to_sqlite(self, json_lib: JsonLibrary): """Migrate JSON library data to the SQLite database.""" logger.info("Starting Library Conversion...") start_time = time.time() - folder: Folder = Folder(path=self.library_dir, uuid=str(uuid4())) # Tags for tag in json_lib.tags: @@ -312,7 +308,6 @@ def migrate_json_to_sqlite(self, json_lib: JsonLibrary): [ Entry( path=entry.path / entry.filename, - folder=folder, fields=[], id=entry.id + 1, # NOTE: JSON IDs start at 0 instead of 1 date_added=datetime.now(), @@ -479,16 +474,6 @@ def create_sqlite_library( session.add(Version(key=DB_VERSION_CURRENT_KEY, value=DB_VERSION)) session.flush() - # add folder for current path - folder = Folder( - path=library_dir, - uuid=str(uuid4()), - ) - session.add(folder) - session.expunge(folder) - session.flush() - self.folder = folder - # Generate default .ts_ignore file try: ts_ignore_template = ( @@ -584,6 +569,7 @@ def open_sqlite_library( (self.__apply_db200_migration, 200, None), # changes: field tables (self.__apply_db201_migration, 201, 200), # changes: field tables (self.__apply_db202_migration, 202, None), # changes: tag_parents + (self.__apply_db300_migration, 300, None), # changes: deletes folders ] for migration, v, iv in migrations: if loaded_db_version < v and (iv is None or initial_db_version < iv): @@ -601,22 +587,6 @@ def open_sqlite_library( ) logger.info(f"[Library] Library migrated to DB version {DB_VERSION}") - with Session(self.engine) as session: - # TODO: the folder logic has no use and was never finished, remove it - # check if folder matching current path exists already - # NOTE: this has been causing new Folders to be created when the library is moved, since - # its introduction - self.folder = session.scalar(select(Folder).where(Folder.path == library_dir)) - if not self.folder: - folder = Folder( - path=library_dir, - uuid=str(uuid4()), - ) - session.add(folder) - session.expunge(folder) - session.commit() - self.folder = folder - # everything is fine, set the library path self.library_dir = library_dir return LibraryStatus(success=True, library_path=library_dir) @@ -906,6 +876,44 @@ def __apply_db202_migration(self, session: Session, library_dir: Path): session.flush() logger.info("[Library][Migration][202] Verified TagParent table data") + def __apply_db300_migration(self, session: Session, library_dir: Path): + ## remove folder_id column from entries table + # create new table in the desired scheme (without folder_id column) + session.execute( + text(""" + CREATE TABLE entries_new ( + id INTEGER NOT NULL, + path VARCHAR NOT NULL, + suffix VARCHAR NOT NULL, + date_created DATETIME, + date_modified DATETIME, + date_added DATETIME, + filename TEXT NOT NULL DEFAULT '', + PRIMARY KEY (id), + UNIQUE (path) + ) + """) + ) + session.flush() + # transfer data to new table + session.execute( + text(""" + INSERT INTO entries_new (id, path, suffix, date_created, date_modified, date_added, + filename) + SELECT id, path, suffix, date_created, date_modified, date_added, filename + FROM entries + """) + ) + # delete old table + session.execute(text("DROP TABLE entries")) + # rename new table to old table + session.execute(text("ALTER TABLE entries_new RENAME TO entries")) + session.flush() + + ## drop table "folders" + session.execute(text("DROP TABLE folders")) + session.flush() + @property def field_templates(self) -> Sequence[BaseFieldTemplate]: with Session(self.engine) as session: diff --git a/src/tagstudio/core/library/alchemy/models.py b/src/tagstudio/core/library/alchemy/models.py index 668c80f5c..0b0a31c22 100644 --- a/src/tagstudio/core/library/alchemy/models.py +++ b/src/tagstudio/core/library/alchemy/models.py @@ -182,23 +182,11 @@ def __ge__(self, other: "Tag") -> bool: return self.name >= other.name -class Folder(Base): - __tablename__ = "folders" - - # TODO - implement this - id: Mapped[int] = mapped_column(primary_key=True) - path: Mapped[Path] = mapped_column(PathType, unique=True) - uuid: Mapped[str] = mapped_column(unique=True) - - class Entry(Base): __tablename__ = "entries" id: Mapped[int] = mapped_column(primary_key=True) - folder_id: Mapped[int] = mapped_column(ForeignKey("folders.id")) - folder: Mapped[Folder] = relationship("Folder") - path: Mapped[Path] = mapped_column(PathType, unique=True) filename: Mapped[str] = mapped_column() suffix: Mapped[str] = mapped_column() @@ -235,7 +223,6 @@ def is_archived(self) -> bool: def __init__( self, path: Path, - folder: Folder, fields: list[BaseField], id: int | None = None, date_created: dt | None = None, @@ -244,7 +231,6 @@ def __init__( ) -> None: super().__init__() self.path = path - self.folder = folder self.id = id # pyright: ignore[reportAttributeAccessIssue] self.filename = path.name self.suffix = path.suffix.lstrip(".").lower() diff --git a/src/tagstudio/core/library/refresh.py b/src/tagstudio/core/library/refresh.py index 824cae527..e0267b295 100644 --- a/src/tagstudio/core/library/refresh.py +++ b/src/tagstudio/core/library/refresh.py @@ -16,7 +16,6 @@ from tagstudio.core.library.alchemy.models import Entry from tagstudio.core.library.ignore import PATH_GLOB_FLAGS, Ignore, ignore_to_glob from tagstudio.core.utils.silent_subprocess import silent_run # pyright: ignore -from tagstudio.core.utils.types import unwrap logger = structlog.get_logger(__name__) @@ -41,7 +40,6 @@ def save_new_files(self) -> Iterator[int]: entries = [ Entry( path=entry_path, - folder=unwrap(self.library.folder), fields=[], date_added=dt.now(), ) diff --git a/tests/conftest.py b/tests/conftest.py index 28b1d5777..6a7a30706 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -20,7 +20,6 @@ from tagstudio.core.constants import THUMB_CACHE_NAME, TS_FOLDER_NAME from tagstudio.core.library.alchemy.library import Library from tagstudio.core.library.alchemy.models import Entry, Tag -from tagstudio.core.utils.types import unwrap from tagstudio.qt.thumb_grid_layout import ThumbGridLayout from tagstudio.qt.ts_qt import QtDriver @@ -36,22 +35,18 @@ def file_mediatypes_library(): status = lib.open_library(Path(""), in_memory=True) assert status.success - folder = unwrap(lib.folder) entry1 = Entry( - folder=folder, path=Path("foo.png"), fields=[TextField(name="Title", value="I'm a Test Title")], ) entry2 = Entry( - folder=folder, path=Path("bar.png"), fields=[TextField(name="Title", value="I'm a Test Title")], ) entry3 = Entry( - folder=folder, path=Path("baz.apng"), fields=[TextField(name="Title", value="I'm a Test Title")], ) @@ -87,7 +82,6 @@ def library(request, library_dir: Path): # pyright: ignore lib = Library() status = lib.open_library(library_path, in_memory=True) assert status.success - folder = unwrap(lib.folder) tag = Tag( name="foo", @@ -116,7 +110,6 @@ def library(request, library_dir: Path): # pyright: ignore # default item with deterministic name entry = Entry( id=1, - folder=folder, path=Path("foo.txt"), fields=[TextField(name="Title", value="I'm a Test Title")], ) @@ -124,7 +117,6 @@ def library(request, library_dir: Path): # pyright: ignore entry2 = Entry( id=2, - folder=folder, path=Path("one/two/bar.md"), fields=[TextField(name="Title", value="I'm a Test Title")], ) diff --git a/tests/fixtures/empty_libraries/DB_VERSION_202/.TagStudio/ts_library.sqlite b/tests/fixtures/empty_libraries/DB_VERSION_202/.TagStudio/ts_library.sqlite new file mode 100644 index 000000000..db9b1c395 Binary files /dev/null and b/tests/fixtures/empty_libraries/DB_VERSION_202/.TagStudio/ts_library.sqlite differ diff --git a/tests/fixtures/search_library/.TagStudio/ts_library.sqlite b/tests/fixtures/search_library/.TagStudio/ts_library.sqlite index c8489518a..0cfb5bed4 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_dupe_files.py b/tests/macros/test_dupe_files.py index cd296e168..bc8b926f5 100644 --- a/tests/macros/test_dupe_files.py +++ b/tests/macros/test_dupe_files.py @@ -8,25 +8,21 @@ from tagstudio.core.library.alchemy.library import Library from tagstudio.core.library.alchemy.models import Entry from tagstudio.core.library.alchemy.registries.dupe_files_registry import DupeFilesRegistry -from tagstudio.core.utils.types import unwrap CWD = Path(__file__).parent def test_refresh_dupe_files(library: Library): library.library_dir = Path("/tmp/") - folder = unwrap(library.folder) fields: list[BaseField] = [TextField(name="Title", value="I'm a Test Title")] entry = Entry( - folder=folder, path=Path("bar/foo.txt"), fields=fields, ) entry2 = Entry( - folder=folder, path=Path("foo/foo.txt"), fields=fields, ) diff --git a/tests/test_library.py b/tests/test_library.py index 13b99dd58..e260225a8 100644 --- a/tests/test_library.py +++ b/tests/test_library.py @@ -77,7 +77,6 @@ def test_library_add_file(library: Library): """Check Entry.path handling for insert vs lookup""" entry = Entry( path=Path("bar.txt"), - folder=unwrap(library.folder), fields=[TextField(name="Title", value="I'm a Test Title")], ) @@ -139,8 +138,7 @@ def test_get_entry(library: Library, entry_min: Entry): def test_entries_count(library: Library): - folder = unwrap(library.folder) - entries = [Entry(path=Path(f"{x}.txt"), folder=folder, fields=[]) for x in range(10)] + entries = [Entry(path=Path(f"{x}.txt"), fields=[]) for x in range(10)] new_ids = library.add_entries(entries) assert len(new_ids) == 10 @@ -254,7 +252,6 @@ def test_update_entry_with_multiple_identical_text_fields(library: Library, entr def test_mirror_entry_fields(library: Library): # Create and add entries with fields entry_a = Entry( - folder=unwrap(library.folder), path=Path("title_and_date.txt"), fields=[ TextField(name="Title", value="I'm a Test Title"), @@ -262,7 +259,6 @@ def test_mirror_entry_fields(library: Library): ], ) entry_b = Entry( - folder=unwrap(library.folder), path=Path("notes.txt"), fields=[ TextField(name="Notes", value="These are my notes.\nNo peeking!", is_multiline=True), @@ -270,7 +266,6 @@ def test_mirror_entry_fields(library: Library): ], ) entry_c = Entry( - folder=unwrap(library.folder), path=Path("date_published.txt"), fields=[ DatetimeField(name="Date Published", value="2000-01-01 12:00:00"), @@ -319,14 +314,11 @@ def test_mirror_entry_fields(library: Library): def test_merge_entries(library: Library): - folder = unwrap(library.folder) - tag_0: Tag = unwrap(library.add_tag(Tag(id=1010, name="tag_0"))) tag_1: Tag = unwrap(library.add_tag(Tag(id=1011, name="tag_1"))) tag_2: Tag = unwrap(library.add_tag(Tag(id=1012, name="tag_2"))) entry_a = Entry( - folder=folder, path=Path("a"), fields=[ TextField(name="Author", value="Author McAuthorson"), @@ -334,7 +326,6 @@ def test_merge_entries(library: Library): ], ) entry_b = Entry( - folder=folder, path=Path("b"), fields=[TextField(name="Notes", value="test note", is_multiline=True)], )