app / src-tauri /src /modules /device.rs
AZILS's picture
Upload 323 files
a21c316 verified
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)
}
/// Find storage.json path (prefer custom/portable paths)
pub fn get_storage_path() -> Result<PathBuf, String> {
// 1) --user-data-dir flag
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);
}
}
// 2) Portable mode (based on executable data/user-data)
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);
}
}
}
// 3) Standard installation location
#[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())
}
/// Get directory of storage.json
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())
}
/// Get state.vscdb path (same directory as storage.json)
pub fn get_state_db_path() -> Result<PathBuf, String> {
let dir = get_storage_dir()?;
Ok(dir.join("state.vscdb"))
}
/// Backup storage.json, returns backup file path
#[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)
}
/// Read current device profile from storage.json
#[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))?;
// Supports nested telemetry or flat telemetry.xxx
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")?,
})
}
/// Write device profile to storage.json
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))?;
// Ensure telemetry is an object
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());
}
// Write flat keys as well, compatible with old formats
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()),
);
}
// Sync storage.serviceMachineId (match with devDeviceId), place at root level
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));
// Sync ItemTable.storage.serviceMachineId in state.vscdb
let _ = sync_state_service_machine_id_value(&profile.dev_device_id);
Ok(())
}
/// Only sync serviceMachineId, don't change other fields
#[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(())
}
/// Read serviceMachineId from storage.json (fallback to devDeviceId), sync back if missing and sync state.vscdb
#[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(())
}
/// Load/Save global original profile (shared across all accounts)
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(()); // already exists, don't overwrite
}
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))
}
/// List storage.json backups in current directory (descending by time)
#[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);
}
}
}
}
// Sort by modification time (new to old)
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)
}
/// Restore backup to storage.json. If use_oldest=true, use oldest backup, else use latest.
#[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()
};
// backup current first
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)
}
/// Generate a new set of device fingerprints (Cursor/VSCode style)
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 {
// xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx (y in 8..b)
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
}