diff --git a/Cargo.lock b/Cargo.lock index 9f09281..28549d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,6 +4,7 @@ version = "0.2.0" dependencies = [ "ape 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "app_dirs 1.1.1 (git+https://github.com/agersant/app-dirs-rs)", + "error-chain 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.9.13 (registry+https://github.com/rust-lang/crates.io-index)", "id3 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", @@ -56,6 +57,29 @@ dependencies = [ "xdg 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "backtrace" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace-sys 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "backtrace-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gcc 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "bitflags" version = "0.7.0" @@ -88,6 +112,11 @@ name = "byteorder" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "cfg-if" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "color_quant" version = "1.0.0" @@ -112,6 +141,15 @@ dependencies = [ "url 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "dbghelp-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "deque" version = "0.3.1" @@ -199,6 +237,14 @@ dependencies = [ "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "error-chain" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "flate2" version = "0.2.14" @@ -788,6 +834,11 @@ dependencies = [ "url 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rustc-demangle" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "rustc-serialize" version = "0.3.21" @@ -1050,14 +1101,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66" "checksum ape 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b419c2e36e91776200588f91e24c970d16d34236369136ca819f12dd903c5691" "checksum app_dirs 1.1.1 (git+https://github.com/agersant/app-dirs-rs)" = "" +"checksum backtrace 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f551bc2ddd53aea015d453ef0b635af89444afa5ed2405dd0b2062ad5d600d80" +"checksum backtrace-sys 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3602e8d8c43336088a8505fa55cae2b3884a9be29440863a11528a42f46f6bb7" "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" "checksum bodyparser 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "07b171b407e583dc8f01011a713f20575a81ac60acecf3b8153012709aeb1fd6" "checksum buf_redux 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "861b9d19b9f5cb40647242d10d0cb0a13de0a96d5ff8c8a01ea324fa3956eb7d" "checksum byteorder 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "96c8b41881888cc08af32d47ac4edd52bc7fa27fef774be47a92443756451304" "checksum byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855" +"checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c" "checksum color_quant 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a475fc4af42d83d28adf72968d9bcfaf035a1a9381642d8e85d8a04957767b0d" "checksum conduit-mime-types 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "95ca30253581af809925ef68c2641cc140d6183f43e12e0af4992d53768bd7b8" "checksum cookie 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0e3d6405328b6edb412158b3b7710e2634e23f3614b9bb1c412df7952489a626" +"checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850" "checksum deque 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1614659040e711785ed8ea24219140654da1729f3ec8a47a9719d041112fe7bf" "checksum dtoa 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0dd841b58510c9618291ffa448da2e4e0f699d984d436122372f446dae62263d" "checksum encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" @@ -1069,6 +1124,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" "checksum enum_primitive 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f79eff5be92a4d7d5bddf7daa7d650717ea71628634efe6ca7bcda85b2183c23" "checksum error 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "a6e606f14042bb87cc02ef6a14db6c90ab92ed6f62d87e69377bc759fd7987cc" +"checksum error-chain 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1cd681735364a04cd5d69f01a4f6768e70473941f8d86d8c224faf6955a75799" "checksum flate2 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "3eeb481e957304178d2e782f2da1257f1434dfecbae883bafb61ada2a9fea3bb" "checksum gcc 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)" = "553f11439bdefe755bf366b264820f1da70f3aaf3924e594b886beb9c831bcf5" "checksum gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0912515a8ff24ba900422ecda800b52f4016a56251922d397c576bf92c690518" @@ -1136,6 +1192,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957" "checksum route-recognizer 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "4f0a750d020adb1978f5964ea7bca830585899b09da7cbb3f04961fc2400122d" "checksum router 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b94397bfa5b772b4375be4da12560a7c1c1e74b2e35c46ed312958aad56df726" +"checksum rustc-demangle 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1430d286cadb237c17c885e25447c982c97113926bb579f4379c0eca8d9586dc" "checksum rustc-serialize 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)" = "bff9fc1c79f2dec76b253273d07682e94a978bd8f132ded071188122b2af9818" "checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084" "checksum scoped_threadpool 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3ef399c8893e8cb7aa9696e895427fab3a6bf265977bb96e126f24ddd2cda85a" diff --git a/Cargo.toml b/Cargo.toml index 67b337d..a003eda 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ ui = [] [dependencies] ape = "0.1.2" app_dirs = { git = "https://github.com/agersant/app-dirs-rs" } +error-chain = "0.7.1" getopts = "0.2.14" hyper = "0.9.10" id3 = "0.1.11" diff --git a/src/api.rs b/src/api.rs index e4678ba..30f1156 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,4 +1,3 @@ -use core::str::Utf8Error; use std::fs; use std::io; use std::path::*; @@ -15,7 +14,7 @@ use rustc_serialize::json; use url::percent_encoding::percent_decode; use collection::*; -use error::*; +use errors::*; use thumbnails::*; use utils::*; @@ -37,27 +36,6 @@ impl Version { } } -impl From for IronError { - fn from(err: PError) -> IronError { - match err { - PError::Io(e) => IronError::new(e, status::NotFound), - PError::CannotClearExistingIndex => IronError::new(err, status::InternalServerError), - PError::PathDecoding => IronError::new(err, status::InternalServerError), - PError::ConfigDirectoryError => IronError::new(err, status::InternalServerError), - PError::CacheDirectoryError => IronError::new(err, status::InternalServerError), - PError::PathNotInVfs => IronError::new(err, status::NotFound), - PError::CannotServeDirectory => IronError::new(err, status::BadRequest), - PError::UnsupportedFileType => IronError::new(err, status::BadRequest), - PError::AlbumArtSearchError => IronError::new(err, status::InternalServerError), - PError::ImageProcessingError => IronError::new(err, status::InternalServerError), - PError::UnsupportedMetadataFormat => IronError::new(err, status::InternalServerError), - PError::MetadataDecodingError => IronError::new(err, status::InternalServerError), - PError::Unauthorized => IronError::new(err, status::Unauthorized), - PError::IncorrectCredentials => IronError::new(err, status::BadRequest), - } - } -} - pub fn get_api_handler(collection: Arc) -> Mount { let mut api_handler = Mount::new(); @@ -97,20 +75,18 @@ pub fn get_api_handler(collection: Arc) -> Mount { api_handler } -fn path_from_request(request: &Request) -> Result { +fn path_from_request(request: &Request) -> Result { let path_string = request.url.path().join("\\"); - let decoded_path = try!(percent_decode(path_string.as_bytes()).decode_utf8()); + let decoded_path = percent_decode(path_string.as_bytes()).decode_utf8()?; Ok(PathBuf::from(decoded_path.deref())) } struct AuthRequirement; impl BeforeMiddleware for AuthRequirement { fn before(&self, req: &mut Request) -> IronResult<()> { - let auth_cookie = req.get_cookie("username"); - if auth_cookie.is_some() { - Ok(()) - } else { - Err(IronError::new(PError::Unauthorized, status::Unauthorized)) + match req.get_cookie("username") { + Some(_) => Ok(()), + None => Err(Error::from(ErrorKind::AuthenticationRequired).into()), } } } @@ -125,13 +101,13 @@ fn version(_: &mut Request) -> IronResult { fn auth(request: &mut Request, collection: &Collection) -> IronResult { let input = request.get_ref::().unwrap(); - let username = match input.find(&["username"]) { - Some(¶ms::Value::String(ref username)) => username, - _ => return Err(IronError::from(PError::IncorrectCredentials)), + let username = match input.find(&["username"]) { + Some(¶ms::Value::String(ref username)) => username, + _ => return Err(Error::from(ErrorKind::MissingUsername).into()), }; - let password = match input.find(&["password"]) { - Some(¶ms::Value::String(ref password)) => password, - _ => return Err(IronError::from(PError::IncorrectCredentials)), + let password = match input.find(&["password"]) { + Some(¶ms::Value::String(ref password)) => password, + _ => return Err(Error::from(ErrorKind::MissingPassword).into()), }; if collection.auth(username.as_str(), password.as_str()) { let mut response = Response::with((status::Ok, "")); @@ -140,7 +116,7 @@ fn auth(request: &mut Request, collection: &Collection) -> IronResult response.set_cookie(username_cookie); Ok(response) } else { - Err(IronError::from(PError::IncorrectCredentials)) + Err(Error::from(ErrorKind::IncorrectCredentials).into()) } } @@ -150,7 +126,7 @@ fn browse(request: &mut Request, collection: &Collection) -> IronResult return Err(IronError::new(e, status::BadRequest)), Ok(p) => p, }; - let browse_result = try!(collection.browse(&path)); + let browse_result = collection.browse(&path)?; let result_json = json::encode(&browse_result); let result_json = match result_json { @@ -167,7 +143,7 @@ fn flatten(request: &mut Request, collection: &Collection) -> IronResult return Err(IronError::new(e, status::BadRequest)), Ok(p) => p, }; - let flatten_result = try!(collection.flatten(&path)); + let flatten_result = collection.flatten(&path)?; let result_json = json::encode(&flatten_result); let result_json = match result_json { @@ -204,7 +180,7 @@ fn serve(request: &mut Request, collection: &Collection) -> IronResult }; if !metadata.is_file() { - return Err(IronError::from(PError::CannotServeDirectory)); + return Err(Error::from(ErrorKind::CannotServeDirectory).into()); } if is_song(real_path.as_path()) { @@ -215,7 +191,7 @@ fn serve(request: &mut Request, collection: &Collection) -> IronResult return art(request, real_path.as_path()); } - Err(IronError::from(PError::UnsupportedFileType)) + Err(Error::from(ErrorKind::UnsupportedFileType).into()) } fn art(_: &mut Request, real_path: &Path) -> IronResult { diff --git a/src/collection.rs b/src/collection.rs index c93c950..e33c388 100644 --- a/src/collection.rs +++ b/src/collection.rs @@ -4,9 +4,9 @@ use std::path::PathBuf; use std::sync::Arc; use config::Config; +use errors::*; use index::*; use vfs::*; -use error::*; #[derive(Clone, Debug)] @@ -39,7 +39,7 @@ impl Collection { } } - pub fn load_config(&mut self, config: &Config) -> Result<(), PError> { + pub fn load_config(&mut self, config: &Config) -> Result<()> { self.users = config.users.to_vec(); Ok(()) } @@ -48,15 +48,15 @@ impl Collection { self.users.iter().any(|u| u.name == username && u.password == password) } - pub fn browse(&self, virtual_path: &Path) -> Result, PError> { + pub fn browse(&self, virtual_path: &Path) -> Result> { self.index.deref().browse(virtual_path) } - pub fn flatten(&self, virtual_path: &Path) -> Result, PError> { + pub fn flatten(&self, virtual_path: &Path) -> Result> { self.index.deref().flatten(virtual_path) } - pub fn locate(&self, virtual_path: &Path) -> Result { + pub fn locate(&self, virtual_path: &Path) -> Result { self.vfs.virtual_to_real(virtual_path) } } diff --git a/src/config.rs b/src/config.rs index 51e1557..a57bea0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,12 +1,12 @@ use regex; use std::fs; -use std::io; use std::io::Read; use std::path; use toml; use collection::User; use ddns::DDNSConfig; +use errors::*; use index::IndexConfig; use utils; use vfs::VfsConfig; @@ -27,34 +27,6 @@ const CONFIG_DDNS_HOST: &'static str = "host"; const CONFIG_DDNS_USERNAME: &'static str = "username"; const CONFIG_DDNS_PASSWORD: &'static str = "password"; -#[derive(Debug)] -pub enum ConfigError { - IoError(io::Error), - CacheDirectoryError, - ConfigDirectoryError, - TOMLParseError, - RegexError(regex::Error), - SecretParseError, - SleepDurationParseError, - AlbumArtPatternParseError, - UsersParseError, - MountDirsParseError, - DDNSParseError, - ConflictingMounts, -} - -impl From for ConfigError { - fn from(err: io::Error) -> ConfigError { - ConfigError::IoError(err) - } -} - -impl From for ConfigError { - fn from(err: regex::Error) -> ConfigError { - ConfigError::RegexError(err) - } -} - pub struct Config { pub secret: String, pub vfs: VfsConfig, @@ -64,26 +36,23 @@ pub struct Config { } impl Config { - pub fn parse(custom_path: Option) -> Result { + pub fn parse(custom_path: Option) -> Result { let config_path = match custom_path { Some(p) => p, None => { - let mut root = match utils::get_config_root() { - Ok(r) => r, - Err(_) => return Err(ConfigError::ConfigDirectoryError), - }; + let mut root = utils::get_config_root()?; root.push(DEFAULT_CONFIG_FILE_NAME); root } }; println!("Loading config from: {}", config_path.to_string_lossy()); - let mut config_file = try!(fs::File::open(config_path)); + let mut config_file = fs::File::open(config_path)?; let mut config_file_content = String::new(); - try!(config_file.read_to_string(&mut config_file_content)); + config_file.read_to_string(&mut config_file_content)?; let parsed_config = toml::Parser::new(config_file_content.as_str()).parse(); - let parsed_config = try!(parsed_config.ok_or(ConfigError::TOMLParseError)); + let parsed_config = parsed_config.ok_or("Could not parse config as valid TOML")?; let mut config = Config { secret: String::new(), @@ -93,57 +62,55 @@ impl Config { ddns: None, }; - try!(config.parse_secret(&parsed_config)); - try!(config.parse_index_sleep_duration(&parsed_config)); - try!(config.parse_mount_points(&parsed_config)); - try!(config.parse_users(&parsed_config)); - try!(config.parse_album_art_pattern(&parsed_config)); - try!(config.parse_ddns(&parsed_config)); + config.parse_secret(&parsed_config)?; + config.parse_index_sleep_duration(&parsed_config)?; + config.parse_mount_points(&parsed_config)?; + config.parse_users(&parsed_config)?; + config.parse_album_art_pattern(&parsed_config)?; + config.parse_ddns(&parsed_config)?; - let mut index_path = match utils::get_cache_root() { - Err(_) => return Err(ConfigError::CacheDirectoryError), - Ok(p) => p, - }; + let mut index_path = utils::get_cache_root()?; index_path.push(INDEX_FILE_NAME); - config.index.path = index_path; + config.index.path = index_path; Ok(config) } - fn parse_secret(&mut self, source: &toml::Table) -> Result<(), ConfigError> { - let secret = try!(source.get(CONFIG_SECRET).ok_or(ConfigError::SecretParseError)); - let secret = try!(secret.as_str().ok_or(ConfigError::SecretParseError)); - self.secret = secret.to_owned(); + fn parse_secret(&mut self, source: &toml::Table) -> Result<()> { + self.secret = source.get(CONFIG_SECRET) + .and_then(|s| s.as_str()) + .map(|s| s.to_owned()) + .ok_or("Could not parse config secret")?; Ok(()) } - fn parse_index_sleep_duration(&mut self, source: &toml::Table) -> Result<(), ConfigError> { + fn parse_index_sleep_duration(&mut self, source: &toml::Table) -> Result<()> { let sleep_duration = match source.get(CONFIG_INDEX_SLEEP_DURATION) { Some(s) => s, None => return Ok(()), }; let sleep_duration = match sleep_duration { &toml::Value::Integer(s) => s as u64, - _ => return Err(ConfigError::SleepDurationParseError), + _ => bail!("Could not parse index sleep duration"), }; self.index.sleep_duration = sleep_duration; Ok(()) } - fn parse_album_art_pattern(&mut self, source: &toml::Table) -> Result<(), ConfigError> { + fn parse_album_art_pattern(&mut self, source: &toml::Table) -> Result<()> { let pattern = match source.get(CONFIG_ALBUM_ART_PATTERN) { Some(s) => s, None => return Ok(()), }; let pattern = match pattern { &toml::Value::String(ref s) => s, - _ => return Err(ConfigError::AlbumArtPatternParseError), + _ => bail!("Could not parse album art pattern"), }; - self.index.album_art_pattern = Some(try!(regex::Regex::new(pattern))); + self.index.album_art_pattern = Some(regex::Regex::new(pattern)?); Ok(()) } - fn parse_users(&mut self, source: &toml::Table) -> Result<(), ConfigError> { + fn parse_users(&mut self, source: &toml::Table) -> Result<()> { let users = match source.get(CONFIG_USERS) { Some(s) => s, None => return Ok(()), @@ -151,28 +118,16 @@ impl Config { let users = match users { &toml::Value::Array(ref a) => a, - _ => return Err(ConfigError::UsersParseError), + _ => bail!("Could not parse users array"), }; for user in users { - let name = match user.lookup(CONFIG_USER_NAME) { - None => return Err(ConfigError::UsersParseError), - Some(n) => n, - }; - let name = match name.as_str() { - None => return Err(ConfigError::UsersParseError), - Some(n) => n, - }; - - let password = match user.lookup(CONFIG_USER_PASSWORD) { - None => return Err(ConfigError::UsersParseError), - Some(n) => n, - }; - let password = match password.as_str() { - None => return Err(ConfigError::UsersParseError), - Some(n) => n, - }; - + let name = user.lookup(CONFIG_USER_NAME) + .and_then(|n| n.as_str()) + .ok_or("Could not parse username")?; + let password = user.lookup(CONFIG_USER_PASSWORD) + .and_then(|n| n.as_str()) + .ok_or("Could not parse user password")?; let user = User::new(name.to_owned(), password.to_owned()); self.users.push(user); } @@ -180,7 +135,7 @@ impl Config { Ok(()) } - fn parse_mount_points(&mut self, source: &toml::Table) -> Result<(), ConfigError> { + fn parse_mount_points(&mut self, source: &toml::Table) -> Result<()> { let mount_dirs = match source.get(CONFIG_MOUNT_DIRS) { Some(s) => s, None => return Ok(()), @@ -188,31 +143,19 @@ impl Config { let mount_dirs = match mount_dirs { &toml::Value::Array(ref a) => a, - _ => return Err(ConfigError::MountDirsParseError), + _ => bail!("Could not parse mount directories array"), }; for dir in mount_dirs { - let name = match dir.lookup(CONFIG_MOUNT_DIR_NAME) { - None => return Err(ConfigError::MountDirsParseError), - Some(n) => n, - }; - let name = match name.as_str() { - None => return Err(ConfigError::MountDirsParseError), - Some(n) => n, - }; - - let source = match dir.lookup(CONFIG_MOUNT_DIR_SOURCE) { - None => return Err(ConfigError::MountDirsParseError), - Some(n) => n, - }; - let source = match source.as_str() { - None => return Err(ConfigError::MountDirsParseError), - Some(n) => n, - }; + let name = dir.lookup(CONFIG_MOUNT_DIR_NAME) + .and_then(|n| n.as_str()) + .ok_or("Could not parse mount directory name")?; + let source = dir.lookup(CONFIG_MOUNT_DIR_SOURCE) + .and_then(|n| n.as_str()) + .ok_or("Could not parse mount directory source")?; let source = clean_path_string(source); - if self.vfs.mount_points.contains_key(name) { - return Err(ConfigError::ConflictingMounts); + bail!("Conflicting mount directories"); } self.vfs.mount_points.insert(name.to_owned(), source); } @@ -220,25 +163,24 @@ impl Config { Ok(()) } - fn parse_ddns(&mut self, source: &toml::Table) -> Result<(), ConfigError> { + fn parse_ddns(&mut self, source: &toml::Table) -> Result<()> { let ddns = match source.get(CONFIG_DDNS) { Some(s) => s, None => return Ok(()), }; let ddns = match ddns { &toml::Value::Table(ref a) => a, - _ => return Err(ConfigError::DDNSParseError), + _ => bail!("Could not parse DDNS settings table"), }; - let host = try!(ddns.get(CONFIG_DDNS_HOST).ok_or(ConfigError::DDNSParseError)).as_str(); - let username = try!(ddns.get(CONFIG_DDNS_USERNAME).ok_or(ConfigError::DDNSParseError)) - .as_str(); - let password = try!(ddns.get(CONFIG_DDNS_PASSWORD).ok_or(ConfigError::DDNSParseError)) - .as_str(); - - let host = try!(host.ok_or(ConfigError::DDNSParseError)); - let username = try!(username.ok_or(ConfigError::DDNSParseError)); - let password = try!(password.ok_or(ConfigError::DDNSParseError)); + let host = + ddns.get(CONFIG_DDNS_HOST).and_then(|n| n.as_str()).ok_or("Could not parse DDNS host")?; + let username = ddns.get(CONFIG_DDNS_USERNAME) + .and_then(|n| n.as_str()) + .ok_or("Could not parse DDNS username")?; + let password = ddns.get(CONFIG_DDNS_PASSWORD) + .and_then(|n| n.as_str()) + .ok_or("Could not parse DDNS password")?; self.ddns = Some(DDNSConfig { host: host.to_owned(), diff --git a/src/ddns.rs b/src/ddns.rs index db644ae..1f8b5a3 100644 --- a/src/ddns.rs +++ b/src/ddns.rs @@ -37,9 +37,9 @@ const DDNS_UPDATE_URL: &'static str = "http://ydns.io/api/v1/update/"; fn get_my_ip() -> Result { let client = Client::new(); - let mut res = try!(client.get(MY_IP_API_URL).send()); + let mut res = client.get(MY_IP_API_URL).send()?; let mut buf = String::new(); - try!(res.read_to_string(&mut buf)); + res.read_to_string(&mut buf)?; Ok(buf) } @@ -53,7 +53,7 @@ fn update_my_ip(ip: &String, config: &DDNSConfig) -> Result<(), DDNSError> { password: Some(config.password.to_owned()), }); - let res = try!(client.get(full_url.as_str()).header(auth_header).send()); + let res = client.get(full_url.as_str()).header(auth_header).send()?; match res.status { hyper::status::StatusCode::Ok => Ok(()), s => Err(DDNSError::UpdateError(s)), diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index b9f459f..0000000 --- a/src/error.rs +++ /dev/null @@ -1,111 +0,0 @@ -use ape; -use std::error; -use std::fmt; -use std::io; -use id3; -use image; -use lewton; -use metaflac; - -#[derive(Debug)] -pub enum PError { - CannotClearExistingIndex, - PathDecoding, - Io(io::Error), - CacheDirectoryError, - ConfigDirectoryError, - PathNotInVfs, - CannotServeDirectory, - UnsupportedFileType, - AlbumArtSearchError, - ImageProcessingError, - UnsupportedMetadataFormat, - MetadataDecodingError, - Unauthorized, - IncorrectCredentials, -} - -impl From for PError { - fn from(_: ape::Error) -> PError { - PError::MetadataDecodingError - } -} - -impl From for PError { - fn from(err: io::Error) -> PError { - PError::Io(err) - } -} - -impl From for PError { - fn from(_: id3::Error) -> PError { - PError::MetadataDecodingError - } -} - -impl From for PError { - fn from(_: image::ImageError) -> PError { - PError::ImageProcessingError - } -} - -impl From for PError { - fn from(_: lewton::VorbisError) -> PError { - PError::MetadataDecodingError - } -} - -impl From for PError { - fn from(_: metaflac::Error) -> PError { - PError::MetadataDecodingError - } -} - -impl error::Error for PError { - fn description(&self) -> &str { - match *self { - PError::Io(ref err) => err.description(), - PError::CannotClearExistingIndex => "Error while removing existing index", - PError::PathDecoding => "Error while decoding a Path as a UTF-8 string", - PError::CacheDirectoryError => "Could not access the cache directory", - PError::ConfigDirectoryError => "Could not access the config directory", - PError::PathNotInVfs => "Requested path does not index a mount point", - PError::CannotServeDirectory => "Only individual files can be served", - PError::UnsupportedFileType => "Unrecognized extension", - PError::AlbumArtSearchError => "Error while looking for album art", - PError::ImageProcessingError => "Error while processing image", - PError::UnsupportedMetadataFormat => "Unsupported metadata format", - PError::MetadataDecodingError => "Error while reading song metadata", - PError::Unauthorized => "Authentication required", - PError::IncorrectCredentials => "Incorrect username/password combination", - } - } - - fn cause(&self) -> Option<&error::Error> { - match *self { - PError::Io(ref err) => Some(err), - _ => None, - } - } -} - -impl fmt::Display for PError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - PError::Io(ref err) => write!(f, "IO error: {}", err), - PError::CannotClearExistingIndex => write!(f, "Error while removing existing index"), - PError::PathDecoding => write!(f, "Path decoding error"), - PError::CacheDirectoryError => write!(f, "Could not access the cache directory"), - PError::ConfigDirectoryError => write!(f, "Could not access the config directory"), - PError::PathNotInVfs => write!(f, "Requested path does not index a mount point"), - PError::CannotServeDirectory => write!(f, "Only individual files can be served"), - PError::UnsupportedFileType => write!(f, "Unrecognized extension"), - PError::AlbumArtSearchError => write!(f, "Error while looking for album art"), - PError::ImageProcessingError => write!(f, "Error while processing image"), - PError::UnsupportedMetadataFormat => write!(f, "Unsupported metadata format"), - PError::MetadataDecodingError => write!(f, "Error while reading song metadata"), - PError::Unauthorized => write!(f, "Authentication required"), - PError::IncorrectCredentials => write!(f, "Incorrect username/password combination"), - } - } -} diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..1f32a57 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,50 @@ +use ape; +use core; +use id3; +use image; +use hyper; +use iron::IronError; +use iron::status::Status; +use lewton; +use metaflac; +use regex; +use std; + +error_chain! { + foreign_links { + Ape(ape::Error); + Encoding(core::str::Utf8Error); + Flac(metaflac::Error); + Hyper(hyper::Error); + Id3(id3::Error); + Image(image::ImageError); + Io(std::io::Error); + Regex(regex::Error); + Vorbis(lewton::VorbisError); + } + + errors { + AuthenticationRequired {} + MissingUsername {} + MissingPassword {} + IncorrectCredentials {} + CannotServeDirectory {} + UnsupportedFileType {} + } +} + +impl From for IronError { + fn from(err: Error) -> IronError { + match err { + e @ Error(ErrorKind::AuthenticationRequired, _) => { + IronError::new(e, Status::Unauthorized) + } + e @ Error(ErrorKind::MissingUsername, _) => IronError::new(e, Status::BadRequest), + e @ Error(ErrorKind::MissingPassword, _) => IronError::new(e, Status::BadRequest), + e @ Error(ErrorKind::IncorrectCredentials, _) => IronError::new(e, Status::BadRequest), + e @ Error(ErrorKind::CannotServeDirectory, _) => IronError::new(e, Status::BadRequest), + e @ Error(ErrorKind::UnsupportedFileType, _) => IronError::new(e, Status::BadRequest), + e => IronError::new(e, Status::InternalServerError), + } + } +} diff --git a/src/index.rs b/src/index.rs index 36e907b..31892ee 100644 --- a/src/index.rs +++ b/src/index.rs @@ -8,7 +8,7 @@ use std::sync::Arc; use std::thread; use std::time; -use error::*; +use errors::*; use metadata; use vfs::Vfs; @@ -184,7 +184,7 @@ impl<'db> Drop for IndexBuilder<'db> { } impl Index { - pub fn new(vfs: Arc, config: &IndexConfig) -> Result { + pub fn new(vfs: Arc, config: &IndexConfig) -> Result { let path = &config.path; @@ -536,7 +536,7 @@ impl Index { self.select_songs(&mut select).into_iter().map(|s| CollectionFile::Song(s)).collect() } - pub fn browse(&self, virtual_path: &Path) -> Result, PError> { + pub fn browse(&self, virtual_path: &Path) -> Result> { let mut output = Vec::new(); @@ -553,9 +553,9 @@ impl Index { output.push(CollectionFile::Directory(directory)); } - // Browse sub-directory + // Browse sub-directory } else { - let real_path = try!(self.vfs.virtual_to_real(virtual_path)); + let real_path = self.vfs.virtual_to_real(virtual_path)?; let directories = self.browse_directories(real_path.as_path()); let songs = self.browse_songs(real_path.as_path()); output.extend(directories); @@ -565,9 +565,9 @@ impl Index { Ok(output) } - pub fn flatten(&self, virtual_path: &Path) -> Result, PError> { + pub fn flatten(&self, virtual_path: &Path) -> Result> { let db = self.connect(); - let real_path = try!(self.vfs.virtual_to_real(virtual_path)); + let real_path = self.vfs.virtual_to_real(virtual_path)?; let path_string = real_path.to_string_lossy().into_owned() + "%"; let mut select = db.prepare("SELECT path, disc_number, track_number, title, year, album_artist, \ diff --git a/src/main.rs b/src/main.rs index c4191a1..364bcc4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,10 @@ +#![recursion_limit = "128"] + extern crate ape; extern crate app_dirs; extern crate core; +#[macro_use] +extern crate error_chain; extern crate getopts; extern crate hyper; extern crate id3; @@ -30,6 +34,7 @@ extern crate shell32; #[cfg(windows)] extern crate user32; +use errors::*; use getopts::Options; use iron::prelude::*; use mount::Mount; @@ -41,7 +46,7 @@ mod api; mod collection; mod config; mod ddns; -mod error; +mod errors; mod index; mod metadata; mod ui; @@ -50,6 +55,20 @@ mod thumbnails; mod vfs; fn main() { + if let Err(ref e) = run() { + println!("Error: {}", e); + + for e in e.iter().skip(1) { + println!("caused by: {}", e); + } + if let Some(backtrace) = e.backtrace() { + println!("backtrace: {:?}", backtrace); + } + ::std::process::exit(1); + } +} + +fn run() -> Result<()> { // Parse CLI options let args: Vec = std::env::args().collect(); @@ -63,7 +82,7 @@ fn main() { let config_file_path = config_file_name.map(|n| Path::new(n.as_str()).to_path_buf()); // Parse config - let config = config::Config::parse(config_file_path).unwrap(); + let config = config::Config::parse(config_file_path)?; // Init VFS let vfs = Arc::new(vfs::Vfs::new(config.vfs.clone())); @@ -95,7 +114,7 @@ fn main() { let mut mount = Mount::new(); mount.mount("/api/", api_chain); mount.mount("/", Static::new(Path::new("web"))); - let mut server = Iron::new(mount).http(("0.0.0.0", 5050)).unwrap(); + let mut server = Iron::new(mount).http(("0.0.0.0", 5050))?; // Start DDNS updates match config.ddns { @@ -113,4 +132,6 @@ fn main() { println!("Shutting down server"); server.close().unwrap(); + + Ok(()) } diff --git a/src/metadata.rs b/src/metadata.rs index fa46634..cb8f248 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -7,7 +7,7 @@ use regex::Regex; use std::fs; use std::path::Path; -use error::PError; +use errors::*; use utils; use utils::AudioFormat; @@ -22,18 +22,18 @@ pub struct SongTags { pub year: Option, } -pub fn read(path: &Path) -> Result { +pub fn read(path: &Path) -> Result { match utils::get_audio_format(path) { Some(AudioFormat::FLAC) => read_flac(path), Some(AudioFormat::MP3) => read_id3(path), Some(AudioFormat::MPC) => read_ape(path), Some(AudioFormat::OGG) => read_vorbis(path), - _ => Err(PError::UnsupportedMetadataFormat), + _ => bail!("Unsupported file format for reading metadata"), } } -fn read_id3(path: &Path) -> Result { - let tag = try!(id3::Tag::read_from_path(path)); +fn read_id3(path: &Path) -> Result { + let tag = id3::Tag::read_from_path(path)?; let artist = tag.artist().map(|s| s.to_string()); let album_artist = tag.album_artist().map(|s| s.to_string()); @@ -85,8 +85,8 @@ fn read_ape_x_of_y(item: &ape::Item) -> Option { } } -fn read_ape(path: &Path) -> Result { - let tag = try!(ape::read(path)); +fn read_ape(path: &Path) -> Result { + let tag = ape::read(path)?; let artist = tag.item("Artist").and_then(read_ape_string); let album = tag.item("Album").and_then(read_ape_string); let album_artist = tag.item("Album artist").and_then(read_ape_string); @@ -105,10 +105,10 @@ fn read_ape(path: &Path) -> Result { }) } -fn read_vorbis(path: &Path) -> Result { +fn read_vorbis(path: &Path) -> Result { - let file = try!(fs::File::open(path)); - let source = try!(OggStreamReader::new(PacketReader::new(file))); + let file = fs::File::open(path)?; + let source = OggStreamReader::new(PacketReader::new(file))?; let mut tags = SongTags { artist: None, @@ -136,9 +136,9 @@ fn read_vorbis(path: &Path) -> Result { Ok(tags) } -fn read_flac(path: &Path) -> Result { - let tag = try!(metaflac::Tag::read_from_path(path)); - let vorbis = try!(tag.vorbis_comments().ok_or(PError::MetadataDecodingError)); +fn read_flac(path: &Path) -> Result { + let tag = metaflac::Tag::read_from_path(path)?; + let vorbis = tag.vorbis_comments().ok_or("Missing Vorbis comments")?; let disc_number = vorbis.get("DISCNUMBER").and_then(|d| d[0].parse::().ok()); let year = vorbis.get("DATE").and_then(|d| d[0].parse::().ok()); Ok(SongTags { diff --git a/src/thumbnails.rs b/src/thumbnails.rs index 0a9a3f3..56043cb 100644 --- a/src/thumbnails.rs +++ b/src/thumbnails.rs @@ -9,7 +9,7 @@ use std::fs::DirBuilder; use std::hash::{Hash, Hasher}; use std::path::*; -use error::*; +use errors::*; use utils; const THUMBNAILS_PATH: &'static str = "thumbnails"; @@ -22,16 +22,16 @@ fn hash(path: &Path, dimension: u32) -> u64 { hasher.finish() } -pub fn get_thumbnail(real_path: &Path, max_dimension: u32) -> Result { +pub fn get_thumbnail(real_path: &Path, max_dimension: u32) -> Result { - let mut out_path = try!(utils::get_cache_root()); + let mut out_path = utils::get_cache_root()?; out_path.push(THUMBNAILS_PATH); let mut dir_builder = DirBuilder::new(); dir_builder.recursive(true); - try!(dir_builder.create(out_path.as_path())); + dir_builder.create(out_path.as_path())?; - let source_image = try!(image::open(real_path)); + let source_image = image::open(real_path)?; let (source_width, source_height) = source_image.dimensions(); let cropped_dimension = cmp::max(source_width, source_height); let out_dimension = cmp::min(max_dimension, cropped_dimension); @@ -50,13 +50,13 @@ pub fn get_thumbnail(real_path: &Path, max_dimension: u32) -> Result Result { +pub fn get_config_root() -> Result { if let Ok(mut root) = data_root(AppDataType::SharedConfig) { root.push("Polaris"); - return match fs::create_dir_all(&root) { - Ok(()) => Ok(root), - Err(_) => Err(PError::CacheDirectoryError), - }; + fs::create_dir_all(&root)?; + return Ok(root); } - Err(PError::ConfigDirectoryError) + bail!("Could not retrieve config directory root"); } -pub fn get_cache_root() -> Result { +pub fn get_cache_root() -> Result { if let Ok(mut root) = data_root(AppDataType::SharedData) { root.push("Polaris"); - return match fs::create_dir_all(&root) { - Ok(()) => Ok(root), - Err(_) => Err(PError::CacheDirectoryError), - }; + fs::create_dir_all(&root)?; + return Ok(root); } - Err(PError::CacheDirectoryError) + bail!("Could not retrieve cache directory root"); } #[derive(Debug, PartialEq)] diff --git a/src/vfs.rs b/src/vfs.rs index 4b4e6d7..ce1af22 100644 --- a/src/vfs.rs +++ b/src/vfs.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::path::PathBuf; use std::path::Path; -use error::*; +use errors::*; #[derive(Debug, Clone)] pub struct VfsConfig { @@ -24,7 +24,7 @@ impl Vfs { Vfs { mount_points: config.mount_points } } - pub fn real_to_virtual(&self, real_path: &Path) -> Result { + pub fn real_to_virtual(&self, real_path: &Path) -> Result { for (name, target) in &self.mount_points { match real_path.strip_prefix(target) { Ok(p) => { @@ -34,10 +34,10 @@ impl Vfs { Err(_) => (), } } - Err(PError::PathNotInVfs) + bail!("Real path has no match in VFS") } - pub fn virtual_to_real(&self, virtual_path: &Path) -> Result { + pub fn virtual_to_real(&self, virtual_path: &Path) -> Result { for (name, target) in &self.mount_points { let mount_path = Path::new(&name); match virtual_path.strip_prefix(mount_path) { @@ -51,7 +51,7 @@ impl Vfs { Err(_) => (), } } - Err(PError::PathNotInVfs) + bail!("Virtual path has no match in VFS") } pub fn get_mount_points(&self) -> &HashMap {