Skip to content

Commit 31a4bc5

Browse files
authored
Migration logic for the auto-complete repo (#913)
1 parent 4a97cb4 commit 31a4bc5

File tree

10 files changed

+228
-48
lines changed

10 files changed

+228
-48
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ anyhow = "1.0.98"
3333
appkit-nsworkspace-bindings = { path = "crates/macos-utils/appkit-nsworkspace-bindings" }
3434
async-trait = "0.1.87"
3535
aws-smithy-runtime-api = "1.6.1"
36+
rustix = { version = "1.1.2", features = ["fs"] }
3637
aws-smithy-types = "1.2.10"
3738
aws-types = "1.3.0"
3839
base64 = "0.22.1"

crates/fig_desktop/src/install.rs

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,25 +24,6 @@ const PREVIOUS_VERSION_KEY: &str = "desktop.versionAtPreviousLaunch";
2424
#[cfg(target_os = "macos")]
2525
const MIGRATED_KEY: &str = "desktop.migratedFromFig";
2626

27-
#[cfg(target_os = "macos")]
28-
pub async fn migrate_data_dir() {
29-
// Migrate the user data dir
30-
if let (Ok(old), Ok(new)) = (fig_util::directories::old_fig_data_dir(), fig_data_dir()) {
31-
if !old.is_symlink() && old.is_dir() && !new.is_dir() {
32-
match tokio::fs::rename(&old, &new).await {
33-
Ok(()) => {
34-
if let Err(err) = symlink(&new, &old).await {
35-
error!(%err, "Failed to symlink old user data dir");
36-
}
37-
},
38-
Err(err) => {
39-
error!(%err, "Failed to migrate user data dir");
40-
},
41-
}
42-
}
43-
}
44-
}
45-
4627
#[cfg(target_os = "macos")]
4728
fn run_input_method_migration() {
4829
use fig_integrations::input_method::InputMethod;

crates/fig_desktop/src/main.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,9 @@ async fn main() -> ExitCode {
101101

102102
fig_telemetry::init_global_telemetry_emitter();
103103

104-
#[cfg(target_os = "macos")]
105-
install::migrate_data_dir().await;
104+
if let Err(err) = fig_install::migrate::migrate_if_needed().await {
105+
error!(%err, "Failed to migrate data directory");
106+
}
106107

107108
if let Err(err) = fig_settings::settings::init_global() {
108109
error!(%err, "failed to init global settings");

crates/fig_install/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ bytes.workspace = true
1919
camino.workspace = true
2020
cfg-if.workspace = true
2121
cookie = "0.18.0"
22+
eyre.workspace = true
2223
fig_integrations.workspace = true
2324
fig_os_shim.workspace = true
2425
fig_request.workspace = true
@@ -29,6 +30,7 @@ hex.workspace = true
2930
regex.workspace = true
3031
reqwest.workspace = true
3132
ring.workspace = true
33+
rustix.workspace = true
3234
semver = { version = "1.0.26", features = ["serde"] }
3335
serde.workspace = true
3436
serde_json.workspace = true

crates/fig_install/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ pub mod index;
66
mod linux;
77
#[cfg(target_os = "macos")]
88
pub mod macos;
9+
pub mod migrate;
910
#[cfg(windows)]
1011
mod windows;
1112

crates/fig_install/src/migrate.rs

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
use std::path::PathBuf;
2+
3+
use eyre::Result;
4+
use rustix::fs::{
5+
FlockOperation,
6+
flock,
7+
};
8+
use tokio::fs;
9+
use tracing::debug;
10+
11+
const KIRO_MIGRATION_KEY: &str = "migration.kiro.completed";
12+
const MIGRATION_TIMEOUT_SECS: u64 = 10;
13+
14+
pub async fn migrate_if_needed() -> Result<bool> {
15+
let status = detect_migration().await?;
16+
17+
match status {
18+
MigrationStatus::Completed => {
19+
debug!("Migration already completed");
20+
return Ok(false);
21+
},
22+
MigrationStatus::NotNeeded => {
23+
debug!("No migration needed");
24+
mark_migration_completed()?;
25+
return Ok(false);
26+
},
27+
MigrationStatus::Needed => {
28+
debug!("Migrating database and settings");
29+
},
30+
}
31+
32+
let _lock = match acquire_migration_lock()? {
33+
Some(lock) => lock,
34+
None => {
35+
debug!("Migration already in progress");
36+
return Ok(false);
37+
},
38+
};
39+
40+
let old_dir = fig_util::directories::old_fig_data_dir()?;
41+
let new_dir = fig_util::directories::fig_data_dir()?;
42+
43+
debug!("Old directory: {}", old_dir.display());
44+
debug!("New directory: {}", new_dir.display());
45+
46+
// Copy essential files from old directory to new directory
47+
if !new_dir.exists() {
48+
fs::create_dir_all(&new_dir).await?;
49+
}
50+
debug!("Copying essential files from old to new directory");
51+
copy_essential_files(&old_dir, &new_dir).await?;
52+
53+
// Mark migration as completed in database
54+
debug!("Marking migration as completed");
55+
mark_migration_completed()?;
56+
57+
debug!("Migration completed successfully");
58+
Ok(true)
59+
}
60+
61+
#[derive(Debug)]
62+
enum MigrationStatus {
63+
NotNeeded,
64+
Needed,
65+
Completed,
66+
}
67+
68+
async fn detect_migration() -> Result<MigrationStatus> {
69+
let old_dir = fig_util::directories::old_fig_data_dir()?;
70+
let new_dir = fig_util::directories::fig_data_dir()?;
71+
72+
// If new directory doesn't exist yet, check if old directory exists
73+
if !new_dir.exists() {
74+
if old_dir.exists() && old_dir.is_dir() {
75+
return Ok(MigrationStatus::Needed);
76+
} else {
77+
return Ok(MigrationStatus::NotNeeded);
78+
}
79+
}
80+
81+
// New directory exists, check database flag (safe now since new_dir exists)
82+
let migration_completed = is_migration_completed()?;
83+
84+
if migration_completed {
85+
Ok(MigrationStatus::Completed)
86+
} else if old_dir.exists() && old_dir.is_dir() {
87+
Ok(MigrationStatus::Needed)
88+
} else {
89+
Ok(MigrationStatus::NotNeeded)
90+
}
91+
}
92+
93+
async fn copy_essential_files(src: &std::path::Path, dst: &std::path::Path) -> Result<()> {
94+
// Copy data-local-dir files (database and history)
95+
let data_files = ["data.sqlite3", "history"];
96+
for file_name in data_files {
97+
let src_path = src.join(file_name);
98+
let dst_path = dst.join(file_name);
99+
100+
if src_path.exists() {
101+
debug!("Copying {} to {}", src_path.display(), dst_path.display());
102+
fs::copy(&src_path, &dst_path).await?;
103+
}
104+
}
105+
106+
// Copy settings file to its specific location
107+
let old_settings = src.join("settings.json");
108+
if old_settings.exists() {
109+
let new_settings = fig_util::directories::settings_path()?;
110+
if !new_settings.exists() {
111+
if let Some(parent) = new_settings.parent() {
112+
fs::create_dir_all(parent).await?;
113+
}
114+
debug!("Copying settings to {}", new_settings.display());
115+
fs::copy(&old_settings, &new_settings).await?;
116+
}
117+
}
118+
119+
Ok(())
120+
}
121+
122+
fn mark_migration_completed() -> Result<()> {
123+
let db = fig_settings::sqlite::database()?;
124+
db.set_state_value(KIRO_MIGRATION_KEY, true)?;
125+
Ok(())
126+
}
127+
128+
struct MigrationLock {
129+
_file: std::fs::File,
130+
path: PathBuf,
131+
}
132+
133+
impl Drop for MigrationLock {
134+
fn drop(&mut self) {
135+
let _ = std::fs::remove_file(&self.path);
136+
}
137+
}
138+
139+
fn acquire_migration_lock() -> Result<Option<MigrationLock>> {
140+
let lock_path = migration_lock_path()?;
141+
142+
if let Some(parent) = lock_path.parent() {
143+
std::fs::create_dir_all(parent)?;
144+
}
145+
146+
// Check if lock file exists and is stale
147+
if lock_path.exists() {
148+
match std::fs::metadata(&lock_path)
149+
.and_then(|m| m.modified())
150+
.and_then(|t| t.elapsed().map_err(std::io::Error::other))
151+
{
152+
Ok(elapsed) if elapsed.as_secs() > MIGRATION_TIMEOUT_SECS => {
153+
let _ = std::fs::remove_file(&lock_path);
154+
},
155+
_ => {
156+
// Continue with lock attempt even if metadata operations fail
157+
debug!(
158+
"Failed to get lock file metadata, continuing with lock attempt: {}",
159+
lock_path.display()
160+
);
161+
},
162+
}
163+
}
164+
165+
// Try to acquire the lock
166+
let file = std::fs::OpenOptions::new()
167+
.create(true)
168+
.write(true)
169+
.truncate(false)
170+
.open(&lock_path)?;
171+
172+
match flock(&file, FlockOperation::NonBlockingLockExclusive) {
173+
Ok(()) => Ok(Some(MigrationLock {
174+
_file: file,
175+
path: lock_path,
176+
})),
177+
Err(_) => Ok(None),
178+
}
179+
}
180+
181+
fn migration_lock_path() -> Result<PathBuf> {
182+
Ok(fig_util::directories::fig_data_dir()?.join("migration.lock"))
183+
}
184+
185+
fn is_migration_completed() -> Result<bool> {
186+
Ok(fig_settings::state::get_bool_or(KIRO_MIGRATION_KEY, false))
187+
}

crates/fig_util/src/consts.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ pub const PRODUCT_NAME: &str = "Kiro CLI";
2525

2626
pub const RUNTIME_DIR_NAME: &str = "cwrun";
2727

28+
/// Data directory name used in paths like ~/.local/share/{OLD_DATA_DIR_NAME}
29+
#[cfg(unix)]
30+
pub const OLD_DATA_DIR_NAME: &str = "amazon-q";
31+
#[cfg(windows)]
32+
pub const OLD_DATA_DIR_NAME: &str = "AmazonQ";
33+
2834
/// Data directory name used in paths like ~/.local/share/{DATA_DIR_NAME}
2935
#[cfg(unix)]
3036
pub const DATA_DIR_NAME: &str = "kiro-cli";

crates/fig_util/src/directories.rs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use crate::system_info::{
2828
use crate::{
2929
BACKUP_DIR_NAME,
3030
DATA_DIR_NAME,
31+
OLD_DATA_DIR_NAME,
3132
TAURI_PRODUCT_NAME,
3233
};
3334

@@ -133,7 +134,9 @@ pub fn config_dir() -> Result<PathBuf> {
133134
/// This should be removed at some point in the future, once all our users have migrated
134135
/// - MacOS: `$HOME/Library/Application Support/codewhisperer`
135136
pub fn old_fig_data_dir() -> Result<PathBuf> {
136-
Ok(dirs::data_local_dir().ok_or(DirectoryError::NoHomeDirectory)?.join("q"))
137+
Ok(dirs::data_local_dir()
138+
.ok_or(DirectoryError::NoHomeDirectory)?
139+
.join(OLD_DATA_DIR_NAME))
137140
}
138141

139142
/// The q data directory
@@ -446,11 +449,11 @@ pub fn bundle_metadata_path<Ctx: EnvProvider + PlatformProvider>(ctx: &Ctx) -> R
446449

447450
/// The path to the fig settings file
448451
///
449-
/// - Linux: `$HOME/.aws/kiro-cli/settings.json`
450-
/// - MacOS: `$HOME/.aws/kiro-cli/settings.json`
451-
/// - Windows: `$HOME/.aws/kiro-cli/settings.json`
452+
/// - MacOS: `$HOME/.kiro/settings/cli.json`
453+
/// - Linux: `$HOME/.kiro/settings/cli.json`
454+
/// - Windows: `$HOME/.kiro/settings/cli.json`
452455
pub fn settings_path() -> Result<PathBuf> {
453-
Ok(home_dir()?.join(".aws").join("kiro-cli").join("settings.json"))
456+
Ok(home_dir()?.join(".kiro").join("settings").join("cli.json"))
454457
}
455458

456459
/// The path to the lock file used to indicate that the app is updating
@@ -739,22 +742,22 @@ mod tests {
739742
fn snapshot_figterm_socket_path() {
740743
linux!(figterm_socket_path("$SESSION_ID"), @"$XDG_RUNTIME_DIR/cwrun/t/$SESSION_ID.sock");
741744
macos!(figterm_socket_path("$SESSION_ID"), @"$TMPDIR/cwrun/t/$SESSION_ID.sock");
742-
windows!(figterm_socket_path("$SESSION_ID"), @r"C:\Users\$USER\AppData\Local\Temp\AmazonQ\sockets\t\$SESSION_ID.sock");
745+
windows!(figterm_socket_path("$SESSION_ID"), @r"C:\Users\$USER\AppData\Local\Temp\KiroCli\sockets\t\$SESSION_ID.sock");
743746
}
744747

745748
#[test]
746749
fn snapshot_settings_path() {
747-
linux!(settings_path(), @"$HOME/.local/share/amazon-q/settings.json");
748-
macos!(settings_path(), @"$HOME/.aws/kiro-cli/settings.json");
749-
windows!(settings_path(), @r"C:\Users\$USER\AppData\Local\AmazonQ\settings.json");
750+
linux!(settings_path(), @"$HOME/.local/share/.kiro/settings/cli.json");
751+
macos!(settings_path(), @"$HOME/.kiro/settings/cli.json");
752+
windows!(settings_path(), @r"C:\Users\$USER\AppData\Local\.kiro\settings\cli.json");
750753
}
751754

752755
#[test]
753756
fn snapshot_update_lock_path() {
754757
let ctx = Context::new();
755-
linux!(update_lock_path(&ctx), @"$HOME/.local/share/amazon-q/update.lock");
758+
linux!(update_lock_path(&ctx), @"$HOME/.local/share/kiro-cli/update.lock");
756759
macos!(update_lock_path(&ctx), @"$HOME/Library/Application Support/kiro-cli/update.lock");
757-
windows!(update_lock_path(&ctx), @r"C:\Users\$USER\AppData\Local\AmazonQ\update.lock");
760+
windows!(update_lock_path(&ctx), @r"C:\Users\$USER\AppData\Local\KiroCli\update.lock");
758761
}
759762

760763
#[test]

0 commit comments

Comments
 (0)