diff options
Diffstat (limited to 'src/structs.rs')
| -rw-r--r-- | src/structs.rs | 214 | 
1 files changed, 77 insertions, 137 deletions
| diff --git a/src/structs.rs b/src/structs.rs index 1c3ceaf..2524034 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -1,30 +1,46 @@  use super::actions::*;  use super::utils::*;  use crate::errors::*; +use core::ops::Deref; -use std::collections::BTreeSet; +use std::collections::HashSet;  use std::fs::{self, File}; -use std::io::{self, BufReader, Write}; +use std::io::{self, BufReader, BufWriter, Write};  use chrono::prelude::*; -use rayon::prelude::*;  use regex::Regex;  use rss::{Channel, Item};  use serde_json;  use std::path::PathBuf;  #[cfg(target_os = "macos")] -static ESCAPE_REGEX: &str = r"/"; +const ESCAPE_REGEX: &str = r"/";  #[cfg(target_os = "linux")] -static ESCAPE_REGEX: &str = r"/"; +const ESCAPE_REGEX: &str = r"/";  #[cfg(target_os = "windows")] -static ESCAPE_REGEX: &str = r#"[\\/:*?"<>|]"#; +const ESCAPE_REGEX: &str = r#"[\\/:*?"<>|]"#;  lazy_static! {      static ref FILENAME_ESCAPE: Regex = Regex::new(ESCAPE_REGEX).unwrap();  } -#[derive(Debug, PartialEq, Serialize, Deserialize)] +fn create_new_config_file(path: &PathBuf) -> Result<Config> { +    writeln!( +        io::stdout().lock(), +        "Creating new config file at {:?}", +        &path +    ) +    .ok(); +    let download_limit = 1; +    let file = File::create(&path)?; +    let config = Config { +        auto_download_limit: download_limit, +    }; +    serde_yaml::to_writer(file, &config)?; +    Ok(config) +} + +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]  pub struct Config {      pub auto_download_limit: i64,  } @@ -34,16 +50,21 @@ impl Config {          let mut path = get_podcast_dir()?;          path.push(".config.yaml");          let config = if path.exists() { -            let file = File::open(&path).chain_err(|| UNABLE_TO_OPEN_FILE)?; +            let file = File::open(&path)?;              match serde_yaml::from_reader(file) {                  Ok(config) => config,                  Err(err) => {                      let mut new_path = path.clone();                      new_path.set_extension("yaml.bk"); -                    eprintln!("{}", err); -                    eprintln!("Failed to open config file, moving to {:?}", &new_path); -                    fs::rename(&path, new_path) -                        .chain_err(|| "Failed to move old config file...")?; +                    let stderr = io::stderr(); +                    let mut handle = stderr.lock(); +                    writeln!( +                        &mut handle, +                        "{}\nFailed to open config file, moving to {:?}", +                        err, &new_path +                    ) +                    .ok(); +                    fs::rename(&path, new_path)?;                      create_new_config_file(&path)?                  }              } @@ -54,17 +75,6 @@ impl Config {      }  } -fn create_new_config_file(path: &PathBuf) -> Result<Config> { -    println!("Creating new config file at {:?}", &path); -    let download_limit = 1; -    let file = File::create(&path).chain_err(|| UNABLE_TO_CREATE_FILE)?; -    let config = Config { -        auto_download_limit: download_limit, -    }; -    serde_yaml::to_writer(file, &config).chain_err(|| UNABLE_TO_WRITE_FILE)?; -    Ok(config) -} -  #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]  pub struct Subscription {      pub title: String, @@ -72,6 +82,12 @@ pub struct Subscription {      pub num_episodes: usize,  } +impl Subscription { +    pub fn title(&self) -> &str { +        &self.title +    } +} +  #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]  pub struct State {      pub version: String, @@ -83,24 +99,8 @@ impl State {      pub fn new(version: &str) -> Result<State> {          let path = get_sub_file()?;          if path.exists() { -            let file = File::open(&path).chain_err(|| UNABLE_TO_OPEN_FILE)?; -            let mut state: State = match serde_json::from_reader(&file) { -                Ok(val) => val, -                // This will happen if the struct has changed between versions -                Err(_) => { -                    let v: serde_json::Value = serde_json::from_reader(&file) -                        .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()) -                                .chain_err(|| "unable to parse value from json")?, -                        }, -                    } -                } -            }; +            let file = File::open(&path)?; +            let mut state: State = serde_json::from_reader(BufReader::new(&file))?;              state.version = String::from(version);              // Check if a day has passed (86400 seconds) since last launch              if 86400 @@ -115,7 +115,7 @@ impl State {              state.save()?;              Ok(state)          } else { -            println!("Creating new file {:?}", &path); +            writeln!(io::stdout().lock(), "Creating new file: {:?}", &path).ok();              Ok(State {                  version: String::from(version),                  last_run_time: Utc::now(), @@ -124,12 +124,20 @@ impl State {          }      } +    pub fn subscriptions(&self) -> &[Subscription] { +        &self.subscriptions +    } + +    pub fn subscriptions_mut(&mut self) -> &mut [Subscription] { +        &mut self.subscriptions +    } +      pub fn subscribe(&mut self, url: &str) -> Result<()> { -        let mut set = BTreeSet::new(); +        let mut set = HashSet::new();          for sub in self.subscriptions() {              set.insert(sub.title.clone());          } -        let podcast = Podcast::from(Channel::from_url(url).unwrap()); +        let podcast = Podcast::from(Channel::from_url(url)?);          if !set.contains(podcast.title()) {              self.subscriptions.push(Subscription {                  title: String::from(podcast.title()), @@ -140,45 +148,30 @@ impl State {          self.save()      } -    pub fn subscriptions(&self) -> &[Subscription] { -        &self.subscriptions -    } - -    pub fn subscriptions_mut(&mut self) -> &mut [Subscription] { -        &mut self.subscriptions -    } -      pub fn save(&self) -> Result<()> {          let mut path = get_sub_file()?;          path.set_extension("json.tmp"); -        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)?; -        } -        let sub_file_path = get_sub_file()?; -        fs::rename(&path, &sub_file_path) -            .chain_err(|| format!("unable to rename file {:?} to {:?}", &path, &sub_file_path))?; +        let file = File::create(&path)?; +        serde_json::to_writer(BufWriter::new(file), self)?; +        fs::rename(&path, get_sub_file()?)?;          Ok(())      }  } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)]  pub struct Podcast(Channel); -#[derive(Clone, Debug)] -pub struct Episode(Item); -  impl From<Channel> for Podcast {      fn from(channel: Channel) -> Podcast {          Podcast(channel)      }  } -impl From<Item> for Episode { -    fn from(item: Item) -> Episode { -        Episode(item) +impl Deref for Podcast { +    type Target = Channel; + +    fn deref(&self) -> &Channel { +        &self.0      }  } @@ -194,9 +187,7 @@ impl Podcast {      #[allow(dead_code)]      pub fn from_url(url: &str) -> Result<Podcast> { -        Ok(Podcast::from( -            Channel::from_url(url).chain_err(|| UNABLE_TO_CREATE_CHANNEL_FROM_RESPONSE)?, -        )) +        Ok(Podcast::from(Channel::from_url(url)?))      }      pub fn from_title(title: &str) -> Result<Podcast> { @@ -205,11 +196,8 @@ impl Podcast {          filename.push_str(".xml");          path.push(filename); -        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)?, -        )) +        let file = File::open(&path)?; +        Ok(Podcast::from(Channel::read_from(BufReader::new(file))?))      }      pub fn episodes(&self) -> Vec<Episode> { @@ -219,65 +207,14 @@ impl Podcast {          }          result      } +} -    pub fn download(&self) -> Result<()> { -        print!( -            "You are about to download all episodes of {} (y/n): ", -            self.title() -        ); -        io::stdout().flush().ok(); -        let mut input = String::new(); -        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()?; -        path.push(self.title()); - -        match already_downloaded(self.title()) { -            Ok(downloaded) => { -                self.episodes().par_iter().for_each(|i| { -                    if let Some(ep_title) = i.title() { -                        if !downloaded.contains(&ep_title) { -                            if let Err(err) = download(self.title(), i) { -                                eprintln!("{}", err); -                            } -                        } -                    } -                }); -            } -            Err(_) => { -                self.episodes().par_iter().for_each(|i| { -                    if let Err(err) = download(self.title(), i) { -                        eprintln!("{}", err); -                    } -                }); -            } -        } - -        Ok(()) -    } - -    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())?; -        let episodes = self.episodes(); +#[derive(Clone, Debug, PartialEq)] +pub struct Episode(Item); -        episode_numbers.par_iter().for_each(|ep_num| { -            if let Some(ep_title) = episodes[episodes.len() - ep_num].title() { -                if !downloaded.contains(&ep_title) { -                    if let Err(err) = download(self.title(), &episodes[episodes.len() - ep_num]) { -                        eprintln!("{}", err); -                    } -                } -            } -        }); -        Ok(()) +impl From<Item> for Episode { +    fn from(item: Item) -> Episode { +        Episode(item)      }  } @@ -297,11 +234,14 @@ impl Episode {          }      } -    pub fn extension(&self) -> Option<&str> { +    pub fn extension(&self) -> Option<String> {          match self.0.enclosure()?.mime_type() { -            "audio/mpeg" => Some(".mp3"), -            "audio/mp4" => Some(".m4a"), -            "audio/ogg" => Some(".ogg"), +            "audio/mpeg" => Some("mp3".into()), +            "audio/mp4" => Some("m4a".into()), +            "audio/aac" => Some("m4a".into()), +            "audio/ogg" => Some("ogg".into()), +            "audio/vorbis" => Some("ogg".into()), +            "audio/opus" => Some("opus".into()),              _ => find_extension(self.url().unwrap()),          }      } | 
