diff options
| author | Nathan Jaremko | 2019-03-08 01:22:45 -0500 |
|---|---|---|
| committer | Nathan Jaremko | 2019-03-08 01:22:45 -0500 |
| commit | 1f529fde9e67f5b9c5a4984453d844357532910f (patch) | |
| tree | a262eaa17b8eff551ba4c67c9c5c6cded1bab698 | |
| parent | cf8d69e765549bd08b4e05313927068b96c7cbe7 (diff) | |
| download | podcast-1f529fde9e67f5b9c5a4984453d844357532910f.tar.bz2 | |
Add podcast search support
| -rw-r--r-- | CHANGELOG | 3 | ||||
| -rw-r--r-- | Cargo.toml | 5 | ||||
| -rw-r--r-- | src/actions.rs | 24 | ||||
| -rw-r--r-- | src/arg_parser.rs | 60 | ||||
| -rw-r--r-- | src/command_handler.rs | 4 | ||||
| -rw-r--r-- | src/download.rs | 34 | ||||
| -rw-r--r-- | src/main.rs | 6 | ||||
| -rw-r--r-- | src/parser.rs | 12 | ||||
| -rw-r--r-- | src/utils.rs | 13 |
9 files changed, 121 insertions, 40 deletions
@@ -1,3 +1,6 @@ +0.11.0 +- Add podcast search support. Podcast index is a work in progress, but I'm working on it. + 0.10.0 - Partial re-write of the application to be idiomatic - Improves performance throughout the application (downloads are also faster) @@ -1,7 +1,7 @@ [package] name = "podcast" edition = "2018" -version = "0.10.3" +version = "0.11.0" authors = ["Nathan Jaremko <njaremko@gmail.com>"] description = "A command line podcast manager" license = "GPL-3.0" @@ -32,4 +32,5 @@ serde = "1.0" serde_derive = "1.0" serde_json = "1.0" serde_yaml = "0.8" -toml = "0.4"
\ No newline at end of file +toml = "0.4" +percent-encoding = "1"
\ No newline at end of file diff --git a/src/actions.rs b/src/actions.rs index 847d068..2ae30f5 100644 --- a/src/actions.rs +++ b/src/actions.rs @@ -1,7 +1,7 @@ use crate::download; use crate::errors::*; use crate::structs::*; -use crate::utils::*; +use crate::utils; use std::collections::HashSet; use std::fs::{self, File}; @@ -18,8 +18,8 @@ use toml; pub fn list_episodes(search: &str) -> Result<()> { let re = Regex::new(&format!("(?i){}", &search))?; - let path = get_xml_dir()?; - create_dir_if_not_exist(&path)?; + let path = utils::get_xml_dir()?; + utils::create_dir_if_not_exist(&path)?; for entry in fs::read_dir(&path)? { let entry = entry?; @@ -51,14 +51,14 @@ pub fn list_episodes(search: &str) -> Result<()> { pub fn update_subscription(sub: &mut Subscription) -> Result<()> { println!("Updating {}", sub.title); - let mut path: PathBuf = get_podcast_dir()?; + let mut path: PathBuf = utils::get_podcast_dir()?; path.push(&sub.title); - create_dir_if_not_exist(&path)?; + utils::create_dir_if_not_exist(&path)?; let mut titles = HashSet::new(); for entry in fs::read_dir(&path)? { let unwrapped_entry = &entry?; - titles.insert(trim_extension( + titles.insert(utils::trim_extension( &unwrapped_entry.file_name().into_string().unwrap(), )); } @@ -66,9 +66,9 @@ pub fn update_subscription(sub: &mut Subscription) -> Result<()> { let resp = reqwest::get(&sub.url)?; let podcast = Podcast::from(Channel::read_from(BufReader::new(resp))?); - let mut podcast_rss_path = get_xml_dir()?; - podcast_rss_path.push(podcast.title()); - podcast_rss_path.set_extension("xml"); + let mut podcast_rss_path = utils::get_xml_dir()?; + let title = utils::append_extension(podcast.title(), "xml"); + podcast_rss_path.push(title); let file = File::create(&podcast_rss_path)?; (*podcast).write_to(BufWriter::new(file))?; @@ -112,7 +112,7 @@ pub fn check_for_update(version: &str) -> Result<()> { let config = resp.parse::<toml::Value>()?; let latest = config["package"]["version"] .as_str() - .expect(&format!("Cargo.toml didn't have a version {:?}", config)); + .unwrap_or_else(|| panic!("Cargo.toml didn't have a version {:?}", config)); if version != latest { println!("New version available: {} -> {}", version, latest); } @@ -122,7 +122,7 @@ pub fn check_for_update(version: &str) -> Result<()> { pub fn remove_podcast(state: &mut State, p_search: &str) -> Result<()> { if p_search == "*" { state.subscriptions = vec![]; - return delete_all(); + return utils::delete_all(); } let re_pod = Regex::new(&format!("(?i){}", &p_search))?; @@ -131,7 +131,7 @@ pub fn remove_podcast(state: &mut State, p_search: &str) -> Result<()> { let title = state.subscriptions[subscription].title.clone(); if re_pod.is_match(&title) { state.subscriptions.remove(subscription); - delete(&title)?; + utils::delete(&title)?; } } Ok(()) diff --git a/src/arg_parser.rs b/src/arg_parser.rs index 8f9650a..6438af3 100644 --- a/src/arg_parser.rs +++ b/src/arg_parser.rs @@ -1,12 +1,15 @@ use clap::{App, ArgMatches}; use std::env; +use std::io; +use std::io::Write; use std::path::Path; use crate::actions::*; use crate::download; use crate::errors::*; use crate::playback; +use crate::search; use crate::structs::*; pub fn download(state: &mut State, matches: &ArgMatches) -> Result<()> { @@ -60,14 +63,19 @@ pub fn play(state: &mut State, matches: &ArgMatches) -> Result<()> { Ok(()) } -pub fn subscribe(state: &mut State, config: &Config, matches: &ArgMatches) -> Result<()> { +pub fn subscribe(state: &mut State, config: Config, matches: &ArgMatches) -> Result<()> { let subscribe_matches = matches .subcommand_matches("sub") .or_else(|| matches.subcommand_matches("subscribe")) .unwrap(); let url = subscribe_matches.value_of("URL").unwrap(); + sub(state, config, url)?; + Ok(()) +} + +fn sub(state: &mut State, config: Config, url: &str) -> Result<()> { state.subscribe(url)?; - download::download_rss(&config, url)?; + download::download_rss(config, url)?; Ok(()) } @@ -94,3 +102,51 @@ pub fn complete(app: &mut App, matches: &ArgMatches) -> Result<()> { } Ok(()) } + +pub fn search(state: &mut State, config: Config, matches: &ArgMatches) -> Result<()> { + let matches = matches.subcommand_matches("search").unwrap(); + let podcast = matches.value_of("PODCAST").unwrap(); + let resp = search::search_for_podcast(podcast)?; + if resp.found().is_empty() { + println!("No Results"); + return Ok(()); + } + + { + let stdout = io::stdout(); + let mut lock = stdout.lock(); + for (i, r) in resp.found().iter().enumerate() { + writeln!(&mut lock, "({}) {}", i, r)?; + } + } + + print!("Would you like to subscribe to any of these? (y/n): "); + io::stdout().flush().ok(); + let mut input = String::new(); + io::stdin().read_line(&mut input)?; + if input.to_lowercase().trim() != "y" { + return Ok(()); + } + + print!("Which one? (#): "); + io::stdout().flush().ok(); + let mut num_input = String::new(); + io::stdin().read_line(&mut num_input)?; + let n: usize = num_input.trim().parse()?; + if n > resp.found().len() { + eprintln!("Invalid!"); + return Ok(()); + } + + let rss_resp = search::retrieve_rss(&resp.found()[n])?; + if let Some(err) = rss_resp.error() { + eprintln!("{}", err); + return Ok(()); + } + match rss_resp.url() { + Some(r) => sub(state, config, r)?, + None => eprintln!("Subscription failed. No url in API response."), + } + + Ok(()) +} diff --git a/src/command_handler.rs b/src/command_handler.rs index 742b79e..f65028b 100644 --- a/src/command_handler.rs +++ b/src/command_handler.rs @@ -24,7 +24,7 @@ pub fn parse_sub_command(matches: &ArgMatches) -> commands::Command { pub fn handle_matches( version: &str, state: &mut State, - config: &Config, + config: Config, app: &mut App, matches: &ArgMatches, ) -> Result<()> { @@ -43,7 +43,7 @@ pub fn handle_matches( arg_parser::subscribe(state, config, matches)?; } commands::Command::Search => { - println!("This feature is coming soon..."); + arg_parser::search(state, config, matches)?; } commands::Command::Remove => { arg_parser::remove(state, matches)?; diff --git a/src/download.rs b/src/download.rs index c58c9d8..7acc0b0 100644 --- a/src/download.rs +++ b/src/download.rs @@ -1,10 +1,10 @@ use crate::structs::*; -use crate::utils::*; +use crate::utils; +use failure::Fail; use std::collections::HashSet; use std::fs::File; use std::io::{self, BufReader, BufWriter, Write}; -use failure::Fail; use failure::Error; use rayon::prelude::*; @@ -53,23 +53,18 @@ pub fn download_episode_by_num(state: &State, p_search: &str, e_search: &str) -> #[derive(Debug, Fail)] enum DownloadError { #[fail(display = "File already exists: {}", path)] - AlreadyExists { - path: String, - } + AlreadyExists { path: String }, } pub fn download(podcast_name: &str, episode: &Episode) -> Result<(), Error> { - let mut path = get_podcast_dir()?; + let mut path = utils::get_podcast_dir()?; path.push(podcast_name); - create_dir_if_not_exist(&path)?; + utils::create_dir_if_not_exist(&path)?; if let (Some(mut title), Some(url)) = (episode.title(), episode.url()) { - episode.extension().map(|ext| { - if !title.ends_with(".") { - title.push_str("."); - } - title.push_str(&ext); - }); + if let Some(ext) = episode.extension() { + title = utils::append_extension(&title, &ext); + } path.push(title); if !path.exists() { println!("Downloading: {:?}", &path); @@ -79,7 +74,10 @@ pub fn download(podcast_name: &str, episode: &Episode) -> Result<(), Error> { let mut writer = BufWriter::new(file); io::copy(&mut reader, &mut writer)?; } else { - return Err(DownloadError::AlreadyExists{path: path.to_str().unwrap().to_string()}.into()); + return Err(DownloadError::AlreadyExists { + path: path.to_str().unwrap().to_string(), + } + .into()); } } Ok(()) @@ -142,10 +140,10 @@ pub fn download_all(state: &State, p_search: &str) -> Result<(), Error> { return Ok(()); } - let mut path = get_podcast_dir()?; + let mut path = utils::get_podcast_dir()?; path.push(podcast.title()); - already_downloaded(podcast.title()).map(|downloaded| { + utils::already_downloaded(podcast.title()).map(|downloaded| { podcast .episodes() .par_iter() @@ -160,8 +158,8 @@ pub fn download_all(state: &State, p_search: &str) -> Result<(), Error> { Ok(()) } -pub fn download_rss(config: &Config, url: &str) -> Result<(), Error> { - let channel = download_rss_feed(url)?; +pub fn download_rss(config: Config, url: &str) -> Result<(), Error> { + let channel = utils::download_rss_feed(url)?; let mut download_limit = config.auto_download_limit as usize; if 0 < download_limit { println!( diff --git a/src/main.rs b/src/main.rs index 780f87b..08fa545 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,7 @@ extern crate reqwest; extern crate rss; #[macro_use] extern crate serde_derive; +extern crate percent_encoding; extern crate serde_json; extern crate serde_yaml; extern crate toml; @@ -26,6 +27,7 @@ mod download; mod migration_handler; mod parser; mod playback; +mod search; mod structs; mod utils; @@ -38,7 +40,7 @@ mod errors { use self::structs::*; use errors::Result; -const VERSION: &str = "0.10.3"; +const VERSION: &str = "0.11.0"; fn main() -> Result<()> { utils::create_directories()?; @@ -47,6 +49,6 @@ fn main() -> Result<()> { let config = Config::new()?; let mut app = parser::get_app(&VERSION); let matches = app.clone().get_matches(); - command_handler::handle_matches(&VERSION, &mut state, &config, &mut app, &matches)?; + command_handler::handle_matches(&VERSION, &mut state, config, &mut app, &matches)?; state.save() } diff --git a/src/parser.rs b/src/parser.rs index caae070..743da6d 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -83,6 +83,18 @@ pub fn get_app<'a, 'b>(version: &'a str) -> App<'a, 'b> { Arg::with_name("debug") .short("d") .help("print debug information verbosely"), + ) + .arg( + Arg::with_name("PODCAST") + .help("Regex for subscribed podcast") + .required(true) + .index(1), + ) + .arg( + Arg::with_name("EPISODE") + .help("Episode index") + .required(false) + .index(2), ), ) .subcommand( diff --git a/src/utils.rs b/src/utils.rs index bb96909..21b01a7 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -13,7 +13,7 @@ const UNSUBSCRIBE_NOTE: &str = "Note: this does NOT delete any downloaded podcas pub fn trim_extension(filename: &str) -> Option<String> { let name = String::from(filename); - if name.contains(".") { + if name.contains('.') { name.rfind('.').map(|index| String::from(&name[0..index])) } else { Some(name) @@ -21,7 +21,7 @@ pub fn trim_extension(filename: &str) -> Option<String> { } pub fn find_extension(input: &str) -> Option<String> { - let s: Vec<String> = input.split(".").map(|s| s.to_string()).collect(); + let s: Vec<String> = input.split('.').map(|s| s.to_string()).collect(); if s.len() > 1 { return s.last().cloned(); } @@ -39,6 +39,15 @@ pub fn get_podcast_dir() -> Result<PathBuf> { } } +pub fn append_extension(filename: &str, ext: &str) -> String { + let mut f = filename.to_string(); + if !f.ends_with('.') { + f.push_str("."); + } + f.push_str(ext); + f +} + pub fn create_dir_if_not_exist(path: &PathBuf) -> Result<()> { DirBuilder::new().recursive(true).create(&path)?; Ok(()) |
