Modernized error handling

- Use error-chain instead of writing tons of boilerplate
- Switched try!() macros to '?'
This commit is contained in:
Antoine Gersant 2016-12-03 12:08:55 -08:00
parent 15505f8991
commit ec8a8da81e
14 changed files with 249 additions and 317 deletions

57
Cargo.lock generated
View file

@ -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)" = "<none>"
"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"

View file

@ -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"

View file

@ -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<PError> 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<Collection>) -> Mount {
let mut api_handler = Mount::new();
@ -97,20 +75,18 @@ pub fn get_api_handler(collection: Arc<Collection>) -> Mount {
api_handler
}
fn path_from_request(request: &Request) -> Result<PathBuf, Utf8Error> {
fn path_from_request(request: &Request) -> Result<PathBuf> {
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<Response> {
fn auth(request: &mut Request, collection: &Collection) -> IronResult<Response> {
let input = request.get_ref::<params::Params>().unwrap();
let username = match input.find(&["username"]) {
Some(&params::Value::String(ref username)) => username,
_ => return Err(IronError::from(PError::IncorrectCredentials)),
let username = match input.find(&["username"]) {
Some(&params::Value::String(ref username)) => username,
_ => return Err(Error::from(ErrorKind::MissingUsername).into()),
};
let password = match input.find(&["password"]) {
Some(&params::Value::String(ref password)) => password,
_ => return Err(IronError::from(PError::IncorrectCredentials)),
let password = match input.find(&["password"]) {
Some(&params::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>
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<Response
Err(e) => 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<Respons
Err(e) => 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<Response>
};
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<Response>
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<Response> {

View file

@ -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<Vec<CollectionFile>, PError> {
pub fn browse(&self, virtual_path: &Path) -> Result<Vec<CollectionFile>> {
self.index.deref().browse(virtual_path)
}
pub fn flatten(&self, virtual_path: &Path) -> Result<Vec<Song>, PError> {
pub fn flatten(&self, virtual_path: &Path) -> Result<Vec<Song>> {
self.index.deref().flatten(virtual_path)
}
pub fn locate(&self, virtual_path: &Path) -> Result<PathBuf, PError> {
pub fn locate(&self, virtual_path: &Path) -> Result<PathBuf> {
self.vfs.virtual_to_real(virtual_path)
}
}

View file

@ -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<io::Error> for ConfigError {
fn from(err: io::Error) -> ConfigError {
ConfigError::IoError(err)
}
}
impl From<regex::Error> 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<path::PathBuf>) -> Result<Config, ConfigError> {
pub fn parse(custom_path: Option<path::PathBuf>) -> Result<Config> {
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(),

View file

@ -37,9 +37,9 @@ const DDNS_UPDATE_URL: &'static str = "http://ydns.io/api/v1/update/";
fn get_my_ip() -> Result<String, DDNSError> {
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)),

View file

@ -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<ape::Error> for PError {
fn from(_: ape::Error) -> PError {
PError::MetadataDecodingError
}
}
impl From<io::Error> for PError {
fn from(err: io::Error) -> PError {
PError::Io(err)
}
}
impl From<id3::Error> for PError {
fn from(_: id3::Error) -> PError {
PError::MetadataDecodingError
}
}
impl From<image::ImageError> for PError {
fn from(_: image::ImageError) -> PError {
PError::ImageProcessingError
}
}
impl From<lewton::VorbisError> for PError {
fn from(_: lewton::VorbisError) -> PError {
PError::MetadataDecodingError
}
}
impl From<metaflac::Error> 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"),
}
}
}

50
src/errors.rs Normal file
View file

@ -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<Error> 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),
}
}
}

View file

@ -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<Vfs>, config: &IndexConfig) -> Result<Index, PError> {
pub fn new(vfs: Arc<Vfs>, config: &IndexConfig) -> Result<Index> {
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<Vec<CollectionFile>, PError> {
pub fn browse(&self, virtual_path: &Path) -> Result<Vec<CollectionFile>> {
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<Vec<Song>, PError> {
pub fn flatten(&self, virtual_path: &Path) -> Result<Vec<Song>> {
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, \

View file

@ -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<String> = 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(())
}

View file

@ -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<i32>,
}
pub fn read(path: &Path) -> Result<SongTags, PError> {
pub fn read(path: &Path) -> Result<SongTags> {
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<SongTags, PError> {
let tag = try!(id3::Tag::read_from_path(path));
fn read_id3(path: &Path) -> Result<SongTags> {
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<u32> {
}
}
fn read_ape(path: &Path) -> Result<SongTags, PError> {
let tag = try!(ape::read(path));
fn read_ape(path: &Path) -> Result<SongTags> {
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<SongTags, PError> {
})
}
fn read_vorbis(path: &Path) -> Result<SongTags, PError> {
fn read_vorbis(path: &Path) -> Result<SongTags> {
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<SongTags, PError> {
Ok(tags)
}
fn read_flac(path: &Path) -> Result<SongTags, PError> {
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<SongTags> {
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::<u32>().ok());
let year = vorbis.get("DATE").and_then(|d| d[0].parse::<i32>().ok());
Ok(SongTags {

View file

@ -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<PathBuf, PError> {
pub fn get_thumbnail(real_path: &Path, max_dimension: u32) -> Result<PathBuf> {
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<PathBuf, PE
out_dimension,
out_dimension,
FilterType::Lanczos3);
try!(out_image.save(out_path.as_path()));
out_image.save(out_path.as_path())?;
} else {
let out_image = resize(&source_image,
out_dimension,
out_dimension,
FilterType::Lanczos3);
try!(out_image.save(out_path.as_path()));
out_image.save(out_path.as_path())?;
}
}

View file

@ -2,28 +2,24 @@ use app_dirs::{AppDataType, data_root};
use std::path::{Path, PathBuf};
use std::fs;
use error::PError;
use errors::*;
pub fn get_config_root() -> Result<PathBuf, PError> {
pub fn get_config_root() -> Result<PathBuf> {
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<PathBuf, PError> {
pub fn get_cache_root() -> Result<PathBuf> {
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)]

View file

@ -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<PathBuf, PError> {
pub fn real_to_virtual(&self, real_path: &Path) -> Result<PathBuf> {
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<PathBuf, PError> {
pub fn virtual_to_real(&self, virtual_path: &Path) -> Result<PathBuf> {
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<String, PathBuf> {