use std::{
collections::HashMap,
path::{Path, PathBuf},
};
use clap::Subcommand;
use config::Config;
use directories::ProjectDirs;
use ini::Ini;
use quick_xml::DeError;
use serde::Deserialize;
use crate::{download::Server, PROJECT_DIR};
pub fn execute_configure_action(action: ConfigureAction) -> Result<(), ConfigurationError> {
match action {
ConfigureAction::Servers { action } => execute_server_action(action),
}
}
fn execute_server_action(action: ServerAction) -> Result<(), ConfigurationError> {
match action {
ServerAction::GetDownloadServer => {
let settings = read_settings(PROJECT_DIR.wait());
if let Some(download_server) = settings.download_server {
Ok(println!("{download_server}"))
} else {
Err(ConfigurationError::MissingServer)
}
}
ServerAction::SetDownloadServer { server_name } => {
update_settings(PROJECT_DIR.wait(), |mut settings| {
if settings.servers.contains_key(&server_name) {
settings.download_server = Some(server_name);
Ok(settings)
} else {
Err(ConfigurationError::MissingServer)
}
})
}
ServerAction::Add {
name,
host,
username,
password,
connections,
secure,
} => update_settings(PROJECT_DIR.wait(), |mut settings| {
if settings.servers.contains_key(&name) {
Err(ConfigurationError::ServerExists)
} else {
settings.servers.insert(
name,
Server {
host,
username,
password,
connections,
secure,
},
);
Ok(settings)
}
}),
ServerAction::Remove { server_name } => update_settings(PROJECT_DIR.wait(), |mut settings| {
if settings.servers.remove(&server_name).is_some() {
if let Some(default_server) = &settings.download_server {
if default_server == &server_name {
settings.download_server = None;
}
}
Ok(settings)
} else {
Err(ConfigurationError::MissingServer)
}
}),
ServerAction::List => {
let settings = read_settings(PROJECT_DIR.wait());
Ok(settings.servers.into_iter().for_each(|(name, server)| {
println!("Name: {}", name);
println!("Host: {}", server.host);
println!("Username: {}", server.username);
println!("Connections: {}", server.connections);
println!("Secure: {}", server.secure);
println!();
}))
}
}
}
fn update_settings<F>(app_dir: &ProjectDirs, f: F) -> Result<(), ConfigurationError>
where
F: FnOnce(Settings) -> Result<Settings, ConfigurationError>,
{
f(read_settings(app_dir))?
.write_to_file(&settings_path(app_dir))
.map_err(ConfigurationError::WriteFailed)
}
pub fn read_settings(app_dir: &ProjectDirs) -> Settings {
Settings::from_file(&settings_path(app_dir)).unwrap_or_default()
}
fn settings_path(app_dir: &ProjectDirs) -> PathBuf {
let mut settings_path = PathBuf::from(app_dir.config_dir());
settings_path.push("settings.ini");
settings_path
}
#[derive(Debug, Subcommand)]
pub enum ConfigureAction {
Servers {
#[clap(subcommand)]
action: ServerAction,
},
}
#[derive(Debug, Subcommand)]
pub enum ServerAction {
GetDownloadServer,
SetDownloadServer {
#[clap(value_parser)]
server_name: String,
},
Add {
#[clap(value_parser)]
name: String,
#[clap(value_parser)]
host: String,
#[clap(value_parser)]
username: String,
#[clap(value_parser)]
password: String,
#[clap(value_parser)]
connections: usize,
#[clap(value_parser)]
secure: bool,
},
#[clap(alias = "rm")]
Remove {
#[clap(value_parser)]
server_name: String,
},
#[clap(alias = "ls")]
List,
}
#[derive(Debug)]
pub enum ConfigurationError {
ServerExists,
MissingServer,
WriteFailed(std::io::Error),
ReadFailed(std::io::Error),
DeserializationFailed(DeError),
}
#[derive(Debug, Deserialize)]
pub struct Settings {
pub download_server: Option<String>,
pub servers: HashMap<String, Server>,
}
impl Default for Settings {
fn default() -> Self {
Self {
download_server: Default::default(),
servers: Default::default(),
}
}
}
impl Settings {
fn from_file(settings_file: &Path) -> Result<Settings, config::ConfigError> {
Config::builder()
.add_source(config::File::from(settings_file))
.build()
.and_then(|conf| conf.try_deserialize())
}
fn write_to_file(&self, settings_file: &Path) -> Result<(), std::io::Error> {
let mut ini = Ini::new();
if let Some(default_server) = &self.download_server {
ini
.with_general_section()
.set("download_server", default_server);
}
self.servers.iter().for_each(|(name, server)| {
ini
.with_section(Some(format!("servers.{name}")))
.set("host", &server.host)
.set("username", &server.username)
.set("password", &server.password)
.set("connections", server.connections.to_string())
.set("secure", server.secure.to_string());
});
if let Some(dir) = settings_file.parent() {
if let Err(e) = std::fs::create_dir_all(dir) {
return Err(e);
}
}
ini.write_to_file(settings_file)
}
}