Skip to content

Commit

Permalink
feat: save entry suffix separately
Browse files Browse the repository at this point in the history
  • Loading branch information
yedpodtrzitko committed Sep 14, 2024
1 parent 073d517 commit 854b1a0
Show file tree
Hide file tree
Showing 13 changed files with 216 additions and 61 deletions.
9 changes: 4 additions & 5 deletions tagstudio/src/core/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
TS_FOLDER_NAME: str = ".TagStudio"
BACKUP_FOLDER_NAME: str = "backups"
COLLAGE_FOLDER_NAME: str = "collages"
LIBRARY_FILENAME: str = "ts_library.json"

# TODO: Turn this whitelist into a user-configurable blacklist.
IMAGE_TYPES: list[str] = [
Expand Down Expand Up @@ -122,13 +121,13 @@
+ SHORTCUT_TYPES
)


TAG_FAVORITE = 1
TAG_ARCHIVED = 0


class LibraryPrefs(Enum):
IS_EXCLUDE_LIST = True
EXTENSION_LIST: list[str] = [".json", ".xmp", ".aae"]
IS_EXCLUDE_LIST: bool = True
EXTENSION_LIST: list[str] = ["json", "xmp", "aae"]
PAGE_SIZE: int = 500
DB_VERSION: int = 1
# increase in case of db breaking change (for now)
DB_VERSION: int = 2
40 changes: 40 additions & 0 deletions tagstudio/src/core/driver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from pathlib import Path

import structlog
from PySide6.QtCore import QSettings
from src.core.constants import TS_FOLDER_NAME
from src.core.enums import SettingItems
from src.core.library.alchemy.library import LibraryStatus

logger = structlog.get_logger(__name__)


class DriverMixin:
settings: QSettings

def evaluate_path(self, open_path: str | None) -> LibraryStatus:
"""Check if the path of library is valid."""
library_path: Path | None = None
if open_path:
library_path = Path(open_path)
if not library_path.exists():
logger.error("Path does not exist.", open_path=open_path)
return LibraryStatus(success=False, message="Path does not exist.")
elif self.settings.value(
SettingItems.START_LOAD_LAST, defaultValue=True, type=bool
) and self.settings.value(SettingItems.LAST_LIBRARY):
library_path = Path(str(self.settings.value(SettingItems.LAST_LIBRARY)))
if library_path and not (library_path / TS_FOLDER_NAME).exists():
logger.error(
"TagStudio folder does not exist.",
library_path=library_path,
ts_folder=TS_FOLDER_NAME,
)
self.settings.setValue(SettingItems.LAST_LIBRARY, "")
# dont consider this a fatal error, just skip opening the library
return LibraryStatus(success=True)

return LibraryStatus(
success=True,
library_path=library_path,
)
3 changes: 2 additions & 1 deletion tagstudio/src/core/library/alchemy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from . import constants
from .enums import ItemType
from .library import Library
from .models import Entry, Tag

__all__ = ["Entry", "Library", "Tag", "ItemType"]
__all__ = ["Entry", "Library", "Tag", "ItemType", "constants"]
1 change: 1 addition & 0 deletions tagstudio/src/core/library/alchemy/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
LIBRARY_FILENAME: str = "ts_library.sqlite"
16 changes: 8 additions & 8 deletions tagstudio/src/core/library/alchemy/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,27 @@ class BaseField(Base):
__abstract__ = True

@declared_attr
def id(cls) -> Mapped[int]: # noqa: N805
def id(self) -> Mapped[int]:
return mapped_column(primary_key=True, autoincrement=True)

@declared_attr
def type_key(cls) -> Mapped[str]: # noqa: N805
def type_key(self) -> Mapped[str]:
return mapped_column(ForeignKey("value_type.key"))

@declared_attr
def type(cls) -> Mapped[ValueType]: # noqa: N805
return relationship(foreign_keys=[cls.type_key], lazy=False) # type: ignore
def type(self) -> Mapped[ValueType]:
return relationship(foreign_keys=[self.type_key], lazy=False) # type: ignore

@declared_attr
def entry_id(cls) -> Mapped[int]: # noqa: N805
def entry_id(self) -> Mapped[int]:
return mapped_column(ForeignKey("entries.id"))

@declared_attr
def entry(cls) -> Mapped[Entry]: # noqa: N805
return relationship(foreign_keys=[cls.entry_id]) # type: ignore
def entry(self) -> Mapped[Entry]:
return relationship(foreign_keys=[self.entry_id]) # type: ignore

@declared_attr
def position(cls) -> Mapped[int]: # noqa: N805
def position(self) -> Mapped[int]:
return mapped_column(default=0)

