diff options
| author | Nathan Jaremko | 2018-05-13 23:11:49 -0400 |
|---|---|---|
| committer | Nathan Jaremko | 2018-05-13 23:11:49 -0400 |
| commit | c449861e1940b9f32569a2b75504244742e3dae0 (patch) | |
| tree | c706f095e6f31a1910fc84e4997bda18877329a1 /src | |
| parent | 8c72734e1abbccbeee833270121c2b86500a81d8 (diff) | |
| download | podcast-c449861e1940b9f32569a2b75504244742e3dae0.tar.bz2 | |
Improve error handling
Diffstat (limited to 'src')
| -rw-r--r-- | src/actions.rs | 338 | ||||
| -rw-r--r-- | src/main.rs | 96 | ||||
| -rw-r--r-- | src/structs.rs | 168 | ||||
| -rw-r--r-- | src/utils.rs | 121 |
4 files changed, 386 insertions, 337 deletions
diff --git a/src/actions.rs b/src/actions.rs index 3c1a6e9..8adea4c 100644 --- a/src/actions.rs +++ b/src/actions.rs @@ -6,25 +6,31 @@ use std::fs::{self, DirBuilder, File}; use std::io::{self, BufReader, Read, Write}; use std::process::Command; +use errors::*; use rayon::prelude::*; use regex::Regex; use reqwest; use rss::Channel; +use std::path::PathBuf; use toml; -pub fn list_episodes(search: &str) { +pub fn list_episodes(search: &str) -> Result<()> { let stdout = io::stdout(); let mut handle = stdout.lock(); - let re = Regex::new(&format!("(?i){}", &search)).expect("Failed to parse regex"); - let mut path = get_podcast_dir(); + let re = Regex::new(&format!("(?i){}", &search)).chain_err(|| UNABLE_TO_PARSE_REGEX)?; + let mut path = get_podcast_dir()?; path.push(".rss"); - DirBuilder::new().recursive(true).create(&path).unwrap(); - for entry in fs::read_dir(&path).unwrap() { - let entry = entry.unwrap(); + DirBuilder::new() + .recursive(true) + .create(&path) + .chain_err(|| UNABLE_TO_CREATE_DIRECTORY)?; + for entry in fs::read_dir(&path).chain_err(|| UNABLE_TO_READ_DIRECTORY)? { + let entry = entry.chain_err(|| UNABLE_TO_READ_ENTRY)?; if re.is_match(&entry.file_name().into_string().unwrap()) { - let file = File::open(&entry.path()).unwrap(); - let channel = Channel::read_from(BufReader::new(file)).unwrap(); + let file = File::open(&entry.path()).chain_err(|| UNABLE_TO_OPEN_FILE)?; + let channel = Channel::read_from(BufReader::new(file)) + .chain_err(|| UNABLE_TO_CREATE_CHANNEL_FROM_FILE)?; let podcast = Podcast::from(channel); let episodes = podcast.episodes(); for (num, ep) in episodes.iter().enumerate() { @@ -32,200 +38,217 @@ pub fn list_episodes(search: &str) { &mut handle, "({}) {}\n", episodes.len() - num, - ep.title().unwrap() - ).is_ok(); + ep.title().chain_err(|| "unable to retrieve episode title")? + ).chain_err(|| "unable to write to stdout")? } - return; + return Ok(()); } } + Ok(()) } -pub fn subscribe_rss(url: &str) { +pub fn subscribe_rss(url: &str) -> Result<Channel> { println!("Downloading RSS feed..."); - if let Err(err) = download_rss_feed(url) { - eprintln!("Error: {}", err); - } + download_rss_feed(url) } -pub fn download_rss(config: &Config, url: &str) { +pub fn download_rss(config: &Config, url: &str) -> Result<()> { println!("Downloading episode(s)..."); - match download_rss_feed(url) { - Ok(channel) => { - let download_limit = config.auto_download_limit as usize; - if download_limit > 0 { - let podcast = Podcast::from(channel); - let episodes = podcast.episodes(); - episodes[..download_limit].par_iter().for_each(|ep| { - if let Err(err) = ep.download(podcast.title()) { - eprintln!("Error downloading {}: {}", podcast.title(), err); - } - }); + let channel = download_rss_feed(url)?; + let download_limit = config.auto_download_limit as usize; + if download_limit > 0 { + let podcast = Podcast::from(channel); + let episodes = podcast.episodes(); + episodes[..download_limit].par_iter().for_each(|ep| { + if let Err(err) = ep.download(podcast.title()) { + eprintln!("Error downloading {}: {}", podcast.title(), err); } - } - Err(err) => eprintln!("Error: {}", err), + }); } + Ok(()) +} + +pub fn update_subscription(sub: &mut Subscription) -> Result<()> { + let mut path: PathBuf = get_podcast_dir()?; + path.push(&sub.title); + DirBuilder::new() + .recursive(true) + .create(&path) + .chain_err(|| UNABLE_TO_CREATE_DIRECTORY)?; + + let mut titles = HashSet::new(); + for entry in fs::read_dir(&path).chain_err(|| UNABLE_TO_READ_DIRECTORY)? { + let unwrapped_entry = &entry.chain_err(|| UNABLE_TO_READ_ENTRY)?; + titles.insert(trim_extension(&unwrapped_entry + .file_name() + .into_string() + .unwrap())); + } + + let mut resp = reqwest::get(&sub.url).chain_err(|| UNABLE_TO_GET_HTTP_RESPONSE)?; + let mut content: Vec<u8> = Vec::new(); + resp.read_to_end(&mut content) + .chain_err(|| UNABLE_TO_READ_RESPONSE_TO_END)?; + let podcast = Podcast::from(Channel::read_from(BufReader::new(&content[..])) + .chain_err(|| UNABLE_TO_CREATE_CHANNEL_FROM_RESPONSE)?); + path = get_podcast_dir()?; + path.push(".rss"); + + let mut filename = String::from(podcast.title()); + filename.push_str(".xml"); + path.push(&filename); + let mut file = File::create(&path).unwrap(); + file.write_all(&content).unwrap(); + + if podcast.episodes().len() > sub.num_episodes { + podcast.episodes()[..podcast.episodes().len() - sub.num_episodes] + .par_iter() + .for_each(|ep: &Episode| { + if let Err(err) = ep.download(podcast.title()) { + eprintln!("Error downloading {}: {}", podcast.title(), err); + } + }); + } + sub.num_episodes = podcast.episodes().len(); + Ok(()) } pub fn update_rss(state: &mut State) { println!("Checking for new episodes..."); - state.subscriptions.par_iter_mut().for_each(|sub| { - let mut path = get_podcast_dir(); - path.push(&sub.title); - DirBuilder::new().recursive(true).create(&path).unwrap(); - - let mut titles = HashSet::new(); - for entry in fs::read_dir(&path).unwrap() { - let entry = entry.unwrap(); - titles.insert(trim_extension(&entry.file_name().into_string().unwrap())); - } - - let mut resp = reqwest::get(&sub.url).unwrap(); - let mut content: Vec<u8> = Vec::new(); - resp.read_to_end(&mut content).unwrap(); - let podcast = Podcast::from(Channel::read_from(BufReader::new(&content[..])).unwrap()); - path = get_podcast_dir(); - path.push(".rss"); - - let mut filename = String::from(podcast.title()); - filename.push_str(".xml"); - path.push(&filename); - let mut file = File::create(&path).unwrap(); - file.write_all(&content).unwrap(); - - if podcast.episodes().len() > sub.num_episodes { - podcast.episodes()[..podcast.episodes().len() - sub.num_episodes] - .par_iter() - .for_each(|ep| { - if let Err(err) = ep.download(podcast.title()) { - eprintln!("Error downloading {}: {}", podcast.title(), err); - } - }); - } - sub.num_episodes = podcast.episodes().len(); - }); + let _result: Vec<Result<()>> = state + .subscriptions + .par_iter_mut() + .map(|sub: &mut Subscription| update_subscription(sub)) + .collect(); } -pub fn list_subscriptions(state: &State) { +pub fn list_subscriptions(state: &State) -> Result<()> { let stdout = io::stdout(); let mut handle = stdout.lock(); for podcast in &state.subscriptions() { - write!(&mut handle, "{}\n", &podcast.title).is_ok(); + write!(&mut handle, "{}\n", &podcast.title).chain_err(|| "unable to write to stdout")?; } + Ok(()) } -pub fn download_range(state: &State, p_search: &str, e_search: &str) { - let re_pod = Regex::new(&format!("(?i){}", &p_search)).expect("Failed to parse regex"); +pub fn download_range(state: &State, p_search: &str, e_search: &str) -> Result<()> { + let re_pod = Regex::new(&format!("(?i){}", &p_search)).chain_err(|| UNABLE_TO_PARSE_REGEX)?; for subscription in &state.subscriptions { if re_pod.is_match(&subscription.title) { - match Podcast::from_title(&subscription.title) { - Ok(podcast) => match parse_download_episodes(e_search) { - Ok(episodes_to_download) => { - if let Err(err) = podcast.download_specific(&episodes_to_download) { - eprintln!("Error: {}", err); - } - } - Err(err) => eprintln!("Error: {}", err), - }, - Err(err) => eprintln!("Error: {}", err), - } + let podcast = Podcast::from_title(&subscription.title) + .chain_err(|| UNABLE_TO_RETRIEVE_PODCAST_BY_TITLE)?; + let episodes_to_download = parse_download_episodes(e_search) + .chain_err(|| "unable to parse episodes to download")?; + podcast + .download_specific(&episodes_to_download) + .chain_err(|| "unable to download episodes")?; } } + Ok(()) } -pub fn download_episode(state: &State, p_search: &str, e_search: &str) { - let re_pod = Regex::new(&format!("(?i){}", &p_search)).expect("Failed to parse regex"); - let ep_num = e_search.parse::<usize>().unwrap(); +pub fn download_episode(state: &State, p_search: &str, e_search: &str) -> Result<()> { + let re_pod = Regex::new(&format!("(?i){}", &p_search)).chain_err(|| UNABLE_TO_PARSE_REGEX)?; + let ep_num = e_search + .parse::<usize>() + .chain_err(|| "unable to parse number")?; for subscription in &state.subscriptions { if re_pod.is_match(&subscription.title) { - match Podcast::from_title(&subscription.title) { - Ok(podcast) => { - let episodes = podcast.episodes(); - if let Err(err) = episodes[episodes.len() - ep_num].download(podcast.title()) { - eprintln!("{}", err); - } - } - Err(err) => eprintln!("Error: {}", err), - } + let podcast = Podcast::from_title(&subscription.title) + .chain_err(|| UNABLE_TO_RETRIEVE_PODCAST_BY_TITLE)?; + let episodes = podcast.episodes(); + episodes[episodes.len() - ep_num] + .download(podcast.title()) + .chain_err(|| "unable to download episode")?; } } + Ok(()) } -pub fn download_all(state: &State, p_search: &str) { - let re_pod = Regex::new(&format!("(?i){}", &p_search)).expect("Failed to parse regex"); +pub fn download_all(state: &State, p_search: &str) -> Result<()> { + let re_pod = Regex::new(&format!("(?i){}", &p_search)).chain_err(|| UNABLE_TO_PARSE_REGEX)?; for subscription in &state.subscriptions { if re_pod.is_match(&subscription.title) { - match Podcast::from_title(&subscription.title) { - Ok(podcast) => if let Err(err) = podcast.download() { - eprintln!("{}", err); - }, - Err(err) => eprintln!("Error: {}", err), - } + let podcast = Podcast::from_title(&subscription.title) + .chain_err(|| UNABLE_TO_RETRIEVE_PODCAST_BY_TITLE)?; + podcast + .download() + .chain_err(|| "unable to download podcast")?; } } + Ok(()) } -pub fn play_latest(state: &State, p_search: &str) { - let re_pod = Regex::new(&format!("(?i){}", &p_search)).expect("Failed to parse regex"); - let mut path = get_xml_dir(); - if let Err(err) = DirBuilder::new().recursive(true).create(&path) { - eprintln!( - "Couldn't create directory: {}\nReason: {}", - path.to_str().unwrap(), - err - ); - return; - } +pub fn play_latest(state: &State, p_search: &str) -> Result<()> { + let re_pod: Regex = + Regex::new(&format!("(?i){}", &p_search)).chain_err(|| UNABLE_TO_PARSE_REGEX)?; + let mut path: PathBuf = get_xml_dir()?; + DirBuilder::new() + .recursive(true) + .create(&path) + .chain_err(|| UNABLE_TO_CREATE_DIRECTORY)?; for subscription in &state.subscriptions { if re_pod.is_match(&subscription.title) { - let mut filename = subscription.title.clone(); + let mut filename: String = subscription.title.clone(); filename.push_str(".xml"); path.push(filename); - let mut file = File::open(&path).unwrap(); + let mut file: File = File::open(&path).chain_err(|| UNABLE_TO_OPEN_FILE)?; let mut content: Vec<u8> = Vec::new(); - file.read_to_end(&mut content).unwrap(); + file.read_to_end(&mut content) + .chain_err(|| "unable to read file to end")?; - let podcast = Podcast::from(Channel::read_from(content.as_slice()).unwrap()); + let podcast: Podcast = Podcast::from(Channel::read_from(content.as_slice()) + .chain_err(|| UNABLE_TO_CREATE_CHANNEL_FROM_FILE)?); let episodes = podcast.episodes(); let episode = episodes[0].clone(); - filename = String::from(episode.title().unwrap()); - filename.push_str(episode.extension().unwrap()); - path = get_podcast_dir(); + filename = String::from(episode + .title() + .chain_err(|| "unable to retrieve episode name")?); + filename.push_str(episode + .extension() + .chain_err(|| "unable to retrieve episode extension")?); + path = get_podcast_dir()?; path.push(podcast.title()); path.push(filename); if path.exists() { - launch_player(path.to_str().unwrap()); + launch_player(path.to_str() + .chain_err(|| "unable to convert path to &str")?)?; } else { - launch_player(episode.url().unwrap()); + launch_player(episode + .url() + .chain_err(|| "unable to retrieve episode url")?)?; } - return; + return Ok(()); } } + Ok(()) } -pub fn play_episode(state: &State, p_search: &str, ep_num_string: &str) { - let re_pod = Regex::new(&format!("(?i){}", &p_search)).expect("Failed to parse regex"); - let ep_num = ep_num_string.parse::<usize>().unwrap(); - let mut path = get_xml_dir(); +pub fn play_episode(state: &State, p_search: &str, ep_num_string: &str) -> Result<()> { + let re_pod: Regex = + Regex::new(&format!("(?i){}", &p_search)).chain_err(|| UNABLE_TO_PARSE_REGEX)?; + let ep_num: usize = ep_num_string.parse::<usize>().unwrap(); + let mut path: PathBuf = get_xml_dir()?; if let Err(err) = DirBuilder::new().recursive(true).create(&path) { eprintln!( "Couldn't create directory: {}\nReason: {}", path.to_str().unwrap(), err ); - return; + return Ok(()); } for subscription in &state.subscriptions { if re_pod.is_match(&subscription.title) { - let mut filename = subscription.title.clone(); + let mut filename: String = subscription.title.clone(); filename.push_str(".xml"); path.push(filename); - let mut file = File::open(&path).unwrap(); + let mut file: File = File::open(&path).unwrap(); let mut content: Vec<u8> = Vec::new(); file.read_to_end(&mut content).unwrap(); @@ -235,46 +258,48 @@ pub fn play_episode(state: &State, p_search: &str, ep_num_string: &str) { filename = String::from(episode.title().unwrap()); filename.push_str(episode.extension().unwrap()); - path = get_podcast_dir(); + path = get_podcast_dir()?; path.push(podcast.title()); path.push(filename); if path.exists() { - launch_player(path.to_str().unwrap()); + launch_player(path.to_str().chain_err(|| UNABLE_TO_CONVERT_TO_STR)?)?; } else { - launch_player(episode.url().unwrap()); + launch_player(episode.url().chain_err(|| "unable to retrieve episode url")?)?; } - return; + return Ok(()); } } + Ok(()) } -pub fn check_for_update(version: &str) { +pub fn check_for_update(version: &str) -> Result<()> { println!("Checking for updates..."); let resp: String = reqwest::get( "https://raw.githubusercontent.com/njaremko/podcast/master/Cargo.toml", - ).unwrap() + ).chain_err(|| UNABLE_TO_GET_HTTP_RESPONSE)? .text() - .unwrap(); + .chain_err(|| "unable to convert response to text")?; //println!("{}", resp); - match resp.parse::<toml::Value>() { - Ok(config) => { - let latest = config["package"]["version"].as_str().unwrap(); - if version != latest { - println!("New version avaliable: {} -> {}", version, latest); - } - } - Err(err) => eprintln!("{}", err), + let config = resp.parse::<toml::Value>() + .chain_err(|| "unable to parse toml")?; + let latest = config["package"]["version"] + .as_str() + .chain_err(|| UNABLE_TO_CONVERT_TO_STR)?; + if version != latest { + println!("New version avaliable: {} -> {}", version, latest); } + Ok(()) } -fn launch_player(url: &str) { +fn launch_player(url: &str) -> Result<()> { if launch_mpv(url).is_err() { - launch_vlc(url) + return launch_vlc(url); } + Ok(()) } -fn launch_mpv(url: &str) -> io::Result<()> { +fn launch_mpv(url: &str) -> Result<()> { if let Err(err) = Command::new("mpv") .args(&["--audio-display=no", "--ytdl=no", url]) .status() @@ -282,7 +307,6 @@ fn launch_mpv(url: &str) -> io::Result<()> { match err.kind() { io::ErrorKind::NotFound => { eprintln!("Couldn't open mpv\nTrying vlc..."); - return Err(err); } _ => eprintln!("Error: {}", err), } @@ -290,39 +314,33 @@ fn launch_mpv(url: &str) -> io::Result<()> { Ok(()) } -fn launch_vlc(url: &str) { +fn launch_vlc(url: &str) -> Result<()> { if let Err(err) = Command::new("vlc").args(&["-I ncurses", url]).status() { match err.kind() { io::ErrorKind::NotFound => { - eprintln!("vlc not found in PATH\nAborting..."); + eprintln!("Couldn't open vlc...aborting"); } _ => eprintln!("Error: {}", err), } } + Ok(()) } -pub fn remove_podcast(state: &mut State, p_search: &str) { +pub fn remove_podcast(state: &mut State, p_search: &str) -> Result<()> { if p_search == "*" { - match Podcast::delete_all() { - Ok(_) => println!("Success"), - Err(err) => eprintln!("Error: {}", err), - } - return; + return Podcast::delete_all(); } - let re_pod = Regex::new(&format!("(?i){}", &p_search)).expect("Failed to parse regex"); + let re_pod = Regex::new(&format!("(?i){}", &p_search)).chain_err(|| UNABLE_TO_PARSE_REGEX)?; for subscription in 0..state.subscriptions.len() { let title = state.subscriptions[subscription].title.clone(); if re_pod.is_match(&title) { state.subscriptions.remove(subscription); - match Podcast::delete(&title) { - Ok(_) => println!("Success"), - Err(err) => eprintln!("Error: {}", err), - } - break; + Podcast::delete(&title)?; } } + Ok(()) } pub fn print_completion(arg: &str) { diff --git a/src/main.rs b/src/main.rs index 68c3193..35498df 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,9 @@ +#![recursion_limit = "1024"] + extern crate chrono; extern crate clap; +#[macro_use] +extern crate error_chain; extern crate rayon; extern crate regex; extern crate reqwest; @@ -13,28 +17,24 @@ extern crate yaml_rust; mod actions; mod structs; mod utils; +mod errors { + // Create the Error, ErrorKind, ResultExt, and Result types + error_chain!{} +} use actions::*; -use utils::*; +use errors::*; use structs::*; +use utils::*; use clap::{App, Arg, SubCommand}; -const VERSION: &str = "0.5.3"; +const VERSION: &str = "0.5.4"; -fn main() { - if let Err(err) = create_directories() { - eprintln!("{}", err); - return; - } - let mut state = match State::new(VERSION) { - Ok(val) => val, - Err(err) => { - eprintln!("{}", err); - return; - } - }; - let config = Config::new(); +fn main() -> Result<()> { + create_directories().chain_err(|| "unable to create directories")?; + let mut state = State::new(VERSION).chain_err(|| "unable to load state")?; + let config = Config::new()?; let matches = App::new("podcast") .version(VERSION) .author("Nathan J. <njaremko@gmail.com>") @@ -129,65 +129,77 @@ fn main() { match matches.subcommand_name() { Some("download") => { - let download_matches = matches.subcommand_matches("download").unwrap(); - let podcast = download_matches.value_of("PODCAST").unwrap(); + let download_matches = matches + .subcommand_matches("download") + .chain_err(|| "unable to find subcommand matches")?; + let podcast = download_matches + .value_of("PODCAST") + .chain_err(|| "unable to find subcommand match")?; match download_matches.value_of("EPISODE") { Some(ep) => if String::from(ep).contains(|c| c == '-' || c == ',') { - download_range(&state, podcast, ep) + download_range(&state, podcast, ep)? } else { - download_episode(&state, podcast, ep) + download_episode(&state, podcast, ep)? }, - None => download_all(&state, podcast), + None => download_all(&state, podcast)?, } } Some("ls") | Some("list") => { let list_matches = matches .subcommand_matches("ls") .or(matches.subcommand_matches("list")) - .unwrap(); + .chain_err(|| "unable to find subcommand matches")?; match list_matches.value_of("PODCAST") { - Some(regex) => list_episodes(regex), - None => list_subscriptions(&state), + Some(regex) => list_episodes(regex)?, + None => list_subscriptions(&state)?, } } Some("play") => { - let play_matches = matches.subcommand_matches("play").unwrap(); - let podcast = play_matches.value_of("PODCAST").unwrap(); + let play_matches = matches + .subcommand_matches("play") + .chain_err(|| "unable to find subcommand matches")?; + let podcast = play_matches + .value_of("PODCAST") + .chain_err(|| "unable to find subcommand match")?; match play_matches.value_of("EPISODE") { - Some(episode) => play_episode(&state, podcast, episode), - None => play_latest(&state, podcast), + Some(episode) => play_episode(&state, podcast, episode)?, + None => play_latest(&state, podcast)?, } } Some("subscribe") => { - let subscribe_matches = matches.subcommand_matches("subscribe").unwrap(); - let url = subscribe_matches.value_of("URL").unwrap(); - state.subscribe(url); + let subscribe_matches = matches + .subcommand_matches("subscribe") + .chain_err(|| "unable to find subcommand matches")?; + let url = subscribe_matches + .value_of("URL") + .chain_err(|| "unable to find subcommand match")?; + state.subscribe(url).chain_err(|| "unable to subscribe")?; if subscribe_matches.occurrences_of("download") > 0 { - download_rss(&config, url); + download_rss(&config, url)?; } else { - subscribe_rss(url); + subscribe_rss(url)?; } } Some("search") => println!("This feature is coming soon..."), Some("rm") => { - let rm_matches = matches.subcommand_matches("rm").unwrap(); - match rm_matches.value_of("PODCAST") { - Some(regex) => remove_podcast(&mut state, regex), - None => println!(), - } + let rm_matches = matches + .subcommand_matches("rm") + .chain_err(|| "unable to find subcommand matches")?; + let regex = rm_matches.value_of("PODCAST").chain_err(|| "")?; + remove_podcast(&mut state, regex)? } Some("completion") => { - let matches = matches.subcommand_matches("completion").unwrap(); + let matches = matches + .subcommand_matches("completion") + .chain_err(|| "unable to find subcommand matches")?; match matches.value_of("SHELL") { Some(shell) => print_completion(shell), None => print_completion(""), } } Some("refresh") => update_rss(&mut state), - Some("update") => check_for_update(VERSION), + Some("update") => check_for_update(VERSION)?, _ => (), } - if let Err(err) = state.save() { - eprintln!("{}", err); - } + state.save().chain_err(|| "unable to save state") } diff --git a/src/structs.rs b/src/structs.rs index ac6832e..03a9932 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -1,14 +1,15 @@ use actions::*; +use errors::*; use utils::*; use std::collections::BTreeSet; -use std::fs::{self, remove_dir_all, remove_file, DirBuilder, File}; +use std::fs::{self, DirBuilder, File}; use std::io::{self, BufReader, Read, Write}; use chrono::prelude::*; use rayon::prelude::*; use reqwest; -use rss::{self, Channel, Item}; +use rss::{Channel, Item}; use serde_json; use yaml_rust::YamlLoader; @@ -17,14 +18,18 @@ pub struct Config { } impl Config { - pub fn new() -> Config { - let mut path = get_podcast_dir(); + pub fn new() -> Result<Config> { + let mut path = get_podcast_dir()?; let mut download_limit = 1; path.push(".config"); if path.exists() { let mut s = String::new(); - File::open(&path).unwrap().read_to_string(&mut s).unwrap(); - let config = YamlLoader::load_from_str(&s).unwrap(); + File::open(&path) + .chain_err(|| UNABLE_TO_OPEN_FILE)? + .read_to_string(&mut s) + .chain_err(|| UNABLE_TO_READ_FILE_TO_STRING)?; + let config = + YamlLoader::load_from_str(&s).chain_err(|| "unable to load yaml from string")?; if !config.is_empty() { let doc = &config[0]; if let Some(val) = doc["auto_download_limit"].as_i64() { @@ -32,12 +37,13 @@ impl Config { } } } else { - let mut file = File::create(&path).unwrap(); - file.write_all(b"auto_download_limit: 1").unwrap(); + let mut file = File::create(&path).chain_err(|| UNABLE_TO_CREATE_FILE)?; + file.write_all(b"auto_download_limit: 1") + .chain_err(|| UNABLE_TO_WRITE_FILE)?; } - Config { + Ok(Config { auto_download_limit: download_limit, - } + }) } } @@ -56,29 +62,27 @@ pub struct State { } impl State { - pub fn new(version: &str) -> Result<State, String> { - let mut path = get_podcast_dir(); + pub fn new(version: &str) -> Result<State> { + let mut path = get_podcast_dir()?; path.push(".subscriptions"); if path.exists() { let mut s = String::new(); - let mut file = match File::open(&path) { - Ok(val) => val, - Err(err) => return Err(format!("{}", err)), - }; - if let Err(err) = file.read_to_string(&mut s) { - return Err(format!("{}", err)); - }; + let mut file = File::open(&path).chain_err(|| UNABLE_TO_OPEN_FILE)?; + file.read_to_string(&mut s) + .chain_err(|| UNABLE_TO_READ_FILE_TO_STRING)?; let mut state: State = match serde_json::from_str(&s) { Ok(val) => val, // This will happen if the struct has changed between versions Err(_) => { - let v: serde_json::Value = serde_json::from_str(&s).unwrap(); + let v: serde_json::Value = + serde_json::from_str(&s).chain_err(|| "unable to read json from string")?; State { version: String::from(version), last_run_time: Utc::now(), subscriptions: match serde_json::from_value(v["subscriptions"].clone()) { Ok(val) => val, - Err(_) => serde_json::from_value(v["subs"].clone()).unwrap(), + Err(_) => serde_json::from_value(v["subs"].clone()) + .chain_err(|| "unable to parse value from json")?, }, } } @@ -90,12 +94,10 @@ impl State { .num_seconds() > 86400 { update_rss(&mut state); - check_for_update(&state.version); + check_for_update(&state.version)?; } state.last_run_time = Utc::now(); - if let Err(err) = state.save() { - eprintln!("{}", err); - } + state.save()?; Ok(state) } else { Ok(State { @@ -106,7 +108,7 @@ impl State { } } - pub fn subscribe(&mut self, url: &str) { + pub fn subscribe(&mut self, url: &str) -> Result<()> { let mut set = BTreeSet::new(); for sub in self.subscriptions() { set.insert(sub.title); @@ -119,22 +121,21 @@ impl State { num_episodes: podcast.episodes().len(), }); } - if let Err(err) = self.save() { - eprintln!("{}", err); - } + self.save() } pub fn subscriptions(&self) -> Vec<Subscription> { self.subscriptions.clone() } - pub fn save(&self) -> Result<(), io::Error> { - let mut path = get_podcast_dir(); + pub fn save(&self) -> Result<()> { + let mut path = get_podcast_dir()?; path.push(".subscriptions.tmp"); - let serialized = serde_json::to_string(self)?; - let mut file = File::create(&path)?; - file.write_all(serialized.as_bytes())?; - fs::rename(&path, get_sub_file())?; + let serialized = serde_json::to_string(self).chain_err(|| "unable to serialize state")?; + let mut file = File::create(&path).chain_err(|| UNABLE_TO_CREATE_FILE)?; + file.write_all(serialized.as_bytes()) + .chain_err(|| UNABLE_TO_WRITE_FILE)?; + fs::rename(&path, get_sub_file()?).chain_err(|| "unable to rename file")?; Ok(()) } } @@ -168,61 +169,57 @@ impl Podcast { } #[allow(dead_code)] - pub fn from_url(url: &str) -> Result<Podcast, rss::Error> { - match Channel::from_url(url) { - Ok(val) => Ok(Podcast::from(val)), - Err(err) => Err(err), - } + pub fn from_url(url: &str) -> Result<Podcast> { + Ok( + Podcast::from(Channel::from_url(url).chain_err(|| UNABLE_TO_CREATE_CHANNEL_FROM_RESPONSE)?), + ) } - pub fn from_title(title: &str) -> Result<Podcast, String> { - let mut path = get_xml_dir(); + pub fn from_title(title: &str) -> Result<Podcast> { + let mut path = get_xml_dir()?; let mut filename = String::from(title); filename.push_str(".xml"); path.push(filename); - match File::open(&path) { - Ok(file) => match Channel::read_from(BufReader::new(file)) { - Ok(podcast) => Ok(Podcast::from(podcast)), - Err(err) => Err(format!("Error: {}", err)), - }, - Err(err) => Err(format!("Error: {}", err)), - } + let file = File::open(&path).chain_err(|| UNABLE_TO_OPEN_FILE)?; + Ok(Podcast::from(Channel::read_from(BufReader::new(file)) + .chain_err(|| UNABLE_TO_CREATE_CHANNEL_FROM_FILE)?)) } - pub fn delete(title: &str) -> io::Result<()> { - let mut path = get_xml_dir(); + pub fn delete(title: &str) -> Result<()> { + let mut path = get_xml_dir()?; let mut filename = String::from(title); filename.push_str(".xml"); path.push(filename); - remove_file(path) + fs::remove_file(path).chain_err(|| UNABLE_TO_REMOVE_FILE) } - pub fn delete_all() -> io::Result<()> { - let path = get_xml_dir(); - remove_dir_all(path) + pub fn delete_all() -> Result<()> { + let path = get_xml_dir()?; + fs::remove_dir_all(path).chain_err(|| UNABLE_TO_READ_DIRECTORY) } pub fn episodes(&self) -> Vec<Episode> { let mut result = Vec::new(); - - let items = self.0.items().to_vec(); - for item in items { + for item in self.0.items().to_vec() { result.push(Episode::from(item)); } result } - pub fn download(&self) -> Result<(), io::Error> { + pub fn download(&self) -> Result<()> { print!("You are about to download all episodes (y/n): "); io::stdout().flush().ok(); let mut input = String::new(); - if io::stdin().read_line(&mut input).is_ok() && input.to_lowercase().trim() != "y" { + io::stdin() + .read_line(&mut input) + .chain_err(|| "unable to read stdin")?; + if input.to_lowercase().trim() != "y" { return Ok(()); } - let mut path = get_podcast_dir(); + let mut path = get_podcast_dir()?; path.push(self.title()); match already_downloaded(self.title()) { @@ -231,7 +228,7 @@ impl Podcast { if let Some(ep_title) = i.title() { if !downloaded.contains(ep_title) { if let Err(err) = i.download(self.title()) { - println!("{}", err); + eprintln!("{}", err); } } } @@ -240,7 +237,7 @@ impl Podcast { Err(_) => { self.episodes().par_iter().for_each(|i| { if let Err(err) = i.download(self.title()) { - println!("{}", err); + eprintln!("{}", err); } }); } @@ -249,8 +246,8 @@ impl Podcast { Ok(()) } - pub fn download_specific(&self, episode_numbers: &[usize]) -> Result<(), io::Error> { - let mut path = get_podcast_dir(); + pub fn download_specific(&self, episode_numbers: &[usize]) -> Result<()> { + let mut path = get_podcast_dir()?; path.push(self.title()); let downloaded = already_downloaded(self.title())?; @@ -282,38 +279,41 @@ impl Episode { } pub fn extension(&self) -> Option<&str> { - match self.0.enclosure() { - Some(enclosure) => match enclosure.mime_type() { - "audio/mpeg" => Some(".mp3"), - "audio/mp4" => Some(".m4a"), - "audio/ogg" => Some(".ogg"), - _ => find_extension(self.url().unwrap()), - }, - None => None, + match self.0.enclosure()?.mime_type() { + "audio/mpeg" => Some(".mp3"), + "audio/mp4" => Some(".m4a"), + "audio/ogg" => Some(".ogg"), + _ => find_extension(self.url().unwrap()), } } - pub fn download(&self, podcast_name: &str) -> Result<(), io::Error> { - let mut path = get_podcast_dir(); + pub fn download(&self, podcast_name: &str) -> Result<()> { + let mut path = get_podcast_dir()?; path.push(podcast_name); - DirBuilder::new().recursive(true).create(&path).unwrap(); + DirBuilder::new() + .recursive(true) + .create(&path) + .chain_err(|| UNABLE_TO_CREATE_DIRECTORY)?; if let Some(url) = self.url() { if let Some(title) = self.title() { let mut filename = String::from(title); - filename.push_str(self.extension().unwrap()); + filename.push_str(self.extension() + .chain_err(|| "unable to retrieve extension")?); path.push(filename); if !path.exists() { println!("Downloading: {}", path.to_str().unwrap()); - let mut file = File::create(&path)?; - let mut resp = reqwest::get(url).unwrap(); + let mut file = File::create(&path).chain_err(|| UNABLE_TO_CREATE_FILE)?; + let mut resp = reqwest::get(url).chain_err(|| UNABLE_TO_GET_HTTP_RESPONSE)?; let mut content: Vec<u8> = Vec::new(); - resp.read_to_end(&mut content)?; - file.write_all(&content)?; - return Ok(()); + resp.read_to_end(&mut content) + .chain_err(|| UNABLE_TO_READ_RESPONSE_TO_END)?; + file.write_all(&content).chain_err(|| UNABLE_TO_WRITE_FILE)?; } else { - println!("File already exists: {}", path.to_str().unwrap()); - return Ok(()); + println!( + "File already exists: {}", + path.to_str().chain_err(|| UNABLE_TO_CONVERT_TO_STR)? + ); } } } diff --git a/src/utils.rs b/src/utils.rs index 597d9b6..4648903 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,19 +1,34 @@ use std::collections::HashSet; use std::env; use std::fs::{self, DirBuilder, File}; -use std::io::{self, BufReader, Read, Write}; -use std::num::ParseIntError; +use std::io::{BufReader, Read, Write}; use std::path::PathBuf; +use errors::*; use reqwest; use rss::Channel; +pub const UNABLE_TO_PARSE_REGEX: &'static str = "unable to parse regex"; +pub const UNABLE_TO_OPEN_FILE: &'static str = "unable to open file"; +pub const UNABLE_TO_CREATE_FILE: &'static str = "unable to create file"; +pub const UNABLE_TO_WRITE_FILE: &'static str = "unable to write file"; +pub const UNABLE_TO_READ_FILE_TO_STRING: &'static str = "unable to read file to string"; +pub const UNABLE_TO_READ_DIRECTORY: &'static str = "unable to read directory"; +pub const UNABLE_TO_READ_ENTRY: &'static str = "unable to read entry"; +pub const UNABLE_TO_CREATE_DIRECTORY: &'static str = "unable to create directory"; +pub const UNABLE_TO_READ_RESPONSE_TO_END: &'static str = "unable to read response to end"; +pub const UNABLE_TO_GET_HTTP_RESPONSE: &'static str = "unable to get http response"; +pub const UNABLE_TO_CONVERT_TO_STR: &'static str = "unable to convert to &str"; +pub const UNABLE_TO_REMOVE_FILE: &'static str = "unable to remove file"; +pub const UNABLE_TO_CREATE_CHANNEL_FROM_RESPONSE: &'static str = + "unable to create channel from http response"; +pub const UNABLE_TO_CREATE_CHANNEL_FROM_FILE: &'static str = + "unable to create channel from xml file"; +pub const UNABLE_TO_RETRIEVE_PODCAST_BY_TITLE: &'static str = "unable to retrieve podcast by title"; pub fn trim_extension(filename: &str) -> Option<String> { let name = String::from(filename); - match name.rfind('.') { - Some(index) => Some(String::from(&name[0..index])), - None => None, - } + let index = name.rfind('.')?; + Some(String::from(&name[0..index])) } pub fn find_extension(input: &str) -> Option<&str> { @@ -33,35 +48,42 @@ pub fn find_extension(input: &str) -> Option<&str> { } } -pub fn create_directories() -> Result<(), String> { - let mut path = get_podcast_dir(); - path.push(".rss"); - if let Err(err) = DirBuilder::new().recursive(true).create(&path) { - return Err(format!( - "Couldn't create directory: {}\nReason: {}", - path.to_str().unwrap(), - err - )); +pub fn get_podcast_dir() -> Result<PathBuf> { + match env::var_os("PODCAST") { + Some(val) => Ok(PathBuf::from(val)), + None => { + let mut path = env::home_dir().chain_err(|| "Couldn't find your home directory")?; + path.push("Podcasts"); + Ok(path) + } } - Ok(()) } -pub fn already_downloaded(dir: &str) -> Result<HashSet<String>, io::Error> { +pub fn create_directories() -> Result<()> { + let mut path = get_podcast_dir()?; + path.push(".rss"); + DirBuilder::new() + .recursive(true) + .create(&path) + .chain_err(|| "unable to create directory") +} + +pub fn already_downloaded(dir: &str) -> Result<HashSet<String>> { let mut result = HashSet::new(); - let mut path = get_podcast_dir(); + let mut path = get_podcast_dir()?; path.push(dir); - let entries = fs::read_dir(path)?; + let entries = fs::read_dir(path).chain_err(|| "unable to read directory")?; for entry in entries { - let entry = entry?; + let entry = entry.chain_err(|| "unable to read entry")?; match entry.file_name().into_string() { Ok(name) => { - let index = name.find('.').unwrap(); + let index = name.find('.').chain_err(|| "unable to find string index")?; result.insert(String::from(&name[0..index])); } Err(_) => { - println!( + eprintln!( "OsString: {:?} couldn't be converted to String", entry.file_name() ); @@ -71,46 +93,41 @@ pub fn already_downloaded(dir: &str) -> Result<HashSet<String>, io::Error> { Ok(result) } -pub fn get_podcast_dir() -> PathBuf { - match env::var_os("PODCAST") { - Some(val) => PathBuf::from(val), - None => { - let mut path = env::home_dir().expect("Couldn't find your home directory"); - path.push("Podcasts"); - path - } - } -} - -pub fn get_sub_file() -> PathBuf { - let mut path = get_podcast_dir(); +pub fn get_sub_file() -> Result<PathBuf> { + let mut path = get_podcast_dir()?; path.push(".subscriptions"); - path + Ok(path) } -pub fn get_xml_dir() -> PathBuf { - let mut path = get_podcast_dir(); +pub fn get_xml_dir() -> Result<PathBuf> { + let mut path = get_podcast_dir()?; path.push(".rss"); - path + Ok(path) } -pub fn download_rss_feed(url: &str) -> Result<Channel, String> { - let mut path = get_podcast_dir(); +pub fn download_rss_feed(url: &str) -> Result<Channel> { + let mut path = get_podcast_dir()?; path.push(".rss"); - DirBuilder::new().recursive(true).create(&path).unwrap(); - let mut resp = reqwest::get(url).unwrap(); + DirBuilder::new() + .recursive(true) + .create(&path) + .chain_err(|| "unable to open directory")?; + let mut resp = reqwest::get(url).chain_err(|| "unable to open url")?; let mut content: Vec<u8> = Vec::new(); - resp.read_to_end(&mut content).unwrap(); - let channel = Channel::read_from(BufReader::new(&content[..])).unwrap(); + resp.read_to_end(&mut content) + .chain_err(|| "unable to read http response to end")?; + let channel = Channel::read_from(BufReader::new(&content[..])) + .chain_err(|| "unable to create channel from xml http response")?; let mut filename = String::from(channel.title()); filename.push_str(".xml"); path.push(filename); - let mut file = File::create(&path).unwrap(); - file.write_all(&content).unwrap(); + let mut file = File::create(&path).chain_err(|| "unable to create file")?; + file.write_all(&content) + .chain_err(|| "unable to write file")?; Ok(channel) } -pub fn parse_download_episodes(e_search: &str) -> Result<Vec<usize>, ParseIntError> { +pub fn parse_download_episodes(e_search: &str) -> Result<Vec<usize>> { let input = String::from(e_search); let mut ranges = Vec::<(usize, usize)>::new(); let mut elements = Vec::<usize>::new(); @@ -119,11 +136,13 @@ pub fn parse_download_episodes(e_search: &str) -> Result<Vec<usize>, ParseIntErr let temp = String::from(elem); if temp.contains('-') { let range: Vec<usize> = elem.split('-') - .map(|i| i.parse::<usize>().unwrap()) - .collect(); + .map(|i| i.parse::<usize>().chain_err(|| "unable to parse number")) + .collect::<Result<Vec<usize>>>() + .chain_err(|| "unable to collect ranges")?; ranges.push((range[0], range[1])); } else { - elements.push(elem.parse::<usize>()?); + elements.push(elem.parse::<usize>() + .chain_err(|| "unable to parse number")?); } } |
