DP6ASS5FJGSZUG2B4SQOKZVHFAVZLEHFVEWBNFG3BHMC6FZAJNHQC QG7TWI4VFXEVO3WTXGUPKUE5V5VKEJBWDGIE3IWM2G5L7RQRR2AAC OX53NPUOF2CYET46DF6Y22DVQSKQKR2KW3TGYDAXILEBYVJO2EPQC L3I4GC7R74HPQS3VCQ44UTBAKKBOW6UDVXV2EF7XSWH7H2Z3RRJQC FBQOBNZ6JJQXSHYQK7MCFA4U7NBNB47FXED7Y7HPRTOQVXJFIAGAC XIHPYOWDLQY2MVMVUQPH23O3TBALRG4G2CHSLWSCAYMY5NVJ32WQC IFBRAMVLQ4Z6BAEMWDIXD2V5HSZK4DHRWYZNB32IBY7ZRTNZJVCQC O53GR2OQHGRKAVJT2RVPRHYFB54W5LM4DQYT7EYVGKU7HDK5CJJQC FRLZDOAN7A3N623TLAPO66JVWBLBI45AG6P3DMQMDKGEZ2OBPYAAC UIMZBURR7KOWSREO4GDH5C2LZDUTEZBKQNYWBYSFGUTRYJ4GKSNQC JYSIHNS67XTGAR4HN7ZHWFMGGYSK5IY6J6EHO4YUZOR7UMMWAORQC 3YR56Y65UIAL3J7PUXWVJMOOHYZYDIX4V54OT2TJPZ25WQ6MXHCQC 2CKX4R6ONNXDXGRYZ5NZEBJZFX5Z6BYPGNJ7LMXUHHFB4MUFJRCAC 5JMYBRF3UYX4LFH7JK6S4BEDKRVKDFIL4YKTCWKMKP4TMNNGQFKQC NE63ERXN7OUYSQ4PGPIQIIKEYD7OAOWXXSGMTORD6RJNUZHRLVJAC ZD3G3BCXBEXELHH3KMXMDUQUJ4BXIF4ZSZKFIJJCRO7IDK4XPOWAC Y6BVNXQ747WQKVB4JO3AHYOMBWNNJNPC6SG2UUCCZRIIXDMXJYDQC JDSHG6Q3GBJBEMIQRIDLYSCRUIH6PZD5P2DF6VGXAW5RRZAK73FQC NUYITHNW2PMOOUBX2ZT3JHSK3MQUFP4W3GEZ5HFJFDOMT56R4QSAC DSOQZYPHBDUFWKMMDXJZCHLPP7GRHW6PS7EGTF42M6OKZCEXSY3AC EZMX4SYFEBYNJVQETRVAYONU5MIMQYTTSA5DRMTQET5B7CL6CI6AC ZYS43ILR4OXI7S2AYNGYSTK3IU2UVELIWVCCWDS7RVZQDSNJMDHQC BSPWOOHZMN3RAOHGJ2A3XKUOUCFFAOXS7YR67E3AARPPPIA5YPDAC O3ZP3X5CVIYUAPZJFQD7E4LBE4MA2K4DS5WABI4KWKOG3POBJ2YQC VRLKU5J6FEMVVZIK5ABNDM5KYIGJ56YFJW52CBVTW3EH35FOHU6AC S3YILTUU7CUF6AOV2OHCCXIR37C6B5LTFW4OHJNHEJNMA7UOFKBAC EQUMHANDFLYXP666ZZ4ZSNASEDMA6MP4RZQQZNWUR27WGXGINUQAC RNYYKYFGUE2PWL6MFY5MN7OLB5UZNXQ6EEFM7HROMLYIK6OSC5IQC OWYYKTVHXMHHF6B44IN2SMYQ4MJ46LSKQT3OYPHONLQ6R7TSRMTAC L7S4333LAJBFHRBEI5KB27CQJ3VVWSFZ6HOJ4FPFDDNAE4PUH6GAC ZSO6T3AM36JCKVGEZEIC6NA7WWESSW73TVM67W3BOWUPOSBHZEFQC ADXMUSFXOKBPCHW4XS3T4KLWMPGQPYDMZW6YFFSHZPAQKEGIKCBQC TE6ACJLE7GUBYSV7B77L5YFKUMTV2PYR7CQRKG6A3XZ6YZWBR7TAC USEXBPODUNF4Y7KLGC7SFQZAARXFMTAHK4OKO6HR7G3UPZY4C42QC ORRUAQNB2SPMP67NJKXTJRMWOXVNOONLWE7VXTU2EH726QV3SV2AC GE7XXDPC73SUY6I6F3X6I4QFQO2BCPHD4MZALYOWG7H7SRE5XAFQC UX4YE27CC5I4OFW5DMEKHTIGUUGMCAM6X5S4NXPTS7XK4MJLUKHQC GG5VBVLAQAQFIPOQGYSCC2T5RXXQMDPKXZFLCYGTUNE5DLRQYFDQC M4FCDZ745GHHL3OLH64EVYOEOEGGGVIBCVFGX5JUJDJRE5OLCXLQC SBPKWZNQF5BWAJ7SZHWVK5BG6DTVJNDYND6UG5PDZCWZ2W4W2HXQC 6FJACP6KUOZ4HWK4PSS5PFPGDYXZSCAWSKIARWBDGCZTPJWXA62AC THSENT35O3PIXQ343QPPE3DJGR4YVULN6YPS5ETW5PXSVGZZQIZAC HSDBPX2AMUS4NRA52EHIYOR7H37ABNGJWBJKPQABFMFDU7EITSIAC JJ4SMY257MAHSJSZH5PJZMLBH3GJX5VKH2ZZSBGWLL7FWP7OA7TQC J64KBLKALQ3HQCY4HJU5H6WBXTATS7TKBYNNUUSNJE7JLWLYO66QC 476KTQSS5NXVCTVLVZQRGSYD5OAFBYG75VTSWBN26Q45RSMRT5YQC 5GQNHICLSFAA7ZUFXUCNACCPAIIGK4DV2QPTONDNXLS4TJJTOFHAC TV3GOKIHRVTPGEFYLMTNFQYZBNJRL46ZLOKSQACHVIGW4IWOMDFQC MQKOX2CQ7AON24UJC7RORAC7Y2UROROVG7BBKLVWURPXKY75JV5AC 76TBVFPIFU3LSMXY5NAHZBH6HRJLSLK43PGOPL6QQ2YYVBJ64QAQC 5FEMSWRS6SMVKBJAV4IYJLEJ2CML6QNZM75UGQFIIMAR5FBAACXAC DQ3IKTJCRSC74OONPXMNOMRGCV4MOAYQIMM7IQD6Z7KFJGP6MG7AC EEJ6CBJRTXLPQP44I2RLWVLJBX565DXXAWU4JIWNA3MMNE7WB5LQC JPN37V6Q35ZAW7A2DTGX2WJ3IJ66BAGHXHWXOGHQRHGFAOETFQ7AC let mut path = get_podcast_dir()?;path.push(".rss");DirBuilder::new().recursive(true).create(&path).chain_err(|| UNABLE_TO_CREATE_DIRECTORY)?;
let path = get_xml_dir()?;create_dir_if_not_exist(&path)?;
if let Some(url) = episode.url() {if let Some(title) = episode.title() {let mut filename = title;filename.push_str(episode.extension().chain_err(|| "unable to retrieve extension")?,);path.push(filename);if !path.exists() {{let mut handle = stdout.lock();writeln!(&mut handle, "Downloading: {:?}", &path).ok();}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).chain_err(|| UNABLE_TO_READ_RESPONSE_TO_END)?;file.write_all(&content).chain_err(|| UNABLE_TO_WRITE_FILE)?;} else {let mut handle = stdout.lock();writeln!(&mut handle,"File already exists: {:?}",&path).ok();}}}
const VERSION: &str = "0.7.5";
let matches = App::new("podcast").version(VERSION).author("Nathan J. <njaremko@gmail.com>").about("A command line podcast manager").subcommand(SubCommand::with_name("download").about("download episodes of podcast").arg(Arg::with_name("PODCAST").help("Regex for subscribed podcast").required(true).index(1),).arg(Arg::with_name("EPISODE").required(false).help("Episode index").index(2),).arg(Arg::with_name("name").short("e").long("episode").help("Download using episode name instead of number").required(false),).arg(Arg::with_name("all").short("a").long("all").help("Download all matching episodes").required(false),),).subcommand(SubCommand::with_name("ls").about("list episodes of podcast").arg(Arg::with_name("PODCAST").help("Regex for subscribed podcast").index(1),),).subcommand(SubCommand::with_name("list").about("list episodes of podcast").arg(Arg::with_name("PODCAST").help("Regex for subscribed podcast").index(1),),).subcommand(SubCommand::with_name("play").about("play an episode").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),).arg(Arg::with_name("name").short("e").long("episode").help("Play using episode name instead of number").required(false),),).subcommand(SubCommand::with_name("search").about("searches for podcasts").arg(Arg::with_name("debug").short("d").help("print debug information verbosely"),),).subcommand(SubCommand::with_name("subscribe").about("subscribes to a podcast RSS feed").arg(Arg::with_name("URL").help("URL to RSS feed").required(true).index(1),).arg(Arg::with_name("download").short("d").long("download").help("auto download based on config"),),).subcommand(SubCommand::with_name("refresh").about("refresh subscribed podcasts")).subcommand(SubCommand::with_name("update").about("check for updates")).subcommand(SubCommand::with_name("rm").about("unsubscribe from a podcast").arg(Arg::with_name("PODCAST").help("Podcast to delete").index(1)),).subcommand(SubCommand::with_name("completion").about("install shell completion").arg(Arg::with_name("SHELL").help("Shell to print completion for").index(1),),).get_matches();match matches.subcommand_name() {Some("download") => {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)?} else if download_matches.occurrences_of("name") > 0 {download_episode_by_name(&state,podcast,ep,download_matches.occurrences_of("all") > 0,)?} else {download_episode_by_num(&state, podcast, ep)?}}None => download_all(&state, podcast)?,}}Some("ls") | Some("list") => {let list_matches = matches.subcommand_matches("ls").or_else(|| matches.subcommand_matches("list")).chain_err(|| "unable to find subcommand matches")?;match list_matches.value_of("PODCAST") {Some(regex) => list_episodes(regex)?,None => list_subscriptions(&state)?,}}Some("play") => {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) => {if play_matches.occurrences_of("name") > 0 {play_episode_by_name(&state, podcast, episode)?} else {play_episode_by_num(&state, podcast, episode)?}}None => play_latest(&state, podcast)?,}}Some("subscribe") => {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)?;} else {subscribe_rss(url)?;}}Some("search") => println!("This feature is coming soon..."),Some("rm") => {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").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)?,_ => (),}
let matches = parser::get_matches(&VERSION);match_handler::handle_matches(&VERSION, &mut state, &config, &matches)?;
use clap::ArgMatches;use crate::actions::*;use crate::errors::*;use crate::structs::*;pub fn handle_matches(version: &str,state: &mut State,config: &Config,matches: &ArgMatches,) -> Result<()> {match matches.subcommand_name() {Some("download") => {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)?} else if download_matches.occurrences_of("name") > 0 {download_episode_by_name(&state,podcast,ep,download_matches.occurrences_of("all") > 0,)?} else {download_episode_by_num(&state, podcast, ep)?}}None => download_all(&state, podcast)?,}}Some("ls") | Some("list") => {let list_matches = matches.subcommand_matches("ls").or_else(|| matches.subcommand_matches("list")).chain_err(|| "unable to find subcommand matches")?;match list_matches.value_of("PODCAST") {Some(regex) => list_episodes(regex)?,None => list_subscriptions(&state)?,}}Some("play") => {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) => {if play_matches.occurrences_of("name") > 0 {play_episode_by_name(&state, podcast, episode)?} else {play_episode_by_num(&state, podcast, episode)?}}None => play_latest(&state, podcast)?,}}Some("sub") | Some("subscribe") => {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)?;} else {subscribe_rss(url)?;}}Some("search") => println!("This feature is coming soon..."),Some("rm") => {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(state, regex)?}Some("completion") => {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(state),Some("update") => check_for_update(version)?,_ => (),};Ok(())}
use crate::errors::*;use crate::utils::*;use std::fs;pub fn migrate_old_subscriptions() -> Result<()> {let path = get_podcast_dir()?;let mut old_path = path.clone();old_path.push(".subscriptions");if old_path.exists() {println!("Migrating old subscriptions file...");let new_path = get_sub_file()?;fs::rename(&old_path, &new_path).chain_err(|| format!("Unable to move {:?} to {:?}", &old_path, &new_path))?;}Ok(())}
use clap::{App, Arg, ArgMatches, SubCommand};pub fn get_matches<'a>(version: &str) -> ArgMatches<'a> {App::new("podcast").version(version).author("Nathan J. <njaremko@gmail.com>").about("A command line podcast manager").subcommand(SubCommand::with_name("download").about("download episodes of podcast").arg(Arg::with_name("PODCAST").help("Regex for subscribed podcast").required(true).index(1),).arg(Arg::with_name("EPISODE").required(false).help("Episode index").index(2),).arg(Arg::with_name("name").short("e").long("episode").help("Download using episode name instead of index number").required(false),).arg(Arg::with_name("all").short("a").long("all").help("Download all matching episodes").required(false),),).subcommand(SubCommand::with_name("ls").about("list episodes of podcast").arg(Arg::with_name("PODCAST").help("Regex for subscribed podcast").index(1),),).subcommand(SubCommand::with_name("list").about("list episodes of podcast").arg(Arg::with_name("PODCAST").help("Regex for subscribed podcast").index(1),),).subcommand(SubCommand::with_name("play").about("play an episode").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),).arg(Arg::with_name("name").short("e").long("episode").help("Play using episode name instead of index number").required(false),),).subcommand(SubCommand::with_name("search").about("searches for podcasts").arg(Arg::with_name("debug").short("d").help("print debug information verbosely"),),).subcommand(SubCommand::with_name("subscribe").about("subscribes to a podcast RSS feed").arg(Arg::with_name("URL").help("URL to RSS feed").required(true).index(1),).arg(Arg::with_name("download").short("d").long("download").help("auto download based on config"),),).subcommand(SubCommand::with_name("sub").about("subscribes to a podcast RSS feed").arg(Arg::with_name("URL").help("URL to RSS feed").required(true).index(1),).arg(Arg::with_name("download").short("d").long("download").help("auto download based on config"),),).subcommand(SubCommand::with_name("refresh").about("refresh subscribed podcasts")).subcommand(SubCommand::with_name("update").about("check for updates")).subcommand(SubCommand::with_name("rm").about("unsubscribe from a podcast").arg(Arg::with_name("PODCAST").help("Podcast to delete").index(1)),).subcommand(SubCommand::with_name("completion").about("install shell completion").arg(Arg::with_name("SHELL").help("Shell to print completion for").index(1),),).get_matches()}
let mut download_limit = 1;path.push(".config");if path.exists() {let mut s = String::new();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() {download_limit = val;
path.push(".config.yaml");let config = if path.exists() {let file = File::open(&path).chain_err(|| UNABLE_TO_OPEN_FILE)?;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...")?;create_new_config_file(&path)?
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)?;}Ok(Config {auto_download_limit: download_limit,})
create_new_config_file(&path)?};Ok(config)
#[derive(Serialize, Deserialize, Clone, Debug)]
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)]
let mut s = String::new();{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) {
let file = File::open(&path).chain_err(|| UNABLE_TO_OPEN_FILE)?;let mut state: State = match serde_json::from_reader(&file) {
pub fn subscriptions(&self) -> Vec<Subscription> {self.subscriptions.clone()
pub fn subscriptions(&self) -> &[Subscription] {&self.subscriptions}pub fn subscriptions_mut(&mut self) -> &mut [Subscription] {&mut self.subscriptions
fs::rename(&path, get_sub_file()?).chain_err(|| "unable to rename 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))?;
pub fn download(&self, podcast_name: &str) -> Result<()> {let stdout = io::stdout();let mut path = get_podcast_dir()?;path.push(podcast_name);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 = title;filename.push_str(self.extension().chain_err(|| "unable to retrieve extension")?,);path.push(filename);if !path.exists() {{let mut handle = stdout.lock();writeln!(&mut handle, "Downloading: {}", path.to_str().unwrap()).ok();}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).chain_err(|| UNABLE_TO_READ_RESPONSE_TO_END)?;file.write_all(&content).chain_err(|| UNABLE_TO_WRITE_FILE)?;} else {let mut handle = stdout.lock();writeln!(&mut handle, "File already exists: {}", path.to_str().chain_err(|| UNABLE_TO_CONVERT_TO_STR)?).ok();}}}Ok(())}