nodes/
config.rs

1use super::toml;
2use super::storage;
3
4use std::io;
5use std::env;
6use std::fs;
7
8use std::fs::File;
9use std::io::prelude::*;
10use std::path::PathBuf;
11use std::collections::HashSet;
12use std::collections::HashMap;
13use toml::ValueImpl;
14
15pub struct StorageConfig {
16    default: String,
17    local_search_paths: Vec<String>,
18    storages: HashMap<String, PathBuf>
19}
20
21pub struct Config {
22    value: Option<toml::Value>,
23    storage: StorageConfig
24}
25
26#[derive(Deserialize, Serialize)]
27struct ParseStorage {
28    name: String,
29    path: PathBuf
30}
31
32#[derive(Deserialize, Serialize)]
33struct ParseStorageConfig {
34    default: Option<String>,
35    local_search_paths: Option<Vec<String>>,
36    storages: Option<Vec<ParseStorage>>,
37}
38
39#[derive(Debug)]
40pub enum ConfigError {
41    Read(io::Error),
42    Parse(toml::de::Error),
43    InvalidStorage,
44    NoStorage,
45    NoStorages,
46    RedundantStorages,
47    InvalidDefaultStorage
48}
49
50impl Config {
51    /// Load the configuration from the default location.
52    /// Will load the default configuration if the file in
53    /// the default location does not exist.
54    /// Will only fail if the config file is invalid.
55    pub fn load_default() -> Result<Config, ConfigError> {
56        // second
57        use toml::LoadError;
58        let value = match toml::Value::load(Config::config_path()) {
59            Ok(a) => a,
60            Err(LoadError::Open(_)) => return Ok(Config::default_config()),
61            Err(LoadError::Read(e)) => return Err(ConfigError::Read(e)),
62            Err(LoadError::Parse(e)) => return Err(ConfigError::Parse(e)),
63        };
64
65        let storage = match value.get("storage") {
66            None => return Err(ConfigError::NoStorage),
67            Some(a) => match &a.clone().try_into::<ParseStorageConfig>() {
68                &Ok(ref a) => Config::parse_storage_config(a)?,
69                _ => return Err(ConfigError::InvalidStorage),
70            },
71        };
72
73        Ok(Config{value: Some(value), storage})
74    }
75
76    /// Tries to load the storage with the given name.
77    /// Will return None if there is no such storage or it cannot be loaded.
78    /// Storages are lazily loaded/parsed and not cached so the caller
79    /// should cache it if needed multiple times.
80    pub fn load_storage(&self, name: &str) 
81            -> Result<storage::Storage, storage::LoadStorageError> {
82        let path = match self.storage.storages.get(name) {
83            Some(a) => a.clone(),
84            None => return Err(storage::LoadStorageError::InvalidName),
85        };
86        
87        storage::Storage::load(self, name, path)
88    }
89
90    /// Loads the default storage.
91    pub fn load_default_storage(&self)
92            -> Result<storage::Storage, storage::LoadStorageError> {
93        self.load_storage(&self.storage.default)
94    }
95
96    /// Attempts to load a local storage.
97    /// Will search from cwd upwards and check storage.local-search-paths
98    /// from config in every directory.
99    /// Will not recover from error, i.e. only attempt to load the first
100    /// found node storage.
101    /// Returns LoadStorageError::NotFound if none is found.
102    pub fn load_local_storage(&self)
103            -> Result<storage::Storage, storage::LoadStorageError> {
104        let mut cwd = env::current_dir().expect("Failed to get current dir");
105        'outer: loop {
106            let mut npath = PathBuf::from(cwd.clone());
107            for spath in &self.storage.local_search_paths {
108                npath.push(spath);
109                if !npath.is_dir() {
110                    println!("{:?} isn't a dir", npath);
111                    continue;
112                }
113
114                npath.push("storage");
115                if !npath.is_file() {
116                    println!("{:?} isn't a file", npath);
117                    continue;
118                }
119
120                npath.pop(); // pop "storage" again, we need the storage root
121                let name = cwd.file_name()
122                    .map(|v| v.to_str().expect("Invalid path"))
123                    .unwrap_or("root").to_string();
124                return storage::Storage::load(self, &name, npath)
125            }
126
127            if !cwd.pop() {
128                return Err(storage::LoadStorageError::NotFound);
129            }
130        }
131    }
132
133    pub fn config_folder() -> PathBuf {
134        let mut p = Config::home_dir();
135        p.push(".config");
136        p.push("nodes");
137        p
138    }
139
140    pub fn config_path() -> PathBuf {
141        let mut p = Config::config_folder();
142        p.push("config");
143        p
144    }
145
146
147    /// Returns the parsed config file as value
148    pub fn value(&self) -> &Option<toml::Value> {
149        &self.value
150    }
151
152    // -- private implementation --
153    fn default_config() -> Config {
154        let mut storages = HashMap::new();
155
156        // we make sure that the default storage exists
157        // when running nodes for the first time this assures that
158        // it can already be used
159        let mut storage = Config::default_storage_path();
160        if !storage.is_dir() {
161            fs::create_dir_all(&storage)
162                .expect("Failed to create default storage path");
163
164            storage.push("storage");
165            File::create(&storage)
166                .and_then(|mut f| f.write_all(b"last_id = 0"))
167                .expect("Unable to create default storage file");
168            storage.pop();
169
170            storage.push("nodes");
171            fs::create_dir_all(&storage).unwrap();
172            storage.pop();
173
174            storage.push("meta");
175            fs::create_dir_all(&storage).unwrap();
176            storage.pop();
177        }
178        
179        storages.insert("default".to_string(), storage);
180        Config {
181            value: None,
182            storage: StorageConfig {
183                default: "default".to_string(),
184                local_search_paths: Config::default_local_search_paths(),
185                storages,
186            }
187        }
188    }
189
190    fn parse_storage_config(config: &ParseStorageConfig)
191            -> Result<StorageConfig, ConfigError> {
192        let mut paths = HashSet::new();
193        let mut storages = HashMap::new();
194
195        // there has to be at least one storage
196        let cstorages = match &config.storages {
197            &Some(ref a) => a,
198            &None => return Err(ConfigError::NoStorages),
199        };
200
201        if cstorages.is_empty() {
202            return Err(ConfigError::NoStorages);
203        }
204
205        for storage in cstorages {
206            if !paths.insert(storage.path.clone()) {
207                return Err(ConfigError::RedundantStorages);
208            }
209
210            let v = storages.insert(
211                storage.name.clone(),
212                storage.path.clone()
213            );
214
215            if v.is_some() {
216                return Err(ConfigError::RedundantStorages);
217            }
218        }
219
220        // just use the first entry as default if there is none given
221        // we can unwrap since we already know that storages is not empty
222        let default = config.default.clone()
223            .unwrap_or(cstorages.first().unwrap().name.clone());
224        if storages.get(&default).is_none() {
225            return Err(ConfigError::InvalidDefaultStorage);
226        }
227
228        // local_search_paths
229        let local_search_paths = config.local_search_paths.as_ref()
230            .unwrap_or(&Config::default_local_search_paths()).clone();
231        Ok(StorageConfig{storages, local_search_paths, default})
232    }
233
234    fn home_dir() -> PathBuf {
235        env::home_dir().expect("Could not retrieve home directory")
236    }
237
238    fn default_storage_path() -> PathBuf {
239        let mut p = Config::home_dir();
240        p.push(".local");
241        p.push("share");
242        p.push("nodes-test"); // TODO
243        p
244    }
245
246    fn default_local_search_paths() -> Vec<String> {
247        vec!(String::from(".nodes"))
248    }
249}