diff --git a/src/tagstudio/core/library/alchemy/enums.py b/src/tagstudio/core/library/alchemy/enums.py index e2da73823..430e084fb 100644 --- a/src/tagstudio/core/library/alchemy/enums.py +++ b/src/tagstudio/core/library/alchemy/enums.py @@ -69,7 +69,8 @@ class SortingModeEnum(enum.Enum): DATE_ADDED = "file.date_added" FILE_NAME = "generic.filename" PATH = "file.path" - + DATE_CREATED = "file.date_created" + DATE_MODIFIED = "file.date_modified" @dataclass class FilterState: diff --git a/src/tagstudio/core/library/alchemy/library.py b/src/tagstudio/core/library/alchemy/library.py index bb9326854..955362bc2 100644 --- a/src/tagstudio/core/library/alchemy/library.py +++ b/src/tagstudio/core/library/alchemy/library.py @@ -15,6 +15,7 @@ from typing import TYPE_CHECKING from uuid import uuid4 from warnings import catch_warnings +import platform import structlog from humanfriendly import format_timespan @@ -223,6 +224,19 @@ def close(self): self.folder = None self.included_files = set() + def get_file_time(self, file_path: Path): + """Get the creation and modification times of a file.""" + stat = file_path.stat() + + # st_birthtime on Windows and Mac, st_ctime on Linux. + if platform.system() in ["Windows", "Darwin"]: # Windows & macOS + date_created = datetime.fromtimestamp(stat.st_birthtime) + else: # Linux + date_created = datetime.fromtimestamp(stat.st_ctime) # Linux lacks st_birthtime + + date_modified = datetime.fromtimestamp(stat.st_mtime) + return date_created, date_modified + def migrate_json_to_sqlite(self, json_lib: JsonLibrary): """Migrate JSON library data to the SQLite database.""" logger.info("Starting Library Conversion...") @@ -284,8 +298,12 @@ def migrate_json_to_sqlite(self, json_lib: JsonLibrary): fields=[], id=entry.id + 1, # JSON IDs start at 0 instead of 1 date_added=datetime.now(), + date_modified=date_modified, + date_created=date_created, ) for entry in json_lib.entries + if (date_created := self.get_file_time(entry.path / entry.filename)[0]) is not None + and (date_modified := self.get_file_time(entry.path / entry.filename)[1]) is not None ] ) for entry in json_lib.entries: @@ -896,6 +914,10 @@ def search_library( match search.sorting_mode: case SortingModeEnum.DATE_ADDED: sort_on = Entry.id + case SortingModeEnum.DATE_CREATED: + sort_on = Entry.date_created + case SortingModeEnum.DATE_MODIFIED: + sort_on = Entry.date_modified case SortingModeEnum.FILE_NAME: sort_on = func.lower(Entry.filename) case SortingModeEnum.PATH: diff --git a/src/tagstudio/core/library/alchemy/models.py b/src/tagstudio/core/library/alchemy/models.py index f85a02a44..c7514e97a 100644 --- a/src/tagstudio/core/library/alchemy/models.py +++ b/src/tagstudio/core/library/alchemy/models.py @@ -280,7 +280,7 @@ class ValueType(Base): is_default: Mapped[bool] position: Mapped[int] - # add relations to other tables + # add relations to otheu tables text_fields: Mapped[list[TextField]] = relationship("TextField", back_populates="type") datetime_fields: Mapped[list[DatetimeField]] = relationship( "DatetimeField", back_populates="type" diff --git a/src/tagstudio/core/utils/refresh_dir.py b/src/tagstudio/core/utils/refresh_dir.py index ddbff94e1..e14f2d5d4 100644 --- a/src/tagstudio/core/utils/refresh_dir.py +++ b/src/tagstudio/core/utils/refresh_dir.py @@ -1,11 +1,11 @@ +import datetime as dt from collections.abc import Iterator from dataclasses import dataclass, field -from datetime import datetime as dt from pathlib import Path from time import time +import platform import structlog - from tagstudio.core.constants import TS_FOLDER_NAME from tagstudio.core.library.alchemy.library import Library from tagstudio.core.library.alchemy.models import Entry @@ -26,7 +26,6 @@ ] ) - @dataclass class RefreshDirTracker: library: Library @@ -36,6 +35,20 @@ class RefreshDirTracker: def files_count(self) -> int: return len(self.files_not_in_library) + # Created get_file_time() (basically same thing) in library + def get_file_times(self, file_path: Path): + """Get the creation and modification times of a file.""" + stat = file_path.stat() + + # st_birthtime on Windows and Mac, st_ctime on Linux. + if platform.system() in ["Windows", "Darwin"]: # Windows & macOS + date_created = dt.datetime.fromtimestamp(stat.st_birthtime) + else: # Linux + date_created = dt.datetime.fromtimestamp(stat.st_ctime) # Linux lacks st_birthtime + + date_modified = dt.datetime.fromtimestamp(stat.st_mtime) + return date_created, date_modified + def save_new_files(self): """Save the list of files that are not in the library.""" if self.files_not_in_library: @@ -44,9 +57,13 @@ def save_new_files(self): path=entry_path, folder=self.library.folder, fields=[], - date_added=dt.now(), + date_added=dt.datetime.now(), + date_created=date_created, + date_modified=date_modified, ) for entry_path in self.files_not_in_library + if (date_created := self.get_file_times(entry_path)[0]) is not None + and (date_modified := self.get_file_times(entry_path)[1]) is not None ] self.library.add_entries(entries) @@ -98,7 +115,7 @@ def refresh_dir(self, lib_path: Path) -> Iterator[int]: relative_path = f.relative_to(lib_path) # TODO - load these in batch somehow if not self.library.has_path_entry(relative_path): - self.files_not_in_library.append(relative_path) + self.files_not_in_library.append(f) end_time_total = time() yield dir_file_count