From 434aec7f57a29cf9c5986d5b449566fea2edc191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabrice=20Desr=C3=A9?= Date: Thu, 10 Mar 2016 14:44:06 -0800 Subject: [PATCH] Basic profile service --- src/config_store.rs | 4 +- src/controller.rs | 13 +++++- src/main.rs | 1 + src/profile_service.rs | 93 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 src/profile_service.rs diff --git a/src/config_store.rs b/src/config_store.rs index b7ed126e..926e4dc8 100644 --- a/src/config_store.rs +++ b/src/config_store.rs @@ -193,7 +193,7 @@ describe! config { describe! config_service { before_each { - let mut config = ConfigService::new(&config_file_name); + let config = ConfigService::new(&config_file_name); } it "should remember properties" { @@ -261,7 +261,7 @@ describe! config { it "ConfigService should remember things over restarts" { // Block to make `config` go out of scope { - let mut config = ConfigService::new(&config_file_name); + let config = ConfigService::new(&config_file_name); config.set("foo", "bar", "baz"); } // `config` should now be out of scope and dropped diff --git a/src/controller.rs b/src/controller.rs index 84c2d272..c41e40d9 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -13,6 +13,7 @@ use http_server::HttpServer; use iron::{Request, Response, IronResult}; use iron::headers::{ ContentType, AccessControlAllowOrigin }; use iron::status::Status; +use profile_service::ProfileService; use self::collections::vec::IntoIter; use service::{ Service, ServiceAdapter, ServiceProperties }; use std::collections::hash_map::HashMap; @@ -35,6 +36,7 @@ pub struct FoxBox { websockets: Arc>>, pub config: Arc, upnp: Arc, + profile_service: Arc } const DEFAULT_HOSTNAME: &'static str = "::"; // ipv6 default. @@ -59,6 +61,7 @@ pub trait Controller : Send + Sync + Clone + Reflect + 'static { fn get_config(&self) -> &ConfigService; fn get_upnp_manager(&self) -> Arc; + fn get_profile(&self) -> &ProfileService; } impl FoxBox { @@ -68,6 +71,7 @@ impl FoxBox { http_port: u16, ws_port: u16) -> Self { + let profile_service = ProfileService::new(None); FoxBox { services: Arc::new(Mutex::new(HashMap::new())), websockets: Arc::new(Mutex::new(HashMap::new())), @@ -77,8 +81,9 @@ impl FoxBox { }), http_port: http_port, ws_port: ws_port, - config: Arc::new(ConfigService::new("foxbox.conf")), - upnp: Arc::new(UpnpManager::new()) + config: Arc::new(ConfigService::new(&profile_service.path_for("foxbox.conf"))), + upnp: Arc::new(UpnpManager::new()), + profile_service: Arc::new(profile_service) } } } @@ -203,6 +208,10 @@ impl Controller for FoxBox { &self.config } + fn get_profile(&self) -> &ProfileService { + &self.profile_service + } + fn get_upnp_manager(&self) -> Arc { self.upnp.clone() } diff --git a/src/main.rs b/src/main.rs index 94f309de..1ee9c3d8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -65,6 +65,7 @@ mod config_store; mod controller; mod http_server; mod managed_process; +mod profile_service; mod registration; mod service; mod upnp; diff --git a/src/profile_service.rs b/src/profile_service.rs new file mode 100644 index 00000000..72350940 --- /dev/null +++ b/src/profile_service.rs @@ -0,0 +1,93 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/// Simple service that helps with managing files in a configurable +/// directory. + +use std::env; +use std::fs; + +pub struct ProfileService { + profile_dir: String +} + +fn get_env_var(name: &str) -> Option { + if let Some(value) = env::var_os(name) { + return match value.into_string() { + Ok(s) => Some(s), + Err(_) => None + }; + } + None +} + +impl ProfileService { + pub fn new(profile_dir: Option) -> Self { + // If no explicit profile directory is set, follow the Freedesktop + // standard: If $XDG_DATA_HOME is either not set or empty, a default + // equal to $HOME/.local/share is used. + let dir = match profile_dir { + Some(path) => path, + None => { + if let Some(xdg) = get_env_var("XDG_DATA_HOME") { + format!("{}/foxbox", xdg) + } else { + if let Some(home) = get_env_var("HOME") { + format!("{}/.local/share/foxbox", home) + } else { + panic!("Unable to get $HOME value"); + } + } + } + }; + + // Create the directory if needed. Panic if we can't or if there is an + // existing file with the same path. + match fs::metadata(dir.clone()) { + Ok(meta) => { + if !meta.is_dir() { + panic!("The path {} is a file, and can't be used as a profile.", + dir); + } + }, + Err(_) => { + fs::create_dir_all(dir.clone()).unwrap_or_else(|err| { + panic!("Unable to create directory {} : {}", dir, err); + }); + } + } + + ProfileService { + profile_dir: dir + } + } + + // Returns an absolute path for a file. + // This doesn't try to create the file. + pub fn path_for(&self, relative_path: &str) -> String { + format!("{}/{}", self.profile_dir, relative_path) + } +} + +#[test] +#[should_panic] +fn test_bogus_path() { + let _ = ProfileService::new(Some("/cant_create/that/path".to_owned())); +} + +#[test] +fn test_default_profile() { + use std::fs::File; + use std::io::Write; + + let profile = ProfileService::new(None); + let path = profile.path_for("test.conf"); + // We can't assert anything on the path value since it's environment + // dependant, but we should be able to create & delete the file. + // We let the test panic if something goes wrong. + let mut f = File::create(path.clone()).unwrap(); + f.write_all(b"Hello, world!").unwrap(); + fs::remove_file(path).unwrap(); + assert!(true); +}