Files
FCCS/fccs_scan.py

128 lines
4.5 KiB
Python

"""
Step 1: Scan a restored FCCS backup directory for drawer IDs.
Every subdirectory in the backup root corresponds to a drawer.
Writes the sorted list of drawer IDs to the configured drawer_id_file.
"""
import os
import sys
from fccs_config import (
parse_args, load_config, make_logger, load_lines, check_for_clashes,
)
_IGNORE_HEADER = [
"# Drawer IDs to SKIP during export (one ID per line).",
"# Lines starting with # are comments and are ignored.",
"#",
"# Add password-protected or otherwise-excluded drawers here.",
"#",
"# The IDs auto-added below are prefix clashes: searching any of them in",
"# FCCS can pop a selection box that breaks the automation, so ALL drawers",
"# in each clash group are skipped by default. DELETE or comment out any",
"# you actually DO want exported (then handle them manually), keep the rest.",
"",
]
def update_ignore_file(ignore_file, clashes, log):
"""Create ignore.txt if missing and seed it with clash-group IDs.
Every drawer involved in a clash (both the prefix and each longer ID that
matches it) is added, since any of them can trip the FCCS selection box.
Never overwrites existing entries — only appends IDs not already present,
and de-dupes so no ID is written twice. Returns the list of IDs added.
"""
existing = set(load_lines(ignore_file))
file_exists = os.path.exists(ignore_file)
seen = set(existing) # never re-add anything already listed
blocks = [] # (comment, [new_ids]) to append, in clash order
for short, matches in clashes:
group = [short] + list(matches)
new_ids = [g for g in group if g not in seen]
if not new_ids:
continue
seen.update(new_ids)
blocks.append((f"# clash group: {', '.join(group)}", new_ids))
# Existing file already covers every clash — leave it untouched.
if file_exists and not blocks:
return []
lines = []
if not file_exists:
lines.extend(_IGNORE_HEADER)
for comment, new_ids in blocks:
lines.append(comment)
lines.extend(new_ids)
mode = "a" if file_exists else "w"
with open(ignore_file, mode, encoding="utf-8") as f:
if file_exists:
f.write("\n") # separate the new block from prior content
f.write("\n".join(lines) + "\n")
added = [i for _, ids in blocks for i in ids]
if not file_exists:
log(f"Created ignore file: {ignore_file}")
if added:
log(f"Added {len(added)} clash-group ID(s) to ignore list: "
f"{', '.join(added)}")
return added
def main():
args = parse_args()
cfg = load_config(args.config)
log = make_logger(cfg.get("paths", "log_file"))
backup_dir = cfg.get("paths", "backup_dir")
output_file = cfg.get("paths", "drawer_id_file")
ignore_file = cfg.get("paths", "ignore_file")
if not os.path.isdir(backup_dir):
log(f"ERROR: backup directory not found: {backup_dir}")
sys.exit(1)
drawer_ids = sorted(
name for name in os.listdir(backup_dir)
if os.path.isdir(os.path.join(backup_dir, name))
)
with open(output_file, "w", encoding="utf-8") as f:
for did in drawer_ids:
f.write(did + "\n")
log(f"Found {len(drawer_ids)} drawers, written to {output_file}")
# Flag prefix clashes: FCCS search on the shorter ID pops a selection box.
clashes = check_for_clashes(drawer_ids)
if clashes:
log("-" * 60)
log(f"WARNING: {len(clashes)} potential drawer ID clash(es) found.")
log("Searching the shorter ID in FCCS may show a selection box "
"instead of navigating directly, which breaks the export.")
for short, matches in clashes:
log(f" {short} -> also matches: {', '.join(matches)}")
# Seed ignore.txt with the clash prefixes for review (never overwrites).
update_ignore_file(ignore_file, clashes, log)
log(f"Review the ignore list: {ignore_file}")
log("-" * 60)
else:
log("No drawer ID prefix clashes found.")
# Report which drawers will be skipped by the export.
ignored = set(load_lines(ignore_file))
if ignored:
present = sorted(ignored & set(drawer_ids))
log(f"Ignore list ({ignore_file}): {len(ignored)} IDs listed, "
f"{len(present)} present in this backup — these will be SKIPPED "
f"by fccs_export.py: {', '.join(present) if present else '(none present)'}")
if __name__ == "__main__":
main()