73 """Write JSON atomically via a temp file + rename."""
74 with tempfile.NamedTemporaryFile(
75 mode=
"w", encoding=
"utf-8", dir=path.parent,
76 suffix=
".tmp", delete=
False
78 json.dump(data, tmp, indent=2, ensure_ascii=
False)
79 tmp_path = Path(tmp.name)
80 tmp_path.replace(path)
104 dry_run: bool =
True,
105 verbose: bool =
True,
107 """Run the migration.
110 sessions_dir: Path to the sessions root directory.
111 dry_run: If True, print what would happen but make no changes.
112 verbose: If True, print progress to stdout.
115 Number of sessions migrated (or that would be migrated).
118 root = Path(sessions_dir)
120 if not root.is_dir():
121 print(f
"Sessions directory not found: {root}", file=sys.stderr)
128 all_names: set[str] = set()
129 for child
in root.iterdir():
130 if child.is_dir()
and child.name
not in _SKIP_DIRS:
131 all_names.add(child.name)
136 print(
"No timestamp-format sessions found. Nothing to migrate.")
139 if verbose
and dry_run:
140 print(
"=== DRY RUN — no files will be changed ===\n")
146 col_w = max(len(t.name)
for t
in targets)
148 for session_dir
in targets:
149 old_name = session_dir.name
152 if meta_path
is None:
153 print(f
" SKIP {old_name:<{col_w}} (no meta file found)")
158 new_name = random_name(exists_fn=
lambda n: n
in all_names)
159 all_names.add(new_name)
161 _dbg(
"OC_CHAT.MAIN", func, f
"{old_name!r} → {new_name!r}", 2)
164 print(f
" {'(dry) ' if dry_run else ''}RENAME {old_name:<{col_w}} → {new_name}")
172 for f
in list(session_dir.iterdir()):
175 if f.name.startswith(old_name +
"_"):
176 new_fname = new_name + f.name[len(old_name):]
177 f.rename(session_dir / new_fname)
178 _dbg(
"OC_CHAT.MAIN", func, f
" file: {f.name} → {new_fname}", 3)
181 new_meta_path = session_dir / (new_name + meta_path.name[len(old_name):])
182 if new_meta_path.exists():
184 meta_data[
"session_name"] = new_name
188 new_dir = root / new_name
189 session_dir.rename(new_dir)
193 except OSError
as exc:
194 print(f
" ERROR renaming {old_name}: {exc}", file=sys.stderr)
195 _dbg(
"OC_CHAT.MAIN", func, f
"error renaming {old_name!r}: {exc}", 1, override=
True)
200 mode =
"Would migrate" if dry_run
else "Migrated"
201 print(f
"{mode}: {migrated} | Skipped: {skipped} | Errors: {errors}")
202 if dry_run
and migrated > 0:
203 print(
"\nRe-run without --dry-run to apply changes.")
209 """Entry point for ``python -m oc_chat.scripts.migrate_session_names``."""
210 for _stream
in (sys.stdout, sys.stderr):
212 _stream.reconfigure(errors=
"replace")
213 except (AttributeError, ValueError):
217 parser = argparse.ArgumentParser(
218 description=
"Rename timestamp-format sessions to random-word names.",
219 formatter_class=argparse.RawDescriptionHelpFormatter,
222 " python -m oc_chat.scripts.migrate_session_names # dry run (safe)\n"
223 " python -m oc_chat.scripts.migrate_session_names --apply # apply changes\n"
230 help=
"Apply changes (default is dry-run/preview only)",
234 default=DEFAULT_SESSIONS_DIR,
235 help=f
"Sessions directory (default: {DEFAULT_SESSIONS_DIR})",
241 help=
"Suppress output",
244 args = parser.parse_args()
245 dry_run =
not args.apply
248 sessions_dir=args.sessions_dir,
250 verbose=
not args.quiet,