use super::toml;
use super::storage;
use std::io;
use std::env;
use std::fs;
use std::fs::File;
use std::io::prelude::*;
use std::path::PathBuf;
use std::collections::HashSet;
use std::collections::HashMap;
use toml::ValueImpl;
pub struct StorageConfig {
default: String,
local_search_paths: Vec<String>,
storages: HashMap<String, PathBuf>
}
pub struct Config {
value: Option<toml::Value>,
storage: StorageConfig
}
#[derive(Deserialize, Serialize)]
struct ParseStorage {
name: String,
path: PathBuf
}
#[derive(Deserialize, Serialize)]
struct ParseStorageConfig {
default: Option<String>,
local_search_paths: Option<Vec<String>>,
storages: Option<Vec<ParseStorage>>,
}
#[derive(Debug)]
pub enum ConfigError {
Read(io::Error),
Parse(toml::de::Error),
InvalidStorage,
NoStorage,
NoStorages,
RedundantStorages,
InvalidDefaultStorage
}
impl Config {
pub fn load_default() -> Result<Config, ConfigError> {
use toml::LoadError;
let value = match toml::Value::load(Config::config_path()) {
Ok(a) => a,
Err(LoadError::Open(_)) => return Ok(Config::default_config()),
Err(LoadError::Read(e)) => return Err(ConfigError::Read(e)),
Err(LoadError::Parse(e)) => return Err(ConfigError::Parse(e)),
};
let storage = match value.get("storage") {
None => return Err(ConfigError::NoStorage),
Some(a) => match &a.clone().try_into::<ParseStorageConfig>() {
&Ok(ref a) => Config::parse_storage_config(a)?,
_ => return Err(ConfigError::InvalidStorage),
},
};
Ok(Config{value: Some(value), storage})
}
pub fn load_storage(&self, name: &str)
-> Result<storage::Storage, storage::LoadStorageError> {
let path = match self.storage.storages.get(name) {
Some(a) => a.clone(),
None => return Err(storage::LoadStorageError::InvalidName),
};
storage::Storage::load(self, name, path)
}
pub fn load_default_storage(&self)
-> Result<storage::Storage, storage::LoadStorageError> {
self.load_storage(&self.storage.default)
}
pub fn load_local_storage(&self)
-> Result<storage::Storage, storage::LoadStorageError> {
let mut cwd = env::current_dir().expect("Failed to get current dir");
'outer: loop {
let mut npath = PathBuf::from(cwd.clone());
for spath in &self.storage.local_search_paths {
npath.push(spath);
if !npath.is_dir() {
println!("{:?} isn't a dir", npath);
continue;
}
npath.push("storage");
if !npath.is_file() {
println!("{:?} isn't a file", npath);
continue;
}
npath.pop(); let name = cwd.file_name()
.map(|v| v.to_str().expect("Invalid path"))
.unwrap_or("root").to_string();
return storage::Storage::load(self, &name, npath)
}
if !cwd.pop() {
return Err(storage::LoadStorageError::NotFound);
}
}
}
pub fn config_folder() -> PathBuf {
let mut p = Config::home_dir();
p.push(".config");
p.push("nodes");
p
}
pub fn config_path() -> PathBuf {
let mut p = Config::config_folder();
p.push("config");
p
}
pub fn value(&self) -> &Option<toml::Value> {
&self.value
}
fn default_config() -> Config {
let mut storages = HashMap::new();
let mut storage = Config::default_storage_path();
if !storage.is_dir() {
fs::create_dir_all(&storage)
.expect("Failed to create default storage path");
storage.push("storage");
File::create(&storage)
.and_then(|mut f| f.write_all(b"last_id = 0"))
.expect("Unable to create default storage file");
storage.pop();
storage.push("nodes");
fs::create_dir_all(&storage).unwrap();
storage.pop();
storage.push("meta");
fs::create_dir_all(&storage).unwrap();
storage.pop();
}
storages.insert("default".to_string(), storage);
Config {
value: None,
storage: StorageConfig {
default: "default".to_string(),
local_search_paths: Config::default_local_search_paths(),
storages,
}
}
}
fn parse_storage_config(config: &ParseStorageConfig)
-> Result<StorageConfig, ConfigError> {
let mut paths = HashSet::new();
let mut storages = HashMap::new();
let cstorages = match &config.storages {
&Some(ref a) => a,
&None => return Err(ConfigError::NoStorages),
};
if cstorages.is_empty() {
return Err(ConfigError::NoStorages);
}
for storage in cstorages {
if !paths.insert(storage.path.clone()) {
return Err(ConfigError::RedundantStorages);
}
let v = storages.insert(
storage.name.clone(),
storage.path.clone()
);
if v.is_some() {
return Err(ConfigError::RedundantStorages);
}
}
let default = config.default.clone()
.unwrap_or(cstorages.first().unwrap().name.clone());
if storages.get(&default).is_none() {
return Err(ConfigError::InvalidDefaultStorage);
}
let local_search_paths = config.local_search_paths.as_ref()
.unwrap_or(&Config::default_local_search_paths()).clone();
Ok(StorageConfig{storages, local_search_paths, default})
}
fn home_dir() -> PathBuf {
env::home_dir().expect("Could not retrieve home directory")
}
fn default_storage_path() -> PathBuf {
let mut p = Config::home_dir();
p.push(".local");
p.push("share");
p.push("nodes-test"); p
}
fn default_local_search_paths() -> Vec<String> {
vec!(String::from(".nodes"))
}
}