Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/tagstudio/core/library/alchemy/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
70 changes: 39 additions & 31 deletions src/tagstudio/core/library/alchemy/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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 = (
Expand Down Expand Up @@ -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):
Expand All @@ -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)
Expand Down Expand Up @@ -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:
Expand Down
14 changes: 0 additions & 14 deletions src/tagstudio/core/library/alchemy/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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,
Expand All @@ -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()
Expand Down
2 changes: 0 additions & 2 deletions src/tagstudio/core/library/refresh.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)

Expand All @@ -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(),
)
Expand Down
8 changes: 0 additions & 8 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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")],
)
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -116,15 +110,13 @@ 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")],
)
assert lib.add_tags_to_entries(entry.id, tag.id)

entry2 = Entry(
id=2,
folder=folder,
path=Path("one/two/bar.md"),
fields=[TextField(name="Title", value="I'm a Test Title")],
)
Expand Down
Binary file not shown.
Binary file modified tests/fixtures/search_library/.TagStudio/ts_library.sqlite
Binary file not shown.
4 changes: 0 additions & 4 deletions tests/macros/test_dupe_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down
11 changes: 1 addition & 10 deletions tests/test_library.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")],
)

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -254,23 +252,20 @@ 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"),
DatetimeField(name="Date", value="2026-05-07 12:59:24"),
],
)
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),
TextField(name="Title", value="I'm a Test Title"),
],
)
entry_c = Entry(
folder=unwrap(library.folder),
path=Path("date_published.txt"),
fields=[
DatetimeField(name="Date Published", value="2000-01-01 12:00:00"),
Expand Down Expand Up @@ -319,22 +314,18 @@ 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"),
TextField(name="Description", value="test description", is_multiline=True),
],
)
entry_b = Entry(
folder=folder,
path=Path("b"),
fields=[TextField(name="Notes", value="test note", is_multiline=True)],
)
Expand Down
Loading