use crate::CoreError;
use directories::ProjectDirs;
use lazy_static::lazy_static;
use log::{info, trace};
use quic_p2p::Config as QuicP2pConfig;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
#[cfg(test)]
use std::fs;
use std::{
ffi::OsStr,
fs::File,
io::{self, BufReader},
path::PathBuf,
sync::Mutex,
};
use unwrap::unwrap;
const CONFIG_DIR_QUALIFIER: &str = "net";
const CONFIG_DIR_ORGANISATION: &str = "MaidSafe";
const CONFIG_DIR_APPLICATION: &str = "safe_core";
const CONFIG_FILE: &str = "safe_core.config";
const VAULT_CONFIG_DIR_APPLICATION: &str = "safe_vault";
const VAULT_CONNECTION_INFO_FILE: &str = "vault_connection_info.config";
lazy_static! {
static ref CONFIG_DIR_PATH: Mutex<Option<PathBuf>> = Mutex::new(None);
static ref DEFAULT_SAFE_CORE_PROJECT_DIRS: Option<ProjectDirs> = ProjectDirs::from(
CONFIG_DIR_QUALIFIER,
CONFIG_DIR_ORGANISATION,
CONFIG_DIR_APPLICATION,
);
static ref DEFAULT_VAULT_PROJECT_DIRS: Option<ProjectDirs> = ProjectDirs::from(
CONFIG_DIR_QUALIFIER,
CONFIG_DIR_ORGANISATION,
VAULT_CONFIG_DIR_APPLICATION,
);
}
pub fn set_config_dir_path<P: AsRef<OsStr> + ?Sized>(path: &P) {
*unwrap!(CONFIG_DIR_PATH.lock()) = Some(From::from(path));
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)]
pub struct Config {
pub quic_p2p: QuicP2pConfig,
pub dev: Option<DevConfig>,
}
#[cfg(any(target_os = "android", target_os = "androideabi", target_os = "ios"))]
fn check_config_path_set() -> Result<(), CoreError> {
if unwrap!(CONFIG_DIR_PATH.lock()).is_none() {
Err(CoreError::QuicP2p(quic_p2p::QuicP2pError::Configuration {
e: "Boostrap cache directory not set".to_string(),
}))
} else {
Ok(())
}
}
impl Config {
pub fn new() -> Self {
let quic_p2p = Self::read_qp2p_from_file().unwrap_or_default();
Self {
quic_p2p,
dev: None,
}
}
fn read_qp2p_from_file() -> Result<QuicP2pConfig, CoreError> {
let mut config: QuicP2pConfig = {
match read_config_file(dirs()?, CONFIG_FILE) {
Err(CoreError::IoError(ref err)) if err.kind() == io::ErrorKind::NotFound => {
#[cfg(any(
target_os = "android",
target_os = "androideabi",
target_os = "ios"
))]
check_config_path_set()?;
let custom_dir =
if let Some(custom_path) = unwrap!(CONFIG_DIR_PATH.lock()).clone() {
Some(custom_path.into_os_string().into_string().map_err(|_| {
CoreError::from("Config path is not a valid UTF-8 string")
})?)
} else {
None
};
QuicP2pConfig {
our_type: quic_p2p::OurType::Client,
bootstrap_cache_dir: custom_dir,
..Default::default()
}
}
result => result?,
}
};
if let Ok(node_info) = read_config_file(vault_dirs()?, VAULT_CONNECTION_INFO_FILE) {
let _ = config.hard_coded_contacts.insert(node_info);
}
Ok(config)
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)]
pub struct DevConfig {
pub mock_unlimited_coins: bool,
pub mock_in_memory_storage: bool,
pub mock_vault_path: Option<String>,
}
pub fn get_config() -> Config {
Config::new()
}
pub fn config_dir() -> Result<PathBuf, CoreError> {
Ok(dirs()?.config_dir().to_path_buf())
}
fn dirs() -> Result<ProjectDirs, CoreError> {
let project_dirs = if let Some(custom_path) = unwrap!(CONFIG_DIR_PATH.lock()).clone() {
ProjectDirs::from_path(custom_path)
} else {
DEFAULT_SAFE_CORE_PROJECT_DIRS.clone()
};
project_dirs.ok_or_else(|| CoreError::from("Cannot determine project directory paths"))
}
fn vault_dirs() -> Result<ProjectDirs, CoreError> {
let project_dirs = if let Some(custom_path) = unwrap!(CONFIG_DIR_PATH.lock()).clone() {
ProjectDirs::from_path(custom_path)
} else {
DEFAULT_VAULT_PROJECT_DIRS.clone()
};
project_dirs.ok_or_else(|| CoreError::from("Cannot determine vault directory paths"))
}
fn read_config_file<T>(dirs: ProjectDirs, file: &str) -> Result<T, CoreError>
where
T: DeserializeOwned,
{
let path = dirs.config_dir().join(file);
let file = match File::open(&path) {
Ok(file) => {
trace!("Reading: {}", path.display());
file
}
Err(error) => {
trace!("Not available: {}", path.display());
return Err(error.into());
}
};
let reader = BufReader::new(file);
serde_json::from_reader(reader).map_err(|err| {
info!("Could not parse: {} ({:?})", err, err);
err.into()
})
}
#[cfg(test)]
pub fn write_config_file(config: &Config) -> Result<PathBuf, CoreError> {
let dir = config_dir()?;
fs::create_dir_all(dir.clone())?;
let path = dir.join(CONFIG_FILE);
let mut file = File::create(&path)?;
serde_json::to_writer_pretty(&mut file, config)?;
file.sync_all()?;
Ok(path)
}
#[cfg(all(test, feature = "mock-network"))]
mod test {
use super::*;
use std::env::temp_dir;
#[test]
fn custom_config_path() {
let path = temp_dir();
let temp_dir_path = path.clone();
set_config_dir_path(&path);
let config: Config = Default::default();
unwrap!(write_config_file(&config));
let read_cfg = Config::new();
assert_eq!(config, read_cfg);
let mut path = unwrap!(ProjectDirs::from_path(temp_dir_path.clone()))
.config_dir()
.to_path_buf();
path.push(CONFIG_FILE);
unwrap!(std::fs::remove_file(path));
let config = Config::new();
let expected_config = Config {
quic_p2p: QuicP2pConfig {
our_type: quic_p2p::OurType::Client,
bootstrap_cache_dir: Some(unwrap!(temp_dir_path.into_os_string().into_string())),
..Default::default()
},
..Default::default()
};
assert_eq!(config, expected_config);
}
}