Add statistics and data-consistency menus, recently-added folder

This commit is contained in:
2026-07-02 06:10:14 +02:00
parent b5c2023212
commit f0f38d4257
17 changed files with 498 additions and 78 deletions
+12
View File
@@ -335,6 +335,18 @@ class TestApplyCsfdTags:
movie_file.add_tag("Rok/1999")
assert movie_file.name_context()["year"] == "1999"
def test_added_timestamp_uses_stored_added(self, movie_file):
movie_file.added = "2026-06-16T12:00:00"
from datetime import datetime
assert movie_file.added_timestamp() == datetime.fromisoformat(
"2026-06-16T12:00:00").timestamp()
def test_added_timestamp_falls_back_to_mtime(self, movie_file):
movie_file.added = None
import os
expected = os.stat(movie_file.file_path).st_mtime
assert movie_file.added_timestamp() == expected
def test_set_attribute_persists_and_in_context(self, movie_file):
movie_file.set_attribute("collection_sort", "03")
assert movie_file.attributes["collection_sort"] == "03"
+66
View File
@@ -592,6 +592,72 @@ class TestPoolManagement:
assert movie.csfd_link == "https://csfd.cz/film/1"
assert file_manager.index.get(movie.file_path) is not None
def test_statistics_aggregates_pool(self, file_manager, tmp_path):
file_manager.set_pool_dir(tmp_path / "pool")
(tmp_path / "a.mkv").write_bytes(b"x" * 100)
(tmp_path / "b.mkv").write_bytes(b"y" * 50)
a = file_manager.import_movie(tmp_path / "a.mkv", "A", "https://csfd/1")
a.add_tag("Žánr/Akční")
a.add_tag("Žánr/Sci-Fi")
a.csfd_cache = {"rating": 90}
a.save_metadata()
b = file_manager.import_movie(tmp_path / "b.mkv", "B")
b.add_tag("Žánr/Akční")
s = file_manager.statistics()
assert s["count"] == 2
assert s["total_size"] == 150
assert s["with_csfd"] == 1 and s["without_csfd"] == 1
assert s["untagged"] == 0
assert s["avg_rating"] == 90
# Akční on both movies → count 2, sorted first
assert dict(s["categories"]["Žánr"])["Akční"] == 2
assert s["categories"]["Žánr"][0] == ("Akční", 2)
def test_check_data_consistency_clean(self, file_manager, tmp_path):
file_manager.set_pool_dir(tmp_path / "pool")
(tmp_path / "a.mkv").write_bytes(b"a")
file_manager.import_movie(tmp_path / "a.mkv", "Matrix")
result = file_manager.check_data_consistency()
assert result["ok"]
assert result["missing"] == []
assert result["untracked"] == []
assert result["index_count"] == 1 and result["disk_count"] == 1
def test_check_data_consistency_detects_missing_file(self, file_manager, tmp_path):
file_manager.set_pool_dir(tmp_path / "pool")
(tmp_path / "a.mkv").write_bytes(b"a")
movie = file_manager.import_movie(tmp_path / "a.mkv", "Matrix")
movie.file_path.unlink() # someone deleted the file directly
result = file_manager.check_data_consistency()
assert result["missing"] == ["Filmy/Matrix.mkv"]
assert result["untracked"] == []
def test_check_data_consistency_detects_untracked_file(self, file_manager, tmp_path):
file_manager.set_pool_dir(tmp_path / "pool")
(tmp_path / "a.mkv").write_bytes(b"a")
file_manager.import_movie(tmp_path / "a.mkv", "Matrix")
# a file dropped into the pool without importing
(file_manager.movies_dir / "Sneaked.mkv").write_bytes(b"x")
result = file_manager.check_data_consistency()
assert result["missing"] == []
assert result["untracked"] == ["Filmy/Sneaked.mkv"]
def test_import_movie_records_added_timestamp(self, file_manager, tmp_path):
file_manager.set_pool_dir(tmp_path / "pool")
source = tmp_path / "raw.mkv"
source.write_bytes(b"x")
movie = file_manager.import_movie(source, "Matrix")
assert movie.added is not None
# parseable ISO timestamp, persisted in the index
from datetime import datetime
datetime.fromisoformat(movie.added)
assert file_manager.index.get(movie.file_path)["added"] == movie.added
def test_import_movie_move_removes_source(self, file_manager, tmp_path):
file_manager.set_pool_dir(tmp_path / "pool")
source = tmp_path / "raw.mkv"
+43
View File
@@ -143,6 +143,32 @@ class TestHardlinkManager:
# The mirror (reserved folder) is left alone
assert mirror_link.exists()
def test_sync_removes_orphan_link_from_replaced_movie(
self, temp_source_dir, temp_output_dir, tag_manager
):
"""A stale link pointing at a no-longer-pooled inode is cleaned up."""
f = File(temp_source_dir / "file1.txt", tag_manager)
f.tags.clear()
f.add_tag(Tag("žánr", "Drama")) # a root-level category enables the sweep
f.add_tag(Tag("rok", "1968"))
roots = {"žánr": "", "rok": "- Dle roku"}
manager = HardlinkManager(temp_output_dir)
manager.sync_structure([f], category_roots=roots)
# Simulate a leftover from an OLD layout: a different file (different
# inode, not in the pool) hardlinked under an old unprefixed folder.
orphan_src = temp_source_dir / "orphan.txt"
orphan_src.write_text("old data")
old_dir = temp_output_dir / "Dle roku" / "1968"
old_dir.mkdir(parents=True)
os.link(orphan_src, old_dir / "file1.txt")
manager.sync_structure([f], category_roots=roots)
# new prefixed folder kept, stale unprefixed one (orphan) removed
assert (temp_output_dir / "- Dle roku" / "1968" / "file1.txt").exists()
assert not (temp_output_dir / "Dle roku").exists()
def test_sync_removes_root_genre_folder_when_last_movie_drops_tag(
self, temp_source_dir, temp_output_dir, tag_manager
):
@@ -255,6 +281,23 @@ class TestHardlinkManager:
tips = temp_output_dir / "Tipy dne"
assert len(list(tips.iterdir())) == 1 # not accumulated to 2
def test_generate_recently_added_orders_by_added(
self, files_with_tags, temp_output_dir
):
"""The newest-added movies (by added_timestamp) fill the folder."""
# explicit added timestamps: file2 newest, file1 middle, file3 oldest
files_with_tags[0].added = "2026-01-02T00:00:00"
files_with_tags[1].added = "2026-01-03T00:00:00"
files_with_tags[2].added = "2026-01-01T00:00:00"
manager = HardlinkManager(temp_output_dir)
created, fail = manager.generate_recently_added(
files_with_tags, count=2, subfolder="- Nově přidané")
recent = temp_output_dir / "- Nově přidané"
assert created == 2 and fail == 0
names = {p.name for p in recent.iterdir()}
assert names == {"file2.txt", "file1.txt"} # newest two, file3 excluded
def test_dry_run(self, files_with_tags, temp_output_dir):
"""Test dry run (bez skutečného vytváření)"""
manager = HardlinkManager(temp_output_dir)