cleanup/core/scanner.py
2026-09-05 15:24:52 +02:00

89 lines
2.9 KiB
Python

"""
Walks a vault root and builds FileRecord objects.
Two jobs: filter out junk (config.IGNORE_*), and make a best-guess starting
date for each real file. That date is explicitly NOT trusted as correct —
you said the metadata is often wrong, so this only has to be a reasonable
starting point that you can freely overwrite, never a claim of truth.
"""
from datetime import datetime
from pathlib import Path
from typing import List
from . import config
from .model import FileRecord
def _is_ignored_file(name: str) -> bool:
if name in config.TOOL_STATE_FILENAMES:
return True
return any(p.match(name) for p in config.IGNORE_FILE_PATTERNS)
def _is_inside_ignored_dir(rel_parts) -> bool:
return any(
part in config.IGNORE_DIR_NAMES or part.startswith(".")
for part in rel_parts[:-1] # everything except the filename itself
)
def _best_guess_date(stat) -> str:
"""True creation time isn't reliably available on ext4 via stat() —
this is a heuristic (earliest of mtime/ctime, or real birthtime if the
platform exposes it), not an authoritative answer."""
ts = getattr(stat, "st_birthtime", None)
if ts is None:
ts = min(stat.st_mtime, stat.st_ctime)
return datetime.fromtimestamp(ts).strftime("%Y-%m-%d")
def discover_folders(root: Path) -> List[str]:
"""Every real directory under root, as paths relative to root. This is
the live, always-current list of possible destinations — it updates
automatically as your folder structure changes, nothing hardcoded."""
folders = []
for p in root.rglob("*"):
if p.is_dir() and p.name not in config.IGNORE_DIR_NAMES and not p.name.startswith("."):
folders.append(str(p.relative_to(root)))
return sorted(folders)
def scan(root: Path) -> List[FileRecord]:
"""Scan root recursively and return one FileRecord per real, non-junk file."""
records = []
for p in root.rglob("*"):
if p.is_dir():
continue
rel_parts = p.relative_to(root).parts
if _is_inside_ignored_dir(rel_parts):
continue
if _is_ignored_file(p.name):
continue
stat = p.stat()
fs_date = _best_guess_date(stat)
name_ok = bool(config.ALREADY_CONVENTIONED.match(p.name))
near = (not name_ok) and any(
pat.match(p.name) for pat in config.NEAR_CONVENTION_PATTERNS
)
rec = FileRecord(
path=p,
root=root,
filename=p.name,
ext=p.suffix,
fs_date=fs_date,
name_matches_convention=name_ok,
near_miss=near,
)
# Defaults: no change unless you edit something. Files that already
# match the convention default to "keep name, move only" — see
# FileRecord.keep_filename for why.
rec.date_override = fs_date
rec.dest_folder = rec.current_folder
rec.keep_filename = name_ok
records.append(rec)
return records