From f0c77aed295ef3a849ab47c77478541042db67f9 Mon Sep 17 00:00:00 2001 From: Aaron Manning Date: Tue, 7 Apr 2026 21:20:34 +0100 Subject: [PATCH] deleting and not generating empty unlistened playlists --- src/folders.rs | 3 +++ src/main.rs | 6 ++++++ src/playlist.rs | 34 +++++++++++++++++++++++++++++++--- src/sync.rs | 25 +++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 3 deletions(-) diff --git a/src/folders.rs b/src/folders.rs index 8cd7c0f..6d8e033 100644 --- a/src/folders.rs +++ b/src/folders.rs @@ -11,6 +11,9 @@ pub(crate) const IPOD_PODCASTS_DIR: &str = "Podcasts"; pub(crate) const LISTENED_PLAYLIST_PATH: &str = "[PC Meta] [Listened].m3u"; pub(crate) const MASTER_PLAYLIST_PATH: &str = "[PC Meta] [Master Feed].m3u"; +pub(crate) const PLAYLIST_PREFIX: &str = "[PC]"; +pub(crate) const UNLISTENED_SUFFIX: &str = "(unlistened)"; + pub(crate) fn podcast_folder( root: &path::Path, alias: &str, diff --git a/src/main.rs b/src/main.rs index 176f5d0..18259e6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -80,6 +80,9 @@ fn main() -> anyhow::Result<()> { spec.write_to(&spec_file)?; } Command::Playlist { podcast } => { + // Empty playlist folder. + // playlist::empty_playlists(root)?; + if let Some(alias) = podcast { playlist::generate_podcast_m3u(alias.as_str(), root, false)?; playlist::generate_podcast_m3u(alias.as_str(), root, true)?; @@ -106,6 +109,9 @@ fn main() -> anyhow::Result<()> { } } + // Empty playlist folder. + // playlist::empty_playlists(root)?; + // Generate updated playlist files for (alias, _) in &config.podcasts { playlist::generate_podcast_m3u(alias.as_str(), root, true)?; diff --git a/src/playlist.rs b/src/playlist.rs index b6a0876..7bbf673 100644 --- a/src/playlist.rs +++ b/src/playlist.rs @@ -29,6 +29,10 @@ impl<'a> Playlist<'a> { } } + fn is_empty(&self) -> bool { + self.files.is_empty() + } + /// Writes the playlist file based on the specified filename. /// /// Output boolean indicates if the playlist was written (if it was @@ -41,11 +45,21 @@ impl<'a> Playlist<'a> { let playlists_folder = self.root.join(folders::LOCAL_PLAYLISTS_DIR); if !playlists_folder.exists() { fs::create_dir(&playlists_folder) - .context(format!("failed to create output directory for playlists"))?; + .context("failed to create output directory for playlists")?; } let mut path = playlists_folder.join(sanitise(name)); path.set_extension("m3u"); + // If the playlist is empty, then don't write it, + // and delete the existing one if it exists. + if self.is_empty() { + if path.exists() { + fs::remove_file(&path) + .context("failed to remove existing playlist to empty")?; + } + return Ok(false) + } + let mut output = io::BufWriter::new(Vec::new()); let mut writer = m3u::Writer::new(&mut output); let entries = @@ -136,6 +150,20 @@ pub(crate) fn generate_master_m3u( Ok(()) } +/// Empties the playlist output folder. +pub(crate) fn empty_playlists( + root: &path::Path, +) -> anyhow::Result<()> { + let playlists_folder = root.join(folders::LOCAL_PLAYLISTS_DIR); + if playlists_folder.exists() { + fs::remove_dir_all(&playlists_folder) + .context("failed to remove output directory for playlists")?; + } + fs::create_dir(&playlists_folder) + .context("failed to create output directory for playlists")?; + Ok(()) +} + pub(crate) fn generate_podcast_m3u( alias: &str, @@ -170,9 +198,9 @@ pub(crate) fn generate_podcast_m3u( } let written = if unlistened_only { - playlist.write_as(&format!("[PC] {} (unlistened)", alias), false) + playlist.write_as(&format!("{} {} {}", folders::PLAYLIST_PREFIX, alias, folders::UNLISTENED_SUFFIX), false) } else { - playlist.write_as(&format!("[PC] {}", alias), false) + playlist.write_as(&format!("{} {}", folders::PLAYLIST_PREFIX, alias), false) }?; if written { diff --git a/src/sync.rs b/src/sync.rs index 4d61f56..e1e79e1 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -175,15 +175,40 @@ pub(crate) fn sync( anyhow::bail!("specified target directory does not contain a folder {:?}", folders::LOCAL_PLAYLISTS_DIR); } + // This is a bit of a hack, but we use the list of acknowledged + // files in the target to recognise which unlistened playlists + // should be deleted (because they don't exist in the local directory). + // This won't delete any other extra podcasts, and is just a temporary + // fix to make sure empty unlistened playlists don't exist. + let mut acknowledged = BTreeSet::new(); for source in fs::read_dir(root.join(folders::LOCAL_PLAYLISTS_DIR))? { let source = source?.path(); let target = target_dir .join(folders::LOCAL_PLAYLISTS_DIR) .join(source.file_name().unwrap()); + acknowledged.insert(target.clone()); + if !target.exists() || fs::metadata(&target)?.modified()? < fs::metadata(&source)?.modified()? { println!("[info] copying playlist {:?}.", source.file_name().unwrap()); fs::copy(source, target)?; + + } + } + + // Here we delete the excess unlistened playlists (if the don't exist + // in local directory, and thus correspond to empty playlists). + // + // This could very easily be edited to remove other excess playlists. + for target in fs::read_dir(target_dir.join(folders::LOCAL_PLAYLISTS_DIR))? { + let target = target?.path(); + let file_name = target.file_stem().unwrap(); + let file_name = file_name.to_str().unwrap(); + + if file_name.starts_with(folders::PLAYLIST_PREFIX) + && file_name.ends_with(folders::UNLISTENED_SUFFIX) + && !acknowledged.contains(&target) { + fs::remove_file(&target)?; } }