diff --git a/README.md b/README.md index 7d1b134..d9de38d 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,12 @@ Extracts and organizes documents from FileCabinet CS (Thomson Reuters) using GUI python fccs_scan.py ``` -Reads the restored FCCS backup directory and writes all drawer IDs (subfolder names) to `drawer_ids.txt`. +Reads the restored FCCS backup directory and writes all drawer IDs (subfolder names) to `drawer_ids.txt`. It also: + +- **Flags prefix clashes** — if one drawer ID is a prefix of another (e.g. `02218` and `02218A`), searching the shorter ID in FCCS pops up a selection box that breaks automated navigation. These are reported so you can export them manually or ignore the shorter ID. +- **Reports ignored drawers** — any IDs listed in `ignore.txt` that exist in this backup are shown as ones the export will skip. + +**Ignoring drawers:** To exclude specific drawers from export (e.g. password-protected folders that would stall the automation), add their IDs to the file at `ignore_file` (default `C:\Migration\ignore.txt`), one per line. `drawer_ids.txt` stays a full inventory; the export skips anything in the ignore list. Lines starting with `#` are treated as comments. ### Step 2: Export Documents @@ -88,6 +93,6 @@ Exported filenames follow the format `{drawer_id}_{client_name}_{folder_name}_{c All scripts read from `config.ini` (or specify `--config path\to\config.ini`). -- **`[paths]`** -- backup_dir, export_dir, output_dir, drawer_id_file, completed_file, log_file, screenshot_dir, folder_list +- **`[paths]`** -- backup_dir, export_dir, output_dir, drawer_id_file, completed_file, ignore_file, log_file, screenshot_dir, manifest_dir, folder_list - **`[timeouts]`** -- nav_timeout, dialog_timeout, progress_appear, progress_finish, settle, confirm_timeout - **`[controls]`** -- FCCS window class names and button titles (rarely need changing) diff --git a/__pycache__/fccs_config.cpython-313.pyc b/__pycache__/fccs_config.cpython-313.pyc index e50ec3b..413bac7 100644 Binary files a/__pycache__/fccs_config.cpython-313.pyc and b/__pycache__/fccs_config.cpython-313.pyc differ diff --git a/config.ini b/config.ini index 5118c90..39bd760 100644 --- a/config.ini +++ b/config.ini @@ -5,6 +5,7 @@ export_dir = C:\Migration\Export output_dir = C:\Migration\Output drawer_id_file = C:\Migration\drawer_ids.txt completed_file = C:\Migration\completed.txt +ignore_file = C:\Migration\ignore.txt log_file = C:\Migration\run_log.txt screenshot_dir = C:\Migration\screenshots manifest_dir = C:\Migration\manifests diff --git a/fccs_config.py b/fccs_config.py index 9f4fa4b..f26d3ca 100644 --- a/fccs_config.py +++ b/fccs_config.py @@ -137,3 +137,23 @@ def load_lines(path): return [] with open(path, "r", encoding="utf-8") as f: return [line.strip() for line in f if line.strip() and not line.strip().startswith("#")] + + +def check_for_clashes(drawer_ids): + """Find drawer IDs that are a prefix of another drawer ID. + + FCCS searches by prefix, so searching the shorter ID (e.g. '02218') + pops up a selection box when a longer ID exists (e.g. '02218A'), + which breaks the automated navigation. + + Returns a list of (short_id, [longer_ids...]) tuples, sorted by short_id. + Empty list means no clashes. + """ + ids = sorted(set(drawer_ids)) + clashes = [] + for short in ids: + matches = [other for other in ids + if other != short and other.startswith(short)] + if matches: + clashes.append((short, matches)) + return clashes diff --git a/fccs_export.py b/fccs_export.py index 15b06cc..93e3f22 100755 --- a/fccs_export.py +++ b/fccs_export.py @@ -354,6 +354,7 @@ def main(): paths = { "drawer_id_file": cfg.get("paths", "drawer_id_file"), "completed_file": cfg.get("paths", "completed_file"), + "ignore_file": cfg.get("paths", "ignore_file"), "screenshot_dir": cfg.get("paths", "screenshot_dir"), "manifest_dir": cfg.get("paths", "manifest_dir"), } @@ -391,10 +392,12 @@ def main(): sys.exit(1) completed = load_completed(paths["completed_file"]) + ignored = set(load_lines(paths["ignore_file"])) - pending = [d for d in all_ids if d not in completed] + pending = [d for d in all_ids if d not in completed and d not in ignored] log(f"Total drawers in list : {len(all_ids)}") log(f"Already completed : {len(completed)}") + log(f"Ignored (skipped) : {len(ignored & set(all_ids))}") log(f"Remaining to process : {len(pending)}") if not pending: diff --git a/fccs_scan.py b/fccs_scan.py index 3cf1d18..666cc6d 100644 --- a/fccs_scan.py +++ b/fccs_scan.py @@ -8,7 +8,9 @@ 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 +from fccs_config import ( + parse_args, load_config, make_logger, load_lines, check_for_clashes, +) def main(): @@ -18,6 +20,7 @@ def main(): 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}") @@ -34,6 +37,30 @@ def main(): log(f"Found {len(drawer_ids)} drawers, written to {output_file}") + # Report which drawers are set to be ignored (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, " + f"{len(present)} present in this backup — these will be SKIPPED " + f"by fccs_export.py: {', '.join(present) if present else '(none present)'}") + + # 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.") + log("Consider exporting these manually, or add the shorter ID to " + f"the ignore list ({ignore_file}).") + for short, matches in clashes: + flag = " [ignored]" if short in ignored else "" + log(f" {short}{flag} -> also matches: {', '.join(matches)}") + log("-" * 60) + else: + log("No drawer ID prefix clashes found.") + if __name__ == "__main__": main()