def __hash__(self):
Expand Down
52 changes: 41 additions & 11 deletions tagstudio/src/core/library/alchemy/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
TS_FOLDER_NAME,
LibraryPrefs,
)
from . import constants
from .db import make_tables
from .enums import FieldTypeEnum, FilterState, TagColor
from .fields import (
Expand All @@ -48,8 +49,6 @@
from .joins import TagField, TagSubtag
from .models import Entry, Folder, Preferences, Tag, TagAlias, ValueType

LIBRARY_FILENAME: str = "ts_library.sqlite"

logger = structlog.get_logger(__name__)


Expand Down Expand Up @@ -115,6 +114,13 @@ def __getitem__(self, index: int) -> Entry:
return self.items[index]


@dataclass
class LibraryStatus:
success: bool
library_path: Path | None = None
message: str | None = None


class Library:
"""Class for the Library object, and all CRUD operations made upon it."""

Expand All @@ -130,7 +136,9 @@ def close(self):
self.storage_path = None
self.folder = None

def open_library(self, library_dir: Path | str, storage_path: str | None = None) -> None:
def open_library(
self, library_dir: Path | str, storage_path: str | None = None
) -> LibraryStatus:
if isinstance(library_dir, str):
library_dir = Path(library_dir)

Expand All @@ -139,7 +147,7 @@ def open_library(self, library_dir: Path | str, storage_path: str | None = None)
self.storage_path = storage_path
else:
self.verify_ts_folders(self.library_dir)
self.storage_path = self.library_dir / TS_FOLDER_NAME / LIBRARY_FILENAME
self.storage_path = self.library_dir / TS_FOLDER_NAME / constants.LIBRARY_FILENAME

connection_string = URL.create(
drivername="sqlite",
Expand Down Expand Up @@ -183,6 +191,22 @@ def open_library(self, library_dir: Path | str, storage_path: str | None = None)
logger.debug("ValueType already exists", field=field)
session.rollback()

db_version = session.scalar(
select(Preferences).where(Preferences.key == LibraryPrefs.DB_VERSION.name)
)
# if the db version is different, we cant proceed
if db_version.value != LibraryPrefs.DB_VERSION.value:
logger.error(
"DB version mismatch",
db_version=db_version.value,
expected=LibraryPrefs.DB_VERSION.value,
)
# TODO - handle migration
return LibraryStatus(
success=False,
message="Database version mismatch; delete db file to recreate",
)

# check if folder matching current path exists already
self.folder = session.scalar(select(Folder).where(Folder.path == self.library_dir))
if not self.folder:
Expand All @@ -196,6 +220,8 @@ def open_library(self, library_dir: Path | str, storage_path: str | None = None)
session.commit()
self.folder = folder

return LibraryStatus(success=True)

@property
def default_fields(self) -> list[BaseField]:
with Session(self.engine) as session:
Expand Down Expand Up @@ -324,13 +350,17 @@ def add_entries(self, items: list[Entry]) -> list[int]:

with Session(self.engine) as session:
# add all items
session.add_all(items)
session.flush()

new_ids = [item.id for item in items]
try:
session.add_all(items)
session.flush()
except IntegrityError:
session.rollback()
logger.exception("IntegrityError")
return []

new_ids = [item.id for item in items]
session.expunge_all()

session.commit()

return new_ids
Expand Down Expand Up @@ -396,9 +426,9 @@ def search_library(

if not search.id: # if `id` is set, we don't need to filter by extensions
if extensions and is_exclude_list:
statement = statement.where(Entry.path.notilike(f"%.{','.join(extensions)}"))
statement = statement.where(Entry.suffix.notin_(extensions))
elif extensions:
statement = statement.where(Entry.path.ilike(f"%.{','.join(extensions)}"))
statement = statement.where(Entry.suffix.in_(extensions))

statement = statement.options(
selectinload(Entry.text_fields),
Expand Down Expand Up @@ -770,7 +800,7 @@ def save_library_backup_to_disk(self) -> Path:
target_path = self.library_dir / TS_FOLDER_NAME / BACKUP_FOLDER_NAME / filename

shutil.copy2(
self.library_dir / TS_FOLDER_NAME / LIBRARY_FILENAME,
self.library_dir / TS_FOLDER_NAME / constants.LIBRARY_FILENAME,
target_path,
)

Expand Down
3 changes: 3 additions & 0 deletions tagstudio/src/core/library/alchemy/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ class Entry(Base):
folder: Mapped[Folder] = relationship("Folder")

path: Mapped[Path] = mapped_column(PathType, unique=True)
suffix: Mapped[str] = mapped_column()

text_fields: Mapped[list[TextField]] = relationship(
back_populates="entry",
Expand Down Expand Up @@ -177,6 +178,8 @@ def __init__(
self.path = path
self.folder = folder

self.suffix = path.suffix and path.suffix.lstrip(".").lower()

for field in fields:
if isinstance(field, TextField):
self.text_fields.append(field)
Expand Down
1 change: 1 addition & 0 deletions tagstudio/src/core/library/json/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
LIBRARY_FILENAME: str = "ts_library.json"
2 changes: 1 addition & 1 deletion tagstudio/src/qt/modals/file_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def save(self):
for i in range(self.table.rowCount()):
ext = self.table.item(i, 0)
if ext and ext.text().strip():
extensions.append(ext.text().strip().lower())
extensions.append(ext.text().strip().lstrip(".").lower())

# save preference
self.lib.set_prefs(LibraryPrefs.EXTENSION_LIST, extensions)
Loading

0 comments on commit 854b1a0

Please sign in to comment.