diff --git a/src/download.rs b/src/download.rs index e514baf..10dd1af 100644 --- a/src/download.rs +++ b/src/download.rs @@ -7,8 +7,10 @@ use std::collections::{HashMap, BTreeMap, HashSet}; use anyhow::Context; use sanitise_file_name::sanitise; +use crate::folders; use crate::rss; + #[derive(Debug, Default, serde::Serialize, serde::Deserialize)] pub(crate) struct Specification<'a> { files: HashMap, Cow<'a, path::Path>>, @@ -98,10 +100,10 @@ pub(crate) fn update_podcast( ) -> anyhow::Result<()> { // Create output directory - let output = root.join(sanitise(&alias)); + let output = folders::podcast_folder(root, alias); if !output.exists() { - fs::create_dir(&output) - .with_context(|| format!("failed to create output directory for podcast {}", alias))?; + fs::create_dir_all(&output) + .context(format!("failed to create output directory for podcast {}", alias))?; } println!(r#"[info] scanning feed for "{}""#, alias); @@ -115,7 +117,7 @@ pub(crate) fn update_podcast( .with_header("User-Agent", "podcast-downloader") .with_header("Accept", "*/*") .send() - .with_context(|| format!(r#"error when requesting feed url "{}" for {}"#, feed_url, alias))?; + .context(format!(r#"error when requesting feed url "{}" for {}"#, feed_url, alias))?; if response.status_code != 200 { eprintln!(r#"[error] feed "{}" for alias {} responded with non-200 ({}) status code"#, feed_url, alias, response.status_code); diff --git a/src/folders.rs b/src/folders.rs new file mode 100644 index 0000000..bad7f41 --- /dev/null +++ b/src/folders.rs @@ -0,0 +1,12 @@ +use std::path; + +use sanitise_file_name::sanitise; + +pub(crate) const PODCASTS_DIR: &str = "Podcasts"; + +pub(crate) fn podcast_folder( + root: &path::Path, + alias: &str, +) -> path::PathBuf { + root.join(PODCASTS_DIR).join(sanitise(alias)) +} diff --git a/src/input.rs b/src/input.rs index 68a6900..3ffbb6b 100644 --- a/src/input.rs +++ b/src/input.rs @@ -2,58 +2,58 @@ use std::path; use std::collections::HashMap; #[derive(clap::Parser)] -pub (crate) struct Args { +pub(crate) struct Args { /// Path to the configuration file listing podcast RSS feeds. #[arg(default_value = "./podcasts.toml")] - pub (crate) config : path::PathBuf, + pub(crate) config: path::PathBuf, #[command(subcommand)] - pub (crate) command : Command, + pub(crate) command: Command, } #[derive(clap::ValueEnum, Copy, Clone, Debug,PartialEq, Eq, PartialOrd, Ord)] -pub (crate) enum ListenStatus { +pub(crate) enum ListenStatus { Listened, Unlistened, } #[derive(clap::Subcommand)] -pub (crate) enum Command { +pub(crate) enum Command { /// Updates feed and downloads latest episodes. Download { /// The podcast to update. Updates all in configuration file if unspecified. #[arg(long, short)] - podcast : Option, + podcast: Option, }, /// Lists the episodes for a given podcast, filtered based on if it has been listened or not. List { /// Filter for if episodes have been listened to or not. #[arg(long, short)] - status : Option, + status: Option, /// The podcast to list episodes for. #[arg(long, short)] - podcast : String, + podcast: String, }, /// Marks an entire podcast's history of episodes as played or unplayed. Mark { /// The new listen status for the episodes. #[arg(long, short)] - status : ListenStatus, + status: ListenStatus, /// The podcast to change the listen status of. #[arg(long, short)] - podcast : String, + podcast: String, }, /// Tags files and generates playlists ready for use with an iPod. Tag { /// The podcast to tag and generate playlists for. #[arg(long, short)] - podcast : Option, + podcast: Option, }, } /// Struct modelling configuration file format. #[derive(serde::Deserialize)] -pub (crate) struct Config { +pub(crate) struct Config { /// Map from podcast alias to RSS feed either as a url (prefix: http) or file path. - pub (crate) podcasts : HashMap, + pub(crate) podcasts: HashMap, } diff --git a/src/main.rs b/src/main.rs index d1322c7..7d72d11 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ mod rss; mod input; mod tagging; +mod folders; mod download; use input::{Command, ListenStatus}; @@ -8,7 +9,6 @@ use input::{Command, ListenStatus}; use std::fs; use anyhow::Context; -use sanitise_file_name::sanitise; fn main() -> anyhow::Result<()> { @@ -17,7 +17,7 @@ fn main() -> anyhow::Result<()> { input::Args::parse() }; - let config : input::Config = { + let config: input::Config = { let config = fs::read_to_string(&args.config) .context("failed to read in podcast configuration file")?; @@ -48,7 +48,7 @@ fn main() -> anyhow::Result<()> { } }, Command::List { status, podcast } => { - let output = root.join(sanitise(&podcast)); + let output = folders::podcast_folder(root, &podcast); let spec_file = output.join("spec.toml"); let spec = download::Specification::read_from(&spec_file)?; @@ -64,7 +64,7 @@ fn main() -> anyhow::Result<()> { } }, Command::Mark { status, podcast } => { - let output = root.join(sanitise(&podcast)); + let output = folders::podcast_folder(root, &podcast); let spec_file = output.join("spec.toml"); let mut spec = download::Specification::read_from(&spec_file)?; diff --git a/src/rss.rs b/src/rss.rs index 57977bd..6177c22 100644 --- a/src/rss.rs +++ b/src/rss.rs @@ -8,55 +8,55 @@ use std::borrow::Cow; #[derive(Debug, serde::Deserialize)] pub struct Feed<'a> { - pub (crate) rss : Rss<'a>, + pub(crate) rss: Rss<'a>, } #[derive(Debug, serde::Deserialize)] pub struct Rss<'a> { - pub (crate) channel : Channel<'a>, + pub(crate) channel: Channel<'a>, } #[derive(Debug, serde::Deserialize)] pub struct Channel<'a> { #[serde(rename = "item", default)] - pub (crate) items : Vec>, - pub (crate) link : Option>, - pub (crate) title : Cow<'a, str>, - pub (crate) description : Option>, + pub(crate) items: Vec>, + pub(crate) link: Option>, + pub(crate) title: Cow<'a, str>, + pub(crate) description: Option>, #[serde(rename = "{http://www.itunes.com/dtds/podcast-1.0.dtd}itunes:author")] - pub (crate) author : Option>, + pub(crate) author: Option>, #[serde(rename = "{http://www.itunes.com/dtds/podcast-1.0.dtd}itunes:summary")] - pub (crate) summary : Option>, + pub(crate) summary: Option>, #[serde(rename = "{http://www.itunes.com/dtds/podcast-1.0.dtd}itunes:image")] - pub (crate) itunes_image : Option>, - pub (crate) image : Option>, + pub(crate) itunes_image: Option>, + pub(crate) image: Option>, } #[derive(Debug, serde::Deserialize)] pub struct Image<'a> { - pub (crate) link : Option>, - pub (crate) title : Cow<'a, str>, - pub (crate) url : Cow<'a, str>, + pub(crate) link: Option>, + pub(crate) title: Cow<'a, str>, + pub(crate) url: Cow<'a, str>, } #[derive(Debug, serde::Deserialize)] pub struct ItunesImage<'a> { #[serde(rename = "$attr:href")] - pub (crate) href : Cow<'a, str>, + pub(crate) href: Cow<'a, str>, } -fn deserialize_publish_date<'de, D : serde::de::Deserializer<'de>> ( - deserializer : D +fn deserialize_publish_date<'de, D: serde::de::Deserializer<'de>> ( + deserializer: D ) -> Result { struct Visitor; impl<'de> serde::de::Visitor<'de> for Visitor { type Value = chrono::NaiveDateTime; - fn expecting(&self, formatter : &mut fmt::Formatter) -> fmt::Result { + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a string containing json data") } - fn visit_str(self, input : &str) -> Result { + fn visit_str(self, input: &str) -> Result { chrono::NaiveDateTime::parse_from_str( input, "%a, %d %b %Y %H:%M:%S %Z" @@ -69,23 +69,23 @@ fn deserialize_publish_date<'de, D : serde::de::Deserializer<'de>> ( #[derive(Debug, serde::Deserialize)] pub struct Item<'a> { - pub (crate) title : Cow<'a, str>, - pub (crate) enclosure : Option>, - pub (crate) description : Option>, + pub(crate) title: Cow<'a, str>, + pub(crate) enclosure: Option>, + pub(crate) description: Option>, #[serde(rename = "{http://www.itunes.com/dtds/podcast-1.0.dtd}itunes:summary")] - pub (crate) summary : Option>, + pub(crate) summary: Option>, #[serde(rename = "pubDate", deserialize_with = "deserialize_publish_date")] - pub (crate) published : chrono::NaiveDateTime, - pub (crate) guid : Option>, + pub(crate) published: chrono::NaiveDateTime, + pub(crate) guid: Option>, } #[derive(Debug, serde::Deserialize)] pub struct Enclosure<'a> { #[serde(rename = "$attr:url")] - pub (crate) url : Cow<'a, str>, + pub(crate) url: Cow<'a, str>, #[serde(rename = "$attr:type")] - pub (crate) mime_type : Option>, + pub(crate) mime_type: Option>, //#[serde(rename = "$attr:length")] - //pub (crate) length : Option, + //pub(crate) length: Option, } diff --git a/src/tagging.rs b/src/tagging.rs index 9cd9232..38f0ccc 100644 --- a/src/tagging.rs +++ b/src/tagging.rs @@ -1,6 +1,6 @@ use std::{fs, path}; -use crate::download; +use crate::{folders, download}; use anyhow::Context; @@ -10,7 +10,7 @@ pub(crate) fn generate_m3u( alias: &str, root: &path::Path, ) -> anyhow::Result<()> { - let output = root.join(sanitise(&alias)); + let output = folders::podcast_folder(root, alias); let spec_file = output.join("spec.toml"); let spec = download::Specification::read_from(&spec_file)?; @@ -54,7 +54,7 @@ pub(crate) fn strip_tags( alias: &str, root: &path::Path, ) -> anyhow::Result<()> { - let output = root.join(sanitise(&alias)); + let output = folders::podcast_folder(root, alias); let spec_file = output.join("spec.toml"); let spec = download::Specification::read_from(&spec_file)?;