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
+80
View File
@@ -1,4 +1,6 @@
from pathlib import Path
from datetime import datetime
from collections import Counter
import shutil
from .file import File
from .tag_manager import TagManager
@@ -136,6 +138,83 @@ class FileManager:
file_obj = File(each, self.tagmanager, index=self.index)
self.filelist.append(file_obj)
def check_data_consistency(self) -> dict:
"""Compare the pool index against the actual files in pool/Filmy.
Detects drift such as a movie file deleted directly (metadata left behind)
or a file dropped into the pool without importing (no metadata).
Returns a dict with:
ok — False if the pool isn't set (with 'error')
missing — index keys whose file no longer exists on disk
untracked — pool files (pool-relative) with no index entry
index_count / disk_count — totals for the summary
"""
pool = self.pool_dir
movies = self.movies_dir
if not (pool and movies and movies.is_dir()):
return {"ok": False, "error": "Pool není nastaven.",
"missing": [], "untracked": []}
index = self.index or PoolIndex(pool)
index_keys = set(index.records.keys())
disk_keys = set()
for f in list_files(movies):
if f.name.endswith(METADATA_SUFFIXES):
continue
disk_keys.add(f.relative_to(pool).as_posix())
missing = sorted(k for k in index_keys if not (pool / k).is_file())
untracked = sorted(disk_keys - index_keys)
return {
"ok": True,
"missing": missing,
"untracked": untracked,
"index_count": len(index_keys),
"disk_count": len(disk_keys),
}
def statistics(self) -> dict:
"""Aggregate stats over the loaded pool movies for the stats view.
Returns a dict with totals and, per tag category, the tag counts sorted
by frequency (descending).
"""
files = self.filelist
total_size = 0
with_csfd = 0
untagged = 0
ratings: list[int] = []
categories: dict[str, Counter] = {}
for f in files:
try:
total_size += f.file_path.stat().st_size
except OSError:
pass
if f.csfd_link:
with_csfd += 1
if not f.tags:
untagged += 1
for tag in f.tags:
categories.setdefault(tag.category, Counter())[tag.name] += 1
rating = (f.csfd_cache or {}).get("rating")
if isinstance(rating, (int, float)):
ratings.append(rating)
return {
"count": len(files),
"total_size": total_size,
"with_csfd": with_csfd,
"without_csfd": len(files) - with_csfd,
"untagged": untagged,
"avg_rating": round(sum(ratings) / len(ratings), 1) if ratings else None,
"categories": {
cat: counter.most_common() for cat, counter in categories.items()
},
}
def pooled_with_stem(self, title: str) -> list[File]:
"""Pooled movies whose filename stem matches ``title`` (case-insensitive)."""
stem = title.strip().lower()
@@ -204,6 +283,7 @@ class FileManager:
file_obj = File(target, self.tagmanager, index=self.index)
file_obj.title = safe_title
file_obj.csfd_link = csfd_link or None
file_obj.added = datetime.now().isoformat(timespec="seconds")
file_obj.save_metadata()
self.filelist.append(file_obj)