aboutsummaryrefslogtreecommitdiffstats
path: root/src/structs.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/structs.rs')
-rw-r--r--src/structs.rs214
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()),
}
}