aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorNathan Jaremko2018-05-13 23:11:49 -0400
committerNathan Jaremko2018-05-13 23:11:49 -0400
commitc449861e1940b9f32569a2b75504244742e3dae0 (patch)
treec706f095e6f31a1910fc84e4997bda18877329a1 /src
parent8c72734e1abbccbeee833270121c2b86500a81d8 (diff)
downloadpodcast-c449861e1940b9f32569a2b75504244742e3dae0.tar.bz2
Improve error handling
Diffstat (limited to 'src')
-rw-r--r--src/actions.rs338
-rw-r--r--src/main.rs96
-rw-r--r--src/structs.rs168
-rw-r--r--src/utils.rs121
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")?);
}
}