oxicode/cli/commands/
migrate.rs1use std::path::PathBuf;
27
28use crate::cli::{MigrateBrainArgs, MigrationCommands};
29
30pub async fn handle_migrate(cmd: MigrationCommands) -> i32 {
32 match cmd {
33 MigrationCommands::Brain(args) => handle_migrate_brain(args).await,
34 }
35}
36
37async fn handle_migrate_brain(args: MigrateBrainArgs) -> i32 {
38 let socket = args
39 .socket
40 .unwrap_or_else(crate::foundation::brain::default_socket_path);
41
42 println!("Foundation v1 brain migration");
43 println!("-----------------------------");
44 println!("socket: {}", socket.display());
45 println!("dry-run: {}", args.dry_run);
46 println!("archive-legacy: {}", args.archive_legacy);
47 println!("checkpoint: {}", args.checkpoint.display());
48 println!("batch size: {}", args.batch_size);
49
50 if args.dry_run {
51 println!("\nDRY RUN: no memory will be written, no checkpoint advanced.");
52 println!("The legacy store will be enumerated but not read for content.");
53 }
54
55 let backend = crate::foundation::brain::BrainMemoryBackend::new(socket.clone());
56 let probe = backend.ping().await;
59 println!(
60 "\nbackend health: {}",
61 match &probe {
62 Ok(()) => "ok: oxibrain daemon connected".to_string(),
63 Err(e) => format!("degraded ({e})"),
64 }
65 );
66
67 if args.dry_run {
68 println!("\nDry run complete. Re-run without --dry-run to perform the migration.");
69 return 0;
70 }
71
72 let checkpoint = crate::foundation::migrate::Checkpoint::load(&args.checkpoint);
73 if let Some(last) = checkpoint.last_id() {
74 println!("resuming after id: {last}");
75 } else {
76 println!("starting fresh (no checkpoint found)");
77 }
78
79 let legacy = crate::foundation::migrate::LegacyMemoryReader::for_default_home();
80 let mut migrated = 0usize;
81 let mut failed = 0usize;
82 let mut tx = crate::foundation::migrate::Migration::new(&backend, &args.checkpoint);
83
84 for batch in legacy.batches(args.batch_size) {
85 for item in batch {
86 match tx.migrate_one(item) {
87 Ok(crate::foundation::migrate::MigrationOutcome::Inserted(id)) => {
88 println!(" + {id}");
89 migrated += 1;
90 }
91 Ok(crate::foundation::migrate::MigrationOutcome::Skipped(id)) => {
92 println!(" ~ {id} (already in brain)");
93 }
94 Err(e) => {
95 println!(" ! {e}");
96 failed += 1;
97 }
98 }
99 }
100 }
101
102 println!("\nMigration summary");
103 println!(" inserted: {migrated}");
104 println!(" failed: {failed}");
105
106 if args.archive_legacy {
107 match crate::foundation::migrate::archive_legacy_default() {
108 Ok(archive_path) => {
109 println!("\nLegacy store archived to: {}", archive_path.display());
110 }
111 Err(e) => {
112 println!("\nArchive failed: {e}");
113 return 2;
114 }
115 }
116 }
117
118 if failed > 0 { 1 } else { 0 }
119}
120
121impl Default for MigrateBrainArgs {
122 fn default() -> Self {
123 Self {
124 socket: None,
125 dry_run: false,
126 archive_legacy: false,
127 checkpoint: PathBuf::from("~/.oxicode/migration/brain.json"),
128 batch_size: 64,
129 }
130 }
131}