mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-09-10 21:06:15 +02:00
feat: store and update file created and modified dates
This commit is contained in:
@@ -94,6 +94,7 @@ from tagstudio.core.library.alchemy.models import (
|
||||
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.stat import get_date_created, get_date_modified
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -650,6 +651,54 @@ class Library:
|
||||
make_transient(entry)
|
||||
return entry
|
||||
|
||||
def refresh_file_entry_stats(self, entry_id: int, path: Path | None):
|
||||
"""Updates a file entry's associated stat() data."""
|
||||
needs_update = False
|
||||
|
||||
entry = self.get_entry_full(entry_id, with_fields=False, with_tags=False)
|
||||
if not entry:
|
||||
return
|
||||
|
||||
if not path:
|
||||
full_path = unwrap(self.library_dir) / entry.path
|
||||
else:
|
||||
full_path = unwrap(self.library_dir) / path
|
||||
|
||||
file_date_created = get_date_created(full_path)
|
||||
file_date_modified = get_date_modified(full_path)
|
||||
|
||||
# Log info
|
||||
if entry.date_created != file_date_created:
|
||||
logger.info(full_path)
|
||||
logger.warning(f"Difference in date_created!: {entry.date_created}/{file_date_created}")
|
||||
needs_update = True
|
||||
# else:
|
||||
# logger.info("No difference in date_created.")
|
||||
|
||||
if entry.date_modified != file_date_modified:
|
||||
logger.info(full_path)
|
||||
logger.warning(
|
||||
f"Difference in date_modified!: {entry.date_modified}/{file_date_modified}"
|
||||
)
|
||||
needs_update = True
|
||||
# else:
|
||||
# logger.info("No difference in date_modified")
|
||||
|
||||
if not needs_update:
|
||||
return
|
||||
else:
|
||||
logger.info(f"Updating entry file_metadata for {full_path}")
|
||||
|
||||
with Session(self.engine) as session:
|
||||
stmt = update(Entry).where(Entry.id == entry_id)
|
||||
if file_date_created:
|
||||
stmt = stmt.values(date_created=file_date_created)
|
||||
if file_date_modified:
|
||||
stmt = stmt.values(date_modified=file_date_modified)
|
||||
|
||||
session.execute(stmt)
|
||||
session.commit()
|
||||
|
||||
def get_tag_entries(
|
||||
self, tag_ids: Iterable[int], entry_ids: Iterable[int]
|
||||
) -> dict[int, set[int]]:
|
||||
@@ -758,10 +807,10 @@ class Library:
|
||||
session.query(Entry).where(Entry.id.in_(sub_list)).delete()
|
||||
session.commit()
|
||||
|
||||
def has_entry_with_path(self, path: Path) -> bool:
|
||||
"""Check if an entry with this path is in the library."""
|
||||
def get_entry_id_from_path(self, path: Path) -> int:
|
||||
"""Attempt to return an Entry ID given a filepath, else return -1."""
|
||||
with Session(self.engine) as session:
|
||||
return session.query(exists().where(Entry.path == path)).scalar()
|
||||
return session.scalar(select(Entry.id).where(Entry.path == path).limit(1)) or -1
|
||||
|
||||
def get_paths(self, limit: int = -1) -> list[str]:
|
||||
path_strings: list[str] = []
|
||||
@@ -1079,7 +1128,7 @@ class Library:
|
||||
|
||||
Returns True if the action succeeded and False if the path already exists.
|
||||
"""
|
||||
if self.has_entry_with_path(path):
|
||||
if self.get_entry_id_from_path(path):
|
||||
return False
|
||||
if isinstance(entry_id, Entry):
|
||||
entry_id = entry_id.id
|
||||
|
||||
@@ -202,8 +202,8 @@ class Entry(Base):
|
||||
path: Mapped[Path] = mapped_column(PathType, unique=True)
|
||||
filename: Mapped[str] = mapped_column()
|
||||
suffix: Mapped[str] = mapped_column()
|
||||
date_created: Mapped[dt | None]
|
||||
date_modified: Mapped[dt | None]
|
||||
date_created: Mapped[float | None]
|
||||
date_modified: Mapped[float | None]
|
||||
date_added: Mapped[dt | None]
|
||||
|
||||
tags: Mapped[set[Tag]] = relationship(secondary="tag_entries")
|
||||
@@ -237,8 +237,8 @@ class Entry(Base):
|
||||
path: Path,
|
||||
fields: list[BaseField],
|
||||
id: int | None = None,
|
||||
date_created: dt | None = None,
|
||||
date_modified: dt | None = None,
|
||||
date_created: float | None = None,
|
||||
date_modified: float | None = None,
|
||||
date_added: dt | None = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
|
||||
@@ -16,6 +16,8 @@ from tagstudio.core.library.alchemy.library import Library
|
||||
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.stat import get_date_created, get_date_modified
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
@@ -41,6 +43,8 @@ class RefreshTracker:
|
||||
Entry(
|
||||
path=entry_path,
|
||||
fields=[],
|
||||
date_created=get_date_created(unwrap(self.library.library_dir) / entry_path),
|
||||
date_modified=get_date_modified(unwrap(self.library.library_dir) / entry_path),
|
||||
date_added=dt.now(),
|
||||
)
|
||||
for entry_path in self.files_not_in_library[index:end]
|
||||
@@ -140,6 +144,10 @@ class RefreshTracker:
|
||||
# Skip if the file/path is already mapped in the Library
|
||||
if f in self.library.included_files:
|
||||
dir_file_count += 1
|
||||
|
||||
entry_id = self.library.get_entry_id_from_path(f)
|
||||
self.library.refresh_file_entry_stats(entry_id, path=f)
|
||||
|
||||
continue
|
||||
|
||||
# Ignore if the file is a directory
|
||||
@@ -149,8 +157,13 @@ class RefreshTracker:
|
||||
dir_file_count += 1
|
||||
self.library.included_files.add(f)
|
||||
|
||||
if not self.library.has_entry_with_path(f):
|
||||
# if not self.library.has_entry_with_path(f):
|
||||
# self.files_not_in_library.append(f)
|
||||
entry_id = self.library.get_entry_id_from_path(f)
|
||||
if entry_id < 0:
|
||||
self.files_not_in_library.append(f)
|
||||
else:
|
||||
self.library.refresh_file_entry_stats(entry_id, path=f)
|
||||
|
||||
end_time_total = time()
|
||||
yield dir_file_count
|
||||
@@ -183,6 +196,9 @@ class RefreshTracker:
|
||||
# Skip if the file/path is already mapped in the Library
|
||||
if f in self.library.included_files:
|
||||
dir_file_count += 1
|
||||
relative_path = f.relative_to(library_dir)
|
||||
entry_id = self.library.get_entry_id_from_path(relative_path)
|
||||
self.library.refresh_file_entry_stats(entry_id, path=relative_path)
|
||||
continue
|
||||
|
||||
# Ignore if the file is a directory
|
||||
@@ -194,8 +210,14 @@ class RefreshTracker:
|
||||
|
||||
relative_path = f.relative_to(library_dir)
|
||||
|
||||
if not self.library.has_entry_with_path(relative_path):
|
||||
# if not self.library.has_entry_with_path(relative_path):
|
||||
# self.files_not_in_library.append(relative_path)
|
||||
entry_id = self.library.get_entry_id_from_path(relative_path)
|
||||
if entry_id < 0:
|
||||
self.files_not_in_library.append(relative_path)
|
||||
else:
|
||||
self.library.refresh_file_entry_stats(entry_id, path=relative_path)
|
||||
|
||||
except ValueError:
|
||||
logger.info("[Refresh]: ValueError when refreshing directory with wcmatch!")
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import platform
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def get_date_modified(path: Path) -> float:
|
||||
return path.stat().st_mtime
|
||||
|
||||
|
||||
def get_date_created(path: Path) -> float:
|
||||
if platform.system() in {"Windows", "Darwin"}:
|
||||
return path.stat().st_birthtime
|
||||
else:
|
||||
return path.stat().st_ctime
|
||||
@@ -80,9 +80,9 @@ def test_library_add_file(library: Library):
|
||||
fields=[TextField(name="Title", value="I'm a Test Title")],
|
||||
)
|
||||
|
||||
assert not library.has_entry_with_path(entry.path)
|
||||
assert not library.get_entry_id_from_path(entry.path)
|
||||
assert library.add_entries([entry])
|
||||
assert library.has_entry_with_path(entry.path)
|
||||
assert library.get_entry_id_from_path(entry.path)
|
||||
|
||||
|
||||
def test_create_tag(library: Library, generate_tag: Callable[..., Tag]):
|
||||
@@ -338,8 +338,8 @@ def test_merge_entries(library: Library):
|
||||
entry_b_: Entry = unwrap(library.get_entry_full(entry_b_id))
|
||||
|
||||
assert library.merge_entries(entry_a_, entry_b_)
|
||||
assert not library.has_entry_with_path(Path("a"))
|
||||
assert library.has_entry_with_path(Path("b"))
|
||||
assert not library.get_entry_id_from_path(Path("a"))
|
||||
assert library.get_entry_id_from_path(Path("b"))
|
||||
|
||||
entry_b_merged = unwrap(library.get_entry_full(entry_b_id))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user