From ce1bfc8cfb5b63f4acfbcf801fc363fd39dbc650 Mon Sep 17 00:00:00 2001 From: Tor Date: Tue, 26 Mar 2024 02:37:26 +0100 Subject: [PATCH] wip --- Cargo.lock | 188 ++++++++++++++++++++++++++++-- Cargo.toml | 4 +- default.nix | 25 ++++ shell.nix | 1 - src/config.rs | 75 ++++++++++++ src/main.rs | 315 ++++++++++++++++++++------------------------------ 6 files changed, 405 insertions(+), 203 deletions(-) create mode 100644 default.nix create mode 100644 src/config.rs diff --git a/Cargo.lock b/Cargo.lock index a9ba4e5..acd09f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" + [[package]] name = "atom_syndication" version = "0.12.2" @@ -48,6 +54,18 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + [[package]] name = "bumpalo" version = "3.15.4" @@ -77,7 +95,7 @@ dependencies = [ "js-sys", "num-traits", "wasm-bindgen", - "windows-targets", + "windows-targets 0.52.4", ] [[package]] @@ -97,9 +115,11 @@ dependencies = [ [[package]] name = "cringecast" -version = "0.1.0" +version = "0.1.1" dependencies = [ + "anyhow", "chrono", + "dirs", "rss", "serde", "toml", @@ -181,6 +201,27 @@ dependencies = [ "chrono", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "encoding_rs" version = "0.8.33" @@ -280,6 +321,17 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.5.0", + "libc", + "redox_syscall", +] + [[package]] name = "log" version = "0.4.21" @@ -322,6 +374,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -356,6 +414,26 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + [[package]] name = "ring" version = "0.17.8" @@ -368,7 +446,7 @@ dependencies = [ "libc", "spin", "untrusted", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -474,6 +552,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -628,7 +726,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets", + "windows-targets 0.52.4", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", ] [[package]] @@ -637,7 +744,22 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.4", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -646,51 +768,93 @@ version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.4" diff --git a/Cargo.toml b/Cargo.toml index bef9b92..53cfd69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cringecast" -version = "0.1.0" +version = "0.1.1" edition = "2021" description = "Simple podcast manager for your terminal" license = "MIT" @@ -13,4 +13,6 @@ rss = "2.0.7" ureq = "2.9.6" toml = "0.5" serde = { version = "1.0", features = ["derive"] } +anyhow = "1.0.81" +dirs = "5.0.1" diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..a8dff00 --- /dev/null +++ b/default.nix @@ -0,0 +1,25 @@ +{ pkgs ? import {}, fetchFromGitHub ? pkgs.fetchFromGitHub }: + +pkgs.rustPlatform.buildRustPackage rec { + pname = "cringecast"; + version = "0.1.0"; + + src = fetchFromGitHub { + owner = "tbs1996"; + repo = pname; + rev = "117b01c6f6d723d930290aef7d73f24edee19247"; + sha256 = "0000000000000000000000000000000000000000000000000000"; + }; + + cargoSha256 = "0000000000000000000000000000000000000000000000000000"; + + buildInputs = [ pkgs.openssl pkgs.pkg-config ]; + + meta = with pkgs.lib; { + description = "simple cli podcast manager"; + homepage = "https://github.com/tbs1996/cringecast"; + license = licenses.mit; + maintainers = with maintainers; [ maintainers. ]; + }; +} + diff --git a/shell.nix b/shell.nix index 10ceeaf..7a03836 100644 --- a/shell.nix +++ b/shell.nix @@ -8,7 +8,6 @@ pkgs.mkShell { pkgs.cargo pkgs.rustup pkgs.rust-analyzer - # other dependencies... ]; } diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..6b956d6 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,75 @@ +use serde::{Deserialize, Serialize}; +use std::io::Write; +use std::path::PathBuf; +use std::sync::Arc; + +use anyhow::Result; + +#[derive(Clone)] +pub struct CombinedConfig { + global: Arc, + specific: PodcastConfig, +} + +impl CombinedConfig { + pub fn new(global: Arc, specific: PodcastConfig) -> Self { + Self { global, specific } + } + + pub fn max_age(&self) -> Option { + self.specific.max_age.or(self.global.max_age) + } + + pub fn base_path(&self) -> PathBuf { + PathBuf::from(self.specific.path.as_ref().unwrap_or(&self.global.path)) + } + + pub fn url(&self) -> &str { + &self.specific.url + } +} + +#[derive(Deserialize, Debug, Clone)] +pub struct PodcastConfig { + url: String, + max_age: Option, + path: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct GlobalConfig { + max_age: Option, + path: String, +} + +impl GlobalConfig { + pub fn load() -> Result { + let p = dirs::config_dir() + .ok_or(anyhow::Error::msg("no config dir found"))? + .join("config.toml"); + + if !p.exists() { + let default = Self::default(); + let s = toml::to_string_pretty(&default)?; + let mut f = std::fs::File::create(&p)?; + f.write_all(s.as_bytes())?; + } + + let str = std::fs::read_to_string(p)?; + + Ok(toml::from_str(&str)?) + } +} + +impl Default for GlobalConfig { + fn default() -> Self { + Self { + max_age: Some(90), + path: dirs::home_dir() + .expect("home dir not found") + .join("cringecast") + .to_string_lossy() + .to_string(), + } + } +} diff --git a/src/main.rs b/src/main.rs index 899e158..a8f31ae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,225 +1,165 @@ -use serde::{Deserialize, Serialize}; use std::io::Write; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; +use crate::config::{CombinedConfig, GlobalConfig, PodcastConfig}; + use std::collections::{HashMap, HashSet}; -fn main() { - let global_config = Arc::new(GlobalConfig::load()); - let podcasts = Podcast::load_all(); +use anyhow::Result; - let mut handles = vec![]; - for podcast in podcasts { - let config = Arc::clone(&global_config); +mod config; - handles.push(std::thread::spawn(move || { - podcast.sync(config); - })); - } +fn main() -> Result<()> { + let global_config = Arc::new(GlobalConfig::load()?); + let podcasts = Podcast::load_all(global_config)?; - for handle in handles { - handle.join().unwrap(); + for podcast in podcasts { + podcast.sync()?; } -} - -fn home() -> PathBuf { - PathBuf::from(std::env::var("HOME").unwrap()) -} - -fn config_dir() -> PathBuf { - let p = home().join(".config").join("cringecast"); - std::fs::create_dir_all(&p).unwrap(); - p -} -fn channel_path() -> PathBuf { - config_dir().join("channels.toml") + Ok(()) } -#[derive(Clone)] -struct CombinedConfig<'a> { - global: Arc, - specific: &'a PodcastConfig, +fn channel_path() -> Result { + Ok(dirs::config_dir() + .ok_or(anyhow::Error::msg("no config dir found"))? + .join("cringecast") + .join("channels.toml")) } -impl<'a> CombinedConfig<'a> { - fn new(global: Arc, specific: &'a PodcastConfig) -> Self { - Self { global, specific } - } - - fn max_age(&self) -> Option { - self.specific.max_age.or(self.global.max_age) - } - - fn base_path(&self) -> PathBuf { - PathBuf::from(self.specific.path.as_ref().unwrap_or(&self.global.path)) - } -} - -#[derive(Deserialize, Debug, Clone)] -struct PodcastConfig { +struct Episode { + title: String, url: String, - max_age: Option, - path: Option, + guid: String, + published: i64, } -#[derive(Serialize, Deserialize, Debug)] -struct GlobalConfig { - max_age: Option, - path: String, -} - -impl Default for GlobalConfig { - fn default() -> Self { - Self { - max_age: Some(120), - path: home().join("cringecast").to_string_lossy().to_string(), - } - } -} - -impl GlobalConfig { - fn load() -> Self { - let p = config_dir().join("config.toml"); - - if !p.exists() { - let default = Self::default(); - let s = toml::to_string_pretty(&default).unwrap(); - let mut f = std::fs::File::create(&p).unwrap(); - f.write_all(s.as_bytes()).unwrap(); - } - - let str = std::fs::read_to_string(p).unwrap(); - toml::from_str(&str).unwrap() - } -} - -struct Episode(rss::Item); - impl Episode { - fn title(&self) -> &str { - self.0.title().unwrap() - } - - fn url(&self) -> &str { - self.0.enclosure().unwrap().url() - } - - fn guid(&self) -> &str { - self.0.guid().unwrap().value() - } - - fn download(&self, folder: &Path) { - let url = self.url(); - let title = self.title(); - let file_name = title.replace(" ", "_") + ".mp3"; - let destination = folder.join(file_name); - - let response = ureq::get(&url).call().unwrap(); - let mut reader = response.into_reader(); - let mut file = std::fs::File::create(destination).unwrap(); - std::io::copy(&mut reader, &mut file).unwrap(); - } - - fn should_download(&self, config: &CombinedConfig, downloaded: &DownloadedEpisodes) -> bool { - if downloaded.contains_episode(&self.0) { - return false; - } - - if let Some(max_age) = config.max_age() { - let pub_date = self.0.pub_date().unwrap(); - let published_unix = chrono::DateTime::parse_from_rfc2822(pub_date) - .unwrap() - .timestamp(); - - let current_unix = chrono::Utc::now().timestamp(); - - if (current_unix - published_unix) > max_age as i64 * 86400 { - return false; - } - } + fn new(item: rss::Item) -> Result { + Ok(Self { + title: item + .title() + .ok_or(anyhow::Error::msg("title not found"))? + .to_owned(), + url: item + .enclosure() + .ok_or(anyhow::Error::msg("enclosure not found"))? + .url() + .to_owned(), + guid: item + .guid() + .ok_or(anyhow::Error::msg("guid not found"))? + .value() + .to_string(), + published: chrono::DateTime::parse_from_rfc2822( + item.pub_date() + .ok_or(anyhow::Error::msg("published date not found"))?, + )? + .timestamp(), + }) + } + + fn download(&self, folder: &Path) -> Result<()> { + let mut reader = ureq::get(&self.url).call()?.into_reader(); + + let mut file = { + let file_name = self.title.replace(" ", "_") + ".mp3"; + let file_path = folder.join(file_name); + std::fs::File::create(&file_path)? + }; - true + std::io::copy(&mut reader, &mut file)?; + Ok(()) } } struct Podcast { name: String, - config: PodcastConfig, + config: CombinedConfig, + downloaded: DownloadedEpisodes, } impl Podcast { - fn load_all() -> Vec { - let path = channel_path(); - let config_str = std::fs::read_to_string(path).expect("Failed to read config file"); + fn load_all(global_config: Arc) -> Result> { + let configs: HashMap = { + let config_str = std::fs::read_to_string(channel_path()?)?; + toml::from_str(&config_str)? + }; let mut podcasts = vec![]; - - let configs: HashMap = toml::from_str(&config_str).unwrap(); - for (name, config) in configs { - podcasts.push(Self { name, config }); + let config = CombinedConfig::new(Arc::clone(&global_config), config); + let downloaded = DownloadedEpisodes::load(&name, &config)?; + + podcasts.push(Self { + name, + config, + downloaded, + }); } - podcasts - } - - fn combined_config(&self, global_config: Arc) -> CombinedConfig { - CombinedConfig::new(global_config, &self.config) + Ok(podcasts) } - fn load_episodes(&self) -> Vec { - let agent = ureq::builder().build(); - - let mut reader = agent.get(&self.config.url).call().unwrap().into_reader(); - let mut data = vec![]; + fn load_episodes(&self) -> Result> { + let data = { + let mut reader = ureq::agent().get(&self.config.url()).call()?.into_reader(); + let mut data = vec![]; + reader.read_to_end(&mut data)?; + data + }; - reader.read_to_end(&mut data).unwrap(); - let channel = rss::Channel::read_from(&data[..]).unwrap(); - let items = channel.into_items(); - let mut episodes = vec![]; + rss::Channel::read_from(data.as_slice())? + .into_items() + .into_iter() + .map(Episode::new) + .collect() + } - for item in items { - episodes.push(Episode(item)); - } + fn download_folder(&self) -> Result { + let destination_folder = self.config.base_path().join(&self.name); + std::fs::create_dir_all(&destination_folder)?; + Ok(destination_folder) + } - episodes + fn should_download(&self, episode: &Episode) -> bool { + self.downloaded.contains_episode(episode) + || self.config.max_age().is_some_and(|max_age| { + (chrono::Utc::now().timestamp() - episode.published) < max_age as i64 * 86400 + }) } - fn download_folder(&self, config: &CombinedConfig) -> PathBuf { - let destination_folder = config.base_path().join(&self.name); - std::fs::create_dir_all(&destination_folder).unwrap(); - destination_folder + fn mark_downloaded(&self, episode: &Episode) -> Result<()> { + DownloadedEpisodes::append(&self.name, &self.config, &episode)?; + Ok(()) } - fn sync(&self, global_config: Arc) { + fn sync(&self) -> Result<()> { println!("Syncing {}", &self.name); - let config = self.combined_config(global_config); - let downloaded = DownloadedEpisodes::load(&self.name, &config); - let download_folder = self.download_folder(&config); - let episodes: Vec = self - .load_episodes() + .load_episodes()? .into_iter() - .filter(|episode| episode.should_download(&config, &downloaded)) + .filter(|episode| self.should_download(episode)) .collect(); if episodes.is_empty() { - println!("nothing to download!"); - } else { - println!("downloading {} episodes!", episodes.len()); - - for episode in episodes { - println!("downloading {}", episode.title()); - episode.download(download_folder.as_path()); - DownloadedEpisodes::append(&self.name, &config, &episode); - } + println!("Nothing to download."); + return Ok(()); } - println!("all done!"); + println!("downloading {} episodes!", episodes.len()); + + for episode in episodes { + println!("downloading {}", &episode.title); + + episode.download(self.download_folder()?.as_path())?; + self.mark_downloaded(&episode)?; + } + + Ok(()) } } @@ -228,41 +168,38 @@ impl Podcast { struct DownloadedEpisodes(HashSet); impl DownloadedEpisodes { - fn contains_episode(&self, item: &rss::Item) -> bool { - self.0.contains(item.guid().unwrap().value()) + fn contains_episode(&self, episode: &Episode) -> bool { + self.0.contains(&episode.guid) } - fn load(name: &str, config: &CombinedConfig) -> Self { - let path = Self::file_path(&config, name); + fn load(name: &str, config: &CombinedConfig) -> Result { + let path = Self::file_path(config, name); let s = match std::fs::read_to_string(path) { Ok(s) => s, Err(e) if e.kind() == std::io::ErrorKind::NotFound => { - return Self::default(); + return Ok(Self::default()); } - Err(_e) => panic!(), + e @ Err(_) => e?, }; - let set: HashSet = s - .lines() - .filter_map(|line| line.split_whitespace().next().map(String::from)) - .collect(); - - Self(set) + Ok(Self( + s.lines() + .filter_map(|line| line.split_whitespace().next().map(String::from)) + .collect(), + )) } - fn append(name: &str, config: &CombinedConfig, episode: &Episode) { + fn append(name: &str, config: &CombinedConfig, episode: &Episode) -> Result<()> { let path = Self::file_path(config, name); let mut file = std::fs::OpenOptions::new() .append(true) .create(true) - .open(path) - .unwrap(); - - let line = Self::format(&episode); + .open(path)?; - writeln!(file, "{}", line).unwrap(); + writeln!(file, "{}", Self::format(&episode))?; + Ok(()) } fn file_path(config: &CombinedConfig, pod_name: &str) -> PathBuf { @@ -270,6 +207,6 @@ impl DownloadedEpisodes { } fn format(episode: &Episode) -> String { - format!("{} \"{}\"", episode.guid(), episode.title()) + format!("{} \"{}\"", &episode.guid, &episode.title) } }