Sync working tree before initial Gitea push

Includes:
- cli.py path fix (parents[1]) for config/catalog resolution
- Library cleanup feature design docs (TODO.md, mockup)
- Audit + bug-queue markdowns from May 2026 reliability pass
- .gitignore expanded for transient artifacts
This commit is contained in:
admin
2026-05-26 22:35:42 +02:00
parent 8d6bdb81af
commit f7fc15b17c
24 changed files with 2938 additions and 41 deletions
+72 -9
View File
@@ -125,10 +125,10 @@ console = Console() # replaced in main() if --no-color
# Default remotes used when --search is invoked without explicit --source/--target.
DEFAULT_SOURCE = ["cq:personal-files/ClearJAV"]
DEFAULT_TARGET = ["cq:personal-files/JAV/TMP"]
DEFAULT_TARGET = ["cq:JAV"]
# Default WinCatalog export folder (or specific files). Folder entries auto-discover *.csv / *.xml.
DEFAULT_CATALOG: list[str] = [str(Path(__file__).resolve().parent / "wincatalog")]
DEFAULT_CATALOG: list[str] = [str(Path(__file__).resolve().parents[1] / "wincatalog")]
from rcjav.catalog import (
CATALOG_COL_NAME,
@@ -162,13 +162,14 @@ from rcjav.dupes import (
)
from rcjav.library import (
find_library_issues,
find_missing_resolution,
rename_file_in_remote,
rename_files_batch,
_bracket_to_canonical,
_nohyphen_to_canonical,
)
CONFIG_PATH = Path(__file__).resolve().parent / "config.json"
CONFIG_PATH = Path(__file__).resolve().parents[1] / "config.json"
def load_config() -> dict:
if not CONFIG_PATH.exists():
@@ -185,7 +186,14 @@ def load_config() -> dict:
def save_config(cfg: dict) -> None:
tmp = CONFIG_PATH.with_suffix(CONFIG_PATH.suffix + ".tmp")
tmp.write_text(json.dumps(cfg, indent=2), encoding="utf-8")
os.replace(tmp, CONFIG_PATH)
try:
os.replace(tmp, CONFIG_PATH)
except PermissionError:
# Windows: destination may be briefly locked by antivirus or a concurrent reader.
# Mirrors save_cache's retry to avoid asymmetric crash-on---save when
# cache writes would succeed under the same conditions.
time.sleep(0.5)
os.replace(tmp, CONFIG_PATH)
# ---------- collectors ----------
@@ -271,7 +279,6 @@ def cached_collect(remotes: list[str], source_label: str,
if use_cache:
cache["remotes"][r] = {
"scanned_at": datetime.now().astimezone().isoformat(),
"recursive": True,
"files": [{"path": e.path, "size": e.size, "mod_time": e.mod_time,
"jav_id": e.jav_id} for e in fresh],
"skipped": local_skipped,
@@ -325,7 +332,6 @@ def cached_collect(remotes: list[str], source_label: str,
if use_cache:
cache["remotes"][r] = {
"scanned_at": datetime.now().astimezone().isoformat(),
"recursive": True,
"files": merged_files,
"skipped": sorted(old_skipped),
}
@@ -392,6 +398,12 @@ def main():
ap.add_argument("--library-issues", action="store_true",
help="Report non-canonical filenames (bracket-wrapped IDs, no-hyphen IDs). "
"Reads from cache. Outputs JSON when --format json, plain otherwise.")
ap.add_argument("--missing-resolution", action="store_true",
help="Report cached video files whose filename does not end with a bracketed "
"[resolution] tag before the extension. No live rclone calls.")
ap.add_argument("--limit", type=int, default=None,
help="Limit displayed/report items for cache audit modes. Use 0 for all. "
"Human --missing-resolution defaults to 100; JSON defaults to all.")
ap.add_argument("--rename-file", action="store_true",
help="Rename one file in a remote and patch cache. "
"Requires --remote, --old-path, --new-path. Outputs JSON.")
@@ -419,7 +431,7 @@ def main():
ap.add_argument("--clearjav", action="store_true",
help="Shortcut: use DEFAULT_SOURCE as --source and DEFAULT_TARGET as --target, "
"Equivalent to "
"`--source cq:personal-files/ClearJAV --target cq:personal-files/JAV/TMP`.")
"`--source cq:personal-files/ClearJAV --target cq:JAV`.")
args = ap.parse_args()
global console, BASIC, DEFAULT_SOURCE, DEFAULT_TARGET, DEFAULT_CATALOG
@@ -576,13 +588,15 @@ def main():
if args.library_issues:
cache = load_cache()
issues = find_library_issues(cache)
issues = find_library_issues(cache, cfg)
if args.format == "json" or BASIC:
print(json.dumps({"ok": True, **issues}))
else:
bracket = issues["bracket_names"]
nohyphen = issues["nohyphen_names"]
total = len(bracket) + len(nohyphen)
missing = issues.get("missing_resolution", [])
noncanonical = issues.get("resolution_noncanonical", [])
total = len(bracket) + len(nohyphen) + len(missing) + len(noncanonical)
if not total:
console.print(Panel("[bold green]No library issues found.[/]", title="Library Issues"))
else:
@@ -598,6 +612,55 @@ def main():
for e in nohyphen:
t.add_row("no hyphen", Path(e["path"]).name,
e["canonical_name"], e["remote"])
for e in noncanonical:
kinds = ", ".join(i.get("kind", "") for i in e.get("issues", []) if i.get("kind"))
t.add_row("resolution style", Path(e["path"]).name,
kinds or "noncanonical", e["remote"])
for e in missing:
kinds = ", ".join(i.get("kind", "") for i in e.get("issues", []) if i.get("kind"))
t.add_row("missing resolution", Path(e["path"]).name,
kinds or "needs probe", e["remote"])
console.print(t)
sys.exit(0)
if args.missing_resolution:
cache = load_cache()
report = find_missing_resolution(cache, cfg)
limit = args.limit
if limit is None:
limit = None if args.format == "json" else 100
items = report["items"] if limit in (None, 0) else report["items"][:max(0, limit)]
out = {
"ok": True,
**report,
"shown": len(items),
"truncated": len(items) < report["count"],
"items": items,
}
if args.format == "json":
print(json.dumps(out))
elif BASIC:
for item in items:
print(item["full_path"])
if out["truncated"]:
print(f"# Showing {out['shown']} of {out['count']}. Use --limit 0 to show all.", file=sys.stderr)
else:
total = report["count"]
if not total:
console.print(Panel("[bold green]No missing resolution tags found.[/]", title="Missing Resolution"))
else:
from rich.table import Table
by_ext = ", ".join(f"{k}: {v:,}" for k, v in report["by_extension"].items()) or "none"
summary = f"{total:,} file(s) missing final bracketed [resolution] tag\n{by_ext}"
if out["truncated"]:
summary += f"\nShowing first {out['shown']:,}. Use --limit 0 to show all, or --format json for machine output."
console.print(Panel(summary, title="Missing Resolution", border_style="yellow"))
t = Table(title="Cached files", show_lines=False)
t.add_column("Path")
t.add_column("Remote", style="dim")
t.add_column("Size", justify="right")
for e in items:
t.add_row(e["path"], e["remote"], e["size_human"])
console.print(t)
sys.exit(0)