Add statistics and data-consistency menus, recently-added folder
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user