play status with corresponding commands
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
use std::fs;
|
||||
use std::path;
|
||||
use std::borrow::Cow;
|
||||
use std::iter::Iterator;
|
||||
use std::collections::{HashMap, BTreeMap, HashSet};
|
||||
|
||||
use anyhow::Context;
|
||||
@@ -9,14 +10,14 @@ use sanitise_file_name::sanitise;
|
||||
use crate::rss;
|
||||
|
||||
#[derive(Default, serde::Serialize, serde::Deserialize)]
|
||||
struct Specification<'a> {
|
||||
pub (crate) struct Specification<'a> {
|
||||
files : HashMap<Cow<'a, str>, Cow<'a, path::Path>>,
|
||||
feed : BTreeMap<chrono::NaiveDateTime, Vec<Episode<'a>>>,
|
||||
image_url : Option<Cow<'a, str>>,
|
||||
}
|
||||
|
||||
impl<'a> Specification<'a> {
|
||||
fn read_from(path : &path::Path) -> Result<Self, anyhow::Error> {
|
||||
pub (crate) fn read_from_with_default(path : &path::Path) -> Result<Self, anyhow::Error> {
|
||||
Ok(if path.is_file() {
|
||||
toml::from_str(&fs::read_to_string(&path)?[..])?
|
||||
} else {
|
||||
@@ -24,13 +25,29 @@ impl<'a> Specification<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
fn write_to(&self, path : &path::Path) -> Result<(), anyhow::Error> {
|
||||
pub (crate) fn read_from(path : &path::Path) -> Result<Self, anyhow::Error> {
|
||||
Ok(if path.is_file() {
|
||||
toml::from_str(&fs::read_to_string(&path)?[..])?
|
||||
} else {
|
||||
anyhow::bail!("could not find specification for the desired podcast")
|
||||
})
|
||||
}
|
||||
|
||||
pub (crate) fn write_to(&self, path : &path::Path) -> Result<(), anyhow::Error> {
|
||||
Ok(fs::write(path, toml::to_string(self)?.as_bytes())?)
|
||||
}
|
||||
|
||||
pub (crate) fn feed_iter(&self) -> impl Iterator<Item = (&chrono::NaiveDateTime, &Vec<Episode<'a>>)> {
|
||||
self.feed.iter()
|
||||
}
|
||||
|
||||
pub (crate) fn feed_iter_mut(&mut self) -> impl Iterator<Item = (&chrono::NaiveDateTime, &mut Vec<Episode<'a>>)> {
|
||||
self.feed.iter_mut()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
struct Episode<'a> {
|
||||
pub (crate) struct Episode<'a> {
|
||||
/// Episode title.
|
||||
title : Cow<'a, str>,
|
||||
/// Show notes pulled from description or summary tag.
|
||||
@@ -39,6 +56,15 @@ struct Episode<'a> {
|
||||
id : Cow<'a, str>,
|
||||
/// If the episode exists in the latest version of the feed.
|
||||
current : bool,
|
||||
/// Flag to keep track of which episodes have been listened to.
|
||||
#[serde(default)]
|
||||
pub (crate) listened : bool,
|
||||
}
|
||||
|
||||
impl<'a> Episode<'a> {
|
||||
pub (crate) fn title(&self) -> &str {
|
||||
self.title.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
fn download_to_file(url : &str, path : &path::Path) -> anyhow::Result<()> {
|
||||
@@ -175,7 +201,7 @@ pub (crate) fn update_podcast_from_feed(
|
||||
|
||||
let spec_file = output.join("spec.toml");
|
||||
|
||||
let mut spec = Specification::read_from(&spec_file)?;
|
||||
let mut spec = Specification::read_from_with_default(&spec_file)?;
|
||||
|
||||
// Get set of all currently available episodes such that we can later mark
|
||||
// any other episodes as unavailable
|
||||
@@ -278,6 +304,7 @@ pub (crate) fn update_podcast_from_feed(
|
||||
id : Cow::from(id.to_owned()),
|
||||
current : true,
|
||||
title,
|
||||
listened : false,
|
||||
};
|
||||
|
||||
match spec.feed.get_mut(&item.published) {
|
||||
|
39
src/input.rs
39
src/input.rs
@@ -6,9 +6,42 @@ pub (crate) struct Args {
|
||||
/// Path to the configuration file listing podcast RSS feeds.
|
||||
#[arg(default_value = "./podcasts.toml")]
|
||||
pub (crate) config : path::PathBuf,
|
||||
/// The podcast to update. Updates all in configuration file if unspecified.
|
||||
#[arg(long, short)]
|
||||
pub (crate) podcast : Option<String>,
|
||||
#[command(subcommand)]
|
||||
pub (crate) command : Command,
|
||||
}
|
||||
|
||||
#[derive(clap::ValueEnum, Copy, Clone, Debug,PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub (crate) enum ListenStatus {
|
||||
Listened,
|
||||
Unlistened,
|
||||
}
|
||||
|
||||
#[derive(clap::Subcommand)]
|
||||
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<String>,
|
||||
},
|
||||
/// 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<ListenStatus>,
|
||||
/// The podcast to list episodes for.
|
||||
#[arg(long, short)]
|
||||
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,
|
||||
/// The podcast to change the listen status of.
|
||||
#[arg(long, short)]
|
||||
podcast : String,
|
||||
},
|
||||
}
|
||||
|
||||
/// Struct modelling configuration file format.
|
||||
|
69
src/main.rs
69
src/main.rs
@@ -2,9 +2,12 @@ mod rss;
|
||||
mod input;
|
||||
mod download;
|
||||
|
||||
use input::{Command, ListenStatus};
|
||||
|
||||
use std::fs;
|
||||
|
||||
use anyhow::Context;
|
||||
use sanitise_file_name::sanitise;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
|
||||
@@ -25,23 +28,59 @@ fn main() -> anyhow::Result<()> {
|
||||
anyhow::bail!("could not get parent of configuration path for root directory")
|
||||
};
|
||||
|
||||
// Updating single podcast
|
||||
if let Some(alias) = args.podcast {
|
||||
if let Some(feed_url) = config.podcasts.get(&alias) {
|
||||
download::update_podcast(&alias, root, feed_url)?;
|
||||
}
|
||||
else {
|
||||
anyhow::bail!(r#"podcast "{}" not found in configuration file"#, alias)
|
||||
}
|
||||
}
|
||||
// Updating all podcasts
|
||||
else {
|
||||
for (alias, feed_url) in config.podcasts {
|
||||
download::update_podcast(&alias, root, &feed_url)?;
|
||||
}
|
||||
}
|
||||
|
||||
match args.command {
|
||||
Command::Download { podcast } => {
|
||||
// Updating single podcast
|
||||
if let Some(alias) = podcast {
|
||||
if let Some(feed_url) = config.podcasts.get(&alias) {
|
||||
download::update_podcast(&alias, root, feed_url)?;
|
||||
}
|
||||
else {
|
||||
anyhow::bail!(r#"podcast "{}" not found in configuration file"#, alias)
|
||||
}
|
||||
}
|
||||
// Updating all podcasts
|
||||
else {
|
||||
for (alias, feed_url) in config.podcasts {
|
||||
download::update_podcast(&alias, root, &feed_url)?;
|
||||
}
|
||||
}
|
||||
},
|
||||
Command::List { status, podcast } => {
|
||||
let output = root.join(sanitise(&podcast));
|
||||
let spec_file = output.join("spec.toml");
|
||||
|
||||
let spec = download::Specification::read_from(&spec_file)?;
|
||||
|
||||
for (_, episodes) in spec.feed_iter() {
|
||||
for episode in episodes {
|
||||
if status.is_none()
|
||||
|| (episode.listened && status.is_some_and(|x| x == ListenStatus::Listened))
|
||||
|| (!episode.listened && status.is_some_and(|x| x == ListenStatus::Unlistened)) {
|
||||
println!("{}", episode.title())
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Command::Mark { status, podcast } => {
|
||||
let output = root.join(sanitise(&podcast));
|
||||
let spec_file = output.join("spec.toml");
|
||||
|
||||
let mut spec = download::Specification::read_from(&spec_file)?;
|
||||
|
||||
for (_, episodes) in spec.feed_iter_mut() {
|
||||
for episode in episodes {
|
||||
episode.listened = status == ListenStatus::Listened;
|
||||
}
|
||||
}
|
||||
|
||||
spec.write_to(&spec_file)?;
|
||||
},
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user