180 lines
5.5 KiB
Rust
180 lines
5.5 KiB
Rust
use directories::ProjectDirs;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
use std::fs;
|
|
use std::io;
|
|
use std::path::PathBuf;
|
|
use std::sync::Arc;
|
|
use std::sync::RwLock;
|
|
|
|
use super::api::{Context, ContextConfig};
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
pub struct Config {
|
|
pub context: String,
|
|
pub contexts: HashMap<String, ContextConfig>,
|
|
}
|
|
|
|
impl Default for Config {
|
|
fn default() -> Self {
|
|
let mut contexts = HashMap::new();
|
|
contexts.insert("local".to_string(), ContextConfig::Local);
|
|
Self {
|
|
context: "local".to_string(),
|
|
contexts,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Helper managing contexts
|
|
pub struct ContextManager {
|
|
context: RwLock<Arc<Context>>,
|
|
config_path: PathBuf,
|
|
config: RwLock<Config>,
|
|
}
|
|
|
|
pub static MANAGER: std::sync::LazyLock<ContextManager> =
|
|
std::sync::LazyLock::new(|| ContextManager::new().expect("Cannot setup context manager"));
|
|
|
|
impl ContextManager {
|
|
fn new() -> io::Result<Self> {
|
|
let proj_dirs = ProjectDirs::from("com", "pkh", "pkh").ok_or_else(|| {
|
|
io::Error::new(
|
|
io::ErrorKind::NotFound,
|
|
"Could not determine config directory",
|
|
)
|
|
})?;
|
|
let config_dir = proj_dirs.config_dir();
|
|
fs::create_dir_all(config_dir)?;
|
|
let config_path = config_dir.join("contexts.json");
|
|
|
|
let config = if config_path.exists() {
|
|
// Load existing configuration file
|
|
let content = fs::read_to_string(&config_path)?;
|
|
serde_json::from_str(&content)
|
|
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?
|
|
} else {
|
|
// Create a new configuration file
|
|
Config::default()
|
|
};
|
|
|
|
Ok(Self {
|
|
context: RwLock::new(Arc::new(Self::make_context(
|
|
config.context.as_str(),
|
|
&config,
|
|
))),
|
|
config_path,
|
|
config: RwLock::new(config),
|
|
})
|
|
}
|
|
|
|
/// Obtain current ContextManager configuration
|
|
pub fn get_config(&self) -> std::sync::RwLockReadGuard<'_, Config> {
|
|
self.config.read().unwrap()
|
|
}
|
|
|
|
/// Make a ContextManager using a specific configuration path
|
|
pub fn with_path(path: PathBuf) -> Self {
|
|
let config = Config::default();
|
|
Self {
|
|
context: RwLock::new(Arc::new(Self::make_context("local", &config))),
|
|
config_path: path,
|
|
config: RwLock::new(config),
|
|
}
|
|
}
|
|
|
|
/// Save current context configuration to disk
|
|
pub fn save(&self) -> io::Result<()> {
|
|
let config = self.config.read().unwrap();
|
|
let content = serde_json::to_string_pretty(&*config)
|
|
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
|
fs::write(&self.config_path, content)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn make_context(name: &str, config: &Config) -> Context {
|
|
let context_config = config
|
|
.contexts
|
|
.get(name)
|
|
.cloned()
|
|
.expect("Context not found in config");
|
|
Context::new(context_config)
|
|
}
|
|
|
|
/// List contexts from configuration
|
|
pub fn list_contexts(&self) -> Vec<String> {
|
|
self.config
|
|
.read()
|
|
.unwrap()
|
|
.contexts
|
|
.keys()
|
|
.cloned()
|
|
.collect()
|
|
}
|
|
|
|
/// Add a context to configuration
|
|
pub fn add_context(&self, name: &str, config: ContextConfig) -> io::Result<()> {
|
|
self.config
|
|
.write()
|
|
.unwrap()
|
|
.contexts
|
|
.insert(name.to_string(), config);
|
|
self.save()
|
|
}
|
|
|
|
/// Remove context from configuration
|
|
pub fn remove_context(&self, name: &str) -> io::Result<()> {
|
|
let mut config = self.config.write().unwrap();
|
|
if name == "local" {
|
|
return Err(io::Error::new(
|
|
io::ErrorKind::InvalidInput,
|
|
"Cannot remove local context",
|
|
));
|
|
}
|
|
if config.contexts.remove(name).is_some() {
|
|
// If we are removing the current context, fallback to local
|
|
if name == config.context {
|
|
config.context = "local".to_string();
|
|
self.set_current_ephemeral(Self::make_context("local", &config));
|
|
}
|
|
|
|
drop(config); // Drop write lock before saving
|
|
self.save()?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Set current context from name (modifying configuration)
|
|
pub fn set_current(&self, name: &str) -> io::Result<()> {
|
|
let mut config = self.config.write().unwrap();
|
|
if config.contexts.contains_key(name) {
|
|
config.context = name.to_string();
|
|
self.set_current_ephemeral(Self::make_context(name, &config));
|
|
drop(config); // Drop write lock before saving
|
|
self.save()?;
|
|
Ok(())
|
|
} else {
|
|
Err(io::Error::new(
|
|
io::ErrorKind::NotFound,
|
|
format!("Context '{}' not found", name),
|
|
))
|
|
}
|
|
}
|
|
|
|
/// Set current context, without modifying configuration
|
|
pub fn set_current_ephemeral(&self, context: Context) {
|
|
*self.context.write().unwrap() = context.into();
|
|
}
|
|
|
|
/// Obtain current context handle
|
|
pub fn current(&self) -> Arc<Context> {
|
|
self.context.read().unwrap().clone()
|
|
}
|
|
|
|
/// Obtain current context name
|
|
/// Will not work for ephemeral context (obtained from config)
|
|
pub fn current_name(&self) -> String {
|
|
self.config.read().unwrap().context.clone()
|
|
}
|
|
}
|