| use crate::models::DeviceProfile; |
| use crate::modules::{logger, process}; |
| use chrono::Local; |
| use rand::{distributions::Alphanumeric, Rng}; |
| use rusqlite::Connection; |
| use serde_json::Value; |
| use std::fs; |
| use std::path::{Path, PathBuf}; |
| use uuid::Uuid; |
|
|
| const DATA_DIR: &str = ".antigravity_tools"; |
| const GLOBAL_BASELINE: &str = "device_original.json"; |
|
|
| fn get_data_dir() -> Result<PathBuf, String> { |
| let home = dirs::home_dir().ok_or("failed_to_get_home_dir")?; |
| let data_dir = home.join(DATA_DIR); |
| if !data_dir.exists() { |
| fs::create_dir_all(&data_dir).map_err(|e| format!("failed_to_create_data_dir: {}", e))?; |
| } |
| Ok(data_dir) |
| } |
|
|
| |
| pub fn get_storage_path() -> Result<PathBuf, String> { |
| |
| if let Some(user_data_dir) = process::get_user_data_dir_from_process() { |
| let path = user_data_dir |
| .join("User") |
| .join("globalStorage") |
| .join("storage.json"); |
| if path.exists() { |
| return Ok(path); |
| } |
| } |
|
|
| |
| if let Some(exe_path) = process::get_antigravity_executable_path() { |
| if let Some(parent) = exe_path.parent() { |
| let portable = parent |
| .join("data") |
| .join("user-data") |
| .join("User") |
| .join("globalStorage") |
| .join("storage.json"); |
| if portable.exists() { |
| return Ok(portable); |
| } |
| } |
| } |
|
|
| |
| #[cfg(target_os = "macos")] |
| { |
| let home = dirs::home_dir().ok_or("failed_to_get_home_dir")?; |
| let path = |
| home.join("Library/Application Support/Antigravity/User/globalStorage/storage.json"); |
| if path.exists() { |
| return Ok(path); |
| } |
| } |
|
|
| #[cfg(target_os = "windows")] |
| { |
| let appdata = |
| std::env::var("APPDATA").map_err(|_| "failed_to_get_appdata_env".to_string())?; |
| let path = PathBuf::from(appdata).join("Antigravity\\User\\globalStorage\\storage.json"); |
| if path.exists() { |
| return Ok(path); |
| } |
| } |
|
|
| #[cfg(target_os = "linux")] |
| { |
| let home = dirs::home_dir().ok_or("failed_to_get_home_dir")?; |
| let path = home.join(".config/Antigravity/User/globalStorage/storage.json"); |
| if path.exists() { |
| return Ok(path); |
| } |
| } |
|
|
| Err("storage_json_not_found".to_string()) |
| } |
|
|
| |
| pub fn get_storage_dir() -> Result<PathBuf, String> { |
| let path = get_storage_path()?; |
| path.parent() |
| .map(|p| p.to_path_buf()) |
| .ok_or_else(|| "failed_to_get_storage_parent_dir".to_string()) |
| } |
|
|
| |
| pub fn get_state_db_path() -> Result<PathBuf, String> { |
| let dir = get_storage_dir()?; |
| Ok(dir.join("state.vscdb")) |
| } |
|
|
| |
| #[allow(dead_code)] |
| pub fn backup_storage(storage_path: &Path) -> Result<PathBuf, String> { |
| if !storage_path.exists() { |
| return Err(format!("storage_json_missing: {:?}", storage_path)); |
| } |
| let dir = storage_path |
| .parent() |
| .ok_or_else(|| "failed_to_get_storage_parent_dir".to_string())?; |
| let backup_path = dir.join(format!( |
| "storage.json.backup_{}", |
| Local::now().format("%Y%m%d_%H%M%S") |
| )); |
| fs::copy(storage_path, &backup_path).map_err(|e| format!("backup_failed: {}", e))?; |
| Ok(backup_path) |
| } |
|
|
| |
| #[allow(dead_code)] |
| pub fn read_profile(storage_path: &Path) -> Result<DeviceProfile, String> { |
| let content = |
| fs::read_to_string(storage_path).map_err(|e| format!("read_failed ({:?}): {}", storage_path, e))?; |
| let json: Value = |
| serde_json::from_str(&content).map_err(|e| format!("parse_failed ({:?}): {}", storage_path, e))?; |
|
|
| |
| let get_field = |key: &str| -> Option<String> { |
| if let Some(obj) = json.get("telemetry").and_then(|v| v.as_object()) { |
| if let Some(v) = obj.get(key).and_then(|v| v.as_str()) { |
| return Some(v.to_string()); |
| } |
| } |
| if let Some(v) = json |
| .get(format!("telemetry.{key}")) |
| .and_then(|v| v.as_str()) |
| { |
| return Some(v.to_string()); |
| } |
| None |
| }; |
|
|
| Ok(DeviceProfile { |
| machine_id: get_field("machineId").ok_or("missing_machine_id")?, |
| mac_machine_id: get_field("macMachineId").ok_or("missing_mac_machine_id")?, |
| dev_device_id: get_field("devDeviceId").ok_or("missing_dev_device_id")?, |
| sqm_id: get_field("sqmId").ok_or("missing_sqm_id")?, |
| }) |
| } |
|
|
| |
| pub fn write_profile(storage_path: &Path, profile: &DeviceProfile) -> Result<(), String> { |
| if !storage_path.exists() { |
| return Err(format!("storage_json_missing: {:?}", storage_path)); |
| } |
|
|
| let content = |
| fs::read_to_string(storage_path).map_err(|e| format!("read_failed: {}", e))?; |
| let mut json: Value = |
| serde_json::from_str(&content).map_err(|e| format!("parse_failed: {}", e))?; |
|
|
| |
| if !json.get("telemetry").map_or(false, |v| v.is_object()) { |
| if json.as_object_mut().is_some() { |
| json["telemetry"] = serde_json::json!({}); |
| } else { |
| return Err("json_top_level_not_object".to_string()); |
| } |
| } |
|
|
| if let Some(telemetry) = json.get_mut("telemetry").and_then(|v| v.as_object_mut()) { |
| telemetry.insert( |
| "machineId".to_string(), |
| Value::String(profile.machine_id.clone()), |
| ); |
| telemetry.insert( |
| "macMachineId".to_string(), |
| Value::String(profile.mac_machine_id.clone()), |
| ); |
| telemetry.insert( |
| "devDeviceId".to_string(), |
| Value::String(profile.dev_device_id.clone()), |
| ); |
| telemetry.insert("sqmId".to_string(), Value::String(profile.sqm_id.clone())); |
| } else { |
| return Err("telemetry_not_object".to_string()); |
| } |
|
|
| |
| if let Some(map) = json.as_object_mut() { |
| map.insert( |
| "telemetry.machineId".to_string(), |
| Value::String(profile.machine_id.clone()), |
| ); |
| map.insert( |
| "telemetry.macMachineId".to_string(), |
| Value::String(profile.mac_machine_id.clone()), |
| ); |
| map.insert( |
| "telemetry.devDeviceId".to_string(), |
| Value::String(profile.dev_device_id.clone()), |
| ); |
| map.insert( |
| "telemetry.sqmId".to_string(), |
| Value::String(profile.sqm_id.clone()), |
| ); |
| } |
|
|
| |
| if let Some(map) = json.as_object_mut() { |
| map.insert( |
| "storage.serviceMachineId".to_string(), |
| Value::String(profile.dev_device_id.clone()), |
| ); |
| } |
|
|
| let updated = serde_json::to_string_pretty(&json) |
| .map_err(|e| format!("serialize_failed: {}", e))?; |
| fs::write(storage_path, updated).map_err(|e| format!("write_failed ({:?}): {}", storage_path, e))?; |
| logger::log_info(&format!("device_profile_written to {:?}", storage_path)); |
|
|
| |
| let _ = sync_state_service_machine_id_value(&profile.dev_device_id); |
| Ok(()) |
| } |
|
|
| |
| #[allow(dead_code)] |
| pub fn sync_service_machine_id(storage_path: &Path, service_id: &str) -> Result<(), String> { |
| let content = |
| fs::read_to_string(storage_path).map_err(|e| format!("read_failed: {}", e))?; |
| let mut json: Value = |
| serde_json::from_str(&content).map_err(|e| format!("parse_failed: {}", e))?; |
|
|
| if let Some(map) = json.as_object_mut() { |
| map.insert( |
| "storage.serviceMachineId".to_string(), |
| Value::String(service_id.to_string()), |
| ); |
| } |
|
|
| let updated = serde_json::to_string_pretty(&json) |
| .map_err(|e| format!("serialize_failed: {}", e))?; |
| fs::write(storage_path, updated).map_err(|e| format!("write_failed: {}", e))?; |
| logger::log_info("service_machine_id_synced"); |
|
|
| let _ = sync_state_service_machine_id_value(service_id); |
| Ok(()) |
| } |
|
|
| |
| #[allow(dead_code)] |
| pub fn sync_service_machine_id_from_storage(storage_path: &Path) -> Result<(), String> { |
| if !storage_path.exists() { |
| return Err("storage_json_missing".to_string()); |
| } |
| let content = fs::read_to_string(storage_path).map_err(|e| format!("read_failed: {}", e))?; |
| let mut json: Value = serde_json::from_str(&content).map_err(|e| format!("parse_failed: {}", e))?; |
|
|
| let service_id = json |
| .get("storage.serviceMachineId") |
| .and_then(|v| v.as_str()) |
| .map(|s| s.to_string()) |
| .or_else(|| { |
| json.get("telemetry") |
| .and_then(|t| t.get("devDeviceId")) |
| .and_then(|v| v.as_str()) |
| .map(|s| s.to_string()) |
| }) |
| .or_else(|| { |
| json.get("telemetry.devDeviceId") |
| .and_then(|v| v.as_str()) |
| .map(|s| s.to_string()) |
| }) |
| .ok_or("missing_ids_in_storage")?; |
|
|
| let mut dirty = false; |
| if json |
| .get("storage.serviceMachineId") |
| .and_then(|v| v.as_str()) |
| .is_none() |
| { |
| if let Some(map) = json.as_object_mut() { |
| map.insert("storage.serviceMachineId".to_string(), Value::String(service_id.clone())); |
| dirty = true; |
| } |
| } |
|
|
| if dirty { |
| let updated = serde_json::to_string_pretty(&json).map_err(|e| format!("serialize_failed: {}", e))?; |
| fs::write(storage_path, updated).map_err(|e| format!("write_failed: {}", e))?; |
| logger::log_info("service_machine_id_added"); |
| } |
|
|
| sync_state_service_machine_id_value(&service_id) |
| } |
|
|
| fn sync_state_service_machine_id_value(service_id: &str) -> Result<(), String> { |
| let db_path = get_state_db_path()?; |
| if !db_path.exists() { |
| logger::log_warn(&format!( |
| "state_db_missing: {:?}", |
| db_path |
| )); |
| return Ok(()); |
| } |
|
|
| let conn = Connection::open(&db_path).map_err(|e| format!("db_open_failed: {}", e))?; |
| conn.execute( |
| "CREATE TABLE IF NOT EXISTS ItemTable (key TEXT PRIMARY KEY, value TEXT);", |
| [], |
| ) |
| .map_err(|e| format!("failed_to_create_item_table: {}", e))?; |
| conn.execute( |
| "INSERT OR REPLACE INTO ItemTable (key, value) VALUES ('storage.serviceMachineId', ?1);", |
| [service_id], |
| ) |
| .map_err(|e| format!("failed_to_write_to_db: {}", e))?; |
| logger::log_info("service_machine_id_synced_to_db"); |
| Ok(()) |
| } |
|
|
| |
| pub fn load_global_original() -> Option<DeviceProfile> { |
| if let Ok(dir) = get_data_dir() { |
| let path = dir.join(GLOBAL_BASELINE); |
| if path.exists() { |
| if let Ok(content) = fs::read_to_string(&path) { |
| if let Ok(profile) = serde_json::from_str::<DeviceProfile>(&content) { |
| return Some(profile); |
| } |
| } |
| } |
| } |
| None |
| } |
|
|
| pub fn save_global_original(profile: &DeviceProfile) -> Result<(), String> { |
| let dir = get_data_dir()?; |
| let path = dir.join(GLOBAL_BASELINE); |
| if path.exists() { |
| return Ok(()); |
| } |
| let content = |
| serde_json::to_string_pretty(profile).map_err(|e| format!("serialize_failed: {}", e))?; |
| fs::write(&path, content).map_err(|e| format!("write_failed: {}", e)) |
| } |
|
|
| |
| #[allow(dead_code)] |
| pub fn list_backups(storage_path: &Path) -> Result<Vec<PathBuf>, String> { |
| let dir = storage_path |
| .parent() |
| .ok_or_else(|| "failed_to_get_storage_parent_dir".to_string())?; |
| let mut backups = Vec::new(); |
| if let Ok(entries) = fs::read_dir(dir) { |
| for entry in entries.flatten() { |
| let path = entry.path(); |
| if let Some(name) = path.file_name().and_then(|n| n.to_str()) { |
| if name.starts_with("storage.json.backup_") { |
| backups.push(path); |
| } |
| } |
| } |
| } |
| |
| backups.sort_by(|a, b| { |
| let ma = fs::metadata(a).and_then(|m| m.modified()).ok(); |
| let mb = fs::metadata(b).and_then(|m| m.modified()).ok(); |
| mb.cmp(&ma) |
| }); |
| Ok(backups) |
| } |
|
|
| |
| #[allow(dead_code)] |
| pub fn restore_backup(storage_path: &Path, use_oldest: bool) -> Result<PathBuf, String> { |
| let backups = list_backups(storage_path)?; |
| if backups.is_empty() { |
| return Err("no_backups_found".to_string()); |
| } |
| let target = if use_oldest { |
| backups.last().unwrap().clone() |
| } else { |
| backups.first().unwrap().clone() |
| }; |
| |
| let _ = backup_storage(storage_path)?; |
| fs::copy(&target, storage_path).map_err(|e| format!("restore_failed: {}", e))?; |
| logger::log_info(&format!("storage_json_restored: {:?}", target)); |
| Ok(target) |
| } |
|
|
| |
| pub fn generate_profile() -> DeviceProfile { |
| DeviceProfile { |
| machine_id: format!("auth0|user_{}", random_hex(32)), |
| mac_machine_id: new_standard_machine_id(), |
| dev_device_id: Uuid::new_v4().to_string(), |
| sqm_id: format!("{{{}}}", Uuid::new_v4().to_string().to_uppercase()), |
| } |
| } |
|
|
| fn random_hex(length: usize) -> String { |
| rand::thread_rng() |
| .sample_iter(&Alphanumeric) |
| .take(length) |
| .map(char::from) |
| .collect::<String>() |
| .to_lowercase() |
| } |
|
|
| fn new_standard_machine_id() -> String { |
| |
| let mut rng = rand::thread_rng(); |
| let mut id = String::with_capacity(36); |
| for ch in "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".chars() { |
| if ch == '-' || ch == '4' { |
| id.push(ch); |
| } else if ch == 'x' { |
| id.push_str(&format!("{:x}", rng.gen_range(0..16))); |
| } else if ch == 'y' { |
| id.push_str(&format!("{:x}", rng.gen_range(8..12))); |
| } |
| } |
| id |
| } |
|
|