diff --git a/alembic/versions/36cae041ad31_files.py b/alembic/versions/36cae041ad31_files.py index df8a7dfd..8e69db38 100644 --- a/alembic/versions/36cae041ad31_files.py +++ b/alembic/versions/36cae041ad31_files.py @@ -32,7 +32,8 @@ def upgrade(): modification_datetime TIMESTAMPTZ, textsearch tsvector GENERATED ALWAYS AS ( setweight(to_tsvector('english'::regconfig, title), 'A'::"char") - ) stored + ) stored, + idempotency TEXT )''') session.execute('CREATE INDEX file_path_idx ON file(path)') session.execute('CREATE INDEX file_mimetype_idx ON file(textsearch)') diff --git a/modules/files/lib.py b/modules/files/lib.py index 24cab77e..45a15108 100644 --- a/modules/files/lib.py +++ b/modules/files/lib.py @@ -3,6 +3,7 @@ import re from functools import wraps from pathlib import Path from typing import List +from uuid import uuid4 import magic import psycopg2 @@ -17,6 +18,8 @@ from wrolpi.vars import PYTEST logger = logger.getChild(__name__) +__all__ = ['list_files', 'delete_file', 'split_file_name', 'upsert_file', 'refresh_files', 'search'] + def filter_parent_directories(directories: List[Path]) -> List[Path]: """ @@ -108,11 +111,21 @@ def upsert_file(path: Path, session: Session) -> File: def _refresh_files(): """Find and index all files""" logger.info('Refreshing Files') + # Mark all files as unverified. Any records that are unverified after refresh will be deleted. + with get_db_curs(commit=True) as curs: + curs.execute('UPDATE file SET idempotency=null') + paths = filter(lambda i: i.is_file(), walk(get_media_directory())) + idempotency = str(uuid4()) for chunk in chunks(paths, 20): with get_db_session(commit=True) as session: for path in chunk: - upsert_file(path, session) + file = upsert_file(path, session) + file.idempotency = idempotency + + # Remove any records where the file no longer exists. + with get_db_curs(commit=True) as curs: + curs.execute('DELETE FROM file WHERE idempotency IS NULL') @wraps(_refresh_files) diff --git a/modules/files/models.py b/modules/files/models.py index e116fc7b..7b8207b4 100644 --- a/modules/files/models.py +++ b/modules/files/models.py @@ -11,6 +11,7 @@ class File(ModelHelper, Base): id = Column(Integer, primary_key=True) path = Column(MediaPathType) + idempotency = Column(String) mimetype = Column(String) modification_datetime = Column(TZDateTime) size = Column(Integer) diff --git a/modules/files/test/test_lib.py b/modules/files/test/test_lib.py index 57088aed..a15862d5 100644 --- a/modules/files/test/test_lib.py +++ b/modules/files/test/test_lib.py @@ -1,9 +1,12 @@ from pathlib import Path +from typing import List, Iterable import pytest from modules.files import lib +from modules.files.models import File from wrolpi.errors import InvalidFile +from wrolpi.media_path import MediaPathType def test_list_files(make_files_structure, test_directory): @@ -137,3 +140,32 @@ def test_delete_file(make_files_structure, test_directory): ) def test_split_file_name(path, expected): assert lib.split_file_name(Path(path)) == expected + + +def test_refresh_files(test_session, make_files_structure, test_directory): + files = make_files_structure([ + 'foo.txt', + 'bar.txt', + 'baz.txt', + ]) + foo, bar, baz = files + + def get_relative_strs(files_: Iterable[MediaPathType]) -> List[str]: + return sorted([str(i.path.path.relative_to(test_directory)) for i in files_]) + + lib.refresh_files() + results = test_session.query(File) + assert get_relative_strs(results) == ['bar.txt', 'baz.txt', 'foo.txt'] + + baz.unlink() + + lib.refresh_files() + results = test_session.query(File) + assert get_relative_strs(results) == ['bar.txt', 'foo.txt'] + + foo.unlink() + bar.unlink() + + lib.refresh_files() + results = test_session.query(File) + assert get_relative_strs(results) == []