use std::fs::OpenOptions;
use std::io::Write;
use std::path::PathBuf;
use chrono::Utc;
use serde::{Deserialize, Serialize};
use tauri::State;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct VaultProfile {
pub id: String,
pub name: String,
pub vault_dir: String,
pub servers_file: Option<String>,
pub description: Option<String>,
pub is_active: bool,
}
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct ProfilesConfig {
pub active_profile_id: Option<String>,
pub profiles: Vec<VaultProfile>,
}
pub fn get_profiles_dir() -> PathBuf {
if let Some(home) = dirs::home_dir() {
home.join(".config").join("sb-vault")
} else {
PathBuf::from(".config/sb-vault")
}
}
pub fn get_profiles_file_path() -> PathBuf {
get_profiles_dir().join("profiles.toml")
}
pub fn get_default_forge_vault_dir() -> String {
if let Some(home) = dirs::home_dir() {
home.join(".sb-forge").join("vault").to_string_lossy().to_string()
} else {
".sb-forge/vault".to_string()
}
}
pub fn load_profiles_config() -> ProfilesConfig {
let path = get_profiles_file_path();
if !path.exists() {
let default_profile = VaultProfile {
id: "default".to_string(),
name: "S&B Forge Standard".to_string(),
vault_dir: get_default_forge_vault_dir(),
servers_file: None,
description: Some("Standard Workspace für lokale S&B Forge Tresore".to_string()),
is_active: true,
};
let config = ProfilesConfig {
active_profile_id: Some("default".to_string()),
profiles: vec![default_profile],
};
let _ = save_profiles_config(&config);
return config;
}
match std::fs::read_to_string(&path) {
Ok(content) => toml::from_str(&content).unwrap_or_default(),
Err(_) => ProfilesConfig::default(),
}
}
pub fn save_profiles_config(config: &ProfilesConfig) -> Result<(), String> {
let dir = get_profiles_dir();
std::fs::create_dir_all(&dir).map_err(|e| e.to_string())?;
let path = get_profiles_file_path();
let toml_str = toml::to_string_pretty(config).map_err(|e| e.to_string())?;
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&path)
.map_err(|e| e.to_string())?;
file.write_all(toml_str.as_bytes()).map_err(|e| e.to_string())?;
Ok(())
}
pub fn get_active_vault_dir() -> PathBuf {
let config = load_profiles_config();
if let Some(active_id) = config.active_profile_id {
if let Some(profile) = config.profiles.iter().find(|p| p.id == active_id) {
return PathBuf::from(&profile.vault_dir);
}
}
PathBuf::from(get_default_forge_vault_dir())
}
#[tauri::command]
pub fn get_profiles() -> Result<Vec<VaultProfile>, String> {
let config = load_profiles_config();
Ok(config.profiles)
}
#[tauri::command]
pub fn save_profile(mut profile: VaultProfile) -> Result<Vec<VaultProfile>, String> {
let mut config = load_profiles_config();
if profile.id.is_empty() {
profile.id = format!("prof-{}", Utc::now().timestamp_millis());
profile.is_active = false;
config.profiles.push(profile);
} else if let Some(pos) = config.profiles.iter().position(|p| p.id == profile.id) {
profile.is_active = config.profiles[pos].is_active;
config.profiles[pos] = profile;
} else {
config.profiles.push(profile);
}
save_profiles_config(&config)?;
Ok(config.profiles)
}
#[tauri::command]
pub fn delete_profile(id: String) -> Result<Vec<VaultProfile>, String> {
if id == "default" {
return Err("Das Standard-Profil kann nicht gelöscht werden!".to_string());
}
let mut config = load_profiles_config();
if config.active_profile_id.as_deref() == Some(&id) {
return Err("Das aktuell aktive Profil kann nicht gelöscht werden. Bitte zuerst wechseln!".to_string());
}
config.profiles.retain(|p| p.id != id);
save_profiles_config(&config)?;
Ok(config.profiles)
}
#[tauri::command]
pub async fn switch_profile(
id: String,
vault_state: State<'_, crate::vault::VaultState>,
tunnel_state: State<'_, crate::tunnels::TunnelState>,
log_state: State<'_, crate::logs::LogStreamState>,
) -> Result<VaultProfile, String> {
let mut config = load_profiles_config();
let target_profile = config
.profiles
.iter()
.find(|p| p.id == id)
.cloned()
.ok_or_else(|| format!("Profil mit ID '{}' nicht gefunden", id))?;
*vault_state.active_session.lock().unwrap() = None;
crate::tunnels::stop_all_active_tunnels(&tunnel_state).await;
crate::logs::stop_all_active_log_streams(&log_state).await;
let new_vault_dir = PathBuf::from(&target_profile.vault_dir);
if !new_vault_dir.exists() {
let _ = std::fs::create_dir_all(&new_vault_dir);
}
*vault_state.active_vault_dir.lock().unwrap() = new_vault_dir;
config.active_profile_id = Some(id.clone());
for p in config.profiles.iter_mut() {
p.is_active = p.id == id;
}
save_profiles_config(&config)?;
let mut switched = target_profile;
switched.is_active = true;
Ok(switched)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_profile_generation() {
let profile = VaultProfile {
id: "default".to_string(),
name: "S&B Forge Standard".to_string(),
vault_dir: "/home/user/.sb-forge/vault".to_string(),
servers_file: None,
description: Some("Test".to_string()),
is_active: true,
};
let json = serde_json::to_string(&profile).unwrap();
let parsed: VaultProfile = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.id, "default");
assert_eq!(parsed.is_active, true);
}
}