aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG3
-rw-r--r--Cargo.toml5
-rw-r--r--src/actions.rs24
-rw-r--r--src/arg_parser.rs60
-rw-r--r--src/command_handler.rs4
-rw-r--r--src/download.rs34
-rw-r--r--src/main.rs6
-rw-r--r--src/parser.rs12
-rw-r--r--src/utils.rs13
9 files changed, 121 insertions, 40 deletions
diff --git a/CHANGELOG b/CHANGELOG
index eb6cb3d..64a8c88 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -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)
diff --git a/Cargo.toml b/Cargo.toml
index ec7b709..711fd7d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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(())