diff options
author | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2025-06-15 17:33:47 +0200 |
---|---|---|
committer | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2025-06-15 17:33:47 +0200 |
commit | e2d5dc6a9f000a875c3f2a100f660adc2a43275a (patch) | |
tree | 2f32ab1a434edb88361a6248c0378d88d71a8942 | |
parent | refactor(yt/select): Split the `select::select` function up (diff) | |
download | yt-e2d5dc6a9f000a875c3f2a100f660adc2a43275a.zip |
feat(yt/select): Support a directory selection process
The single file approach becomes unwieldy once one has more open videos.
-rw-r--r-- | crates/yt/src/cli.rs | 5 | ||||
-rw-r--r-- | crates/yt/src/main.rs | 12 | ||||
-rw-r--r-- | crates/yt/src/select/mod.rs | 103 |
3 files changed, 110 insertions, 10 deletions
diff --git a/crates/yt/src/cli.rs b/crates/yt/src/cli.rs index 11346b4..43df15f 100644 --- a/crates/yt/src/cli.rs +++ b/crates/yt/src/cli.rs @@ -281,6 +281,10 @@ pub enum SelectCommand { #[arg(long, short)] done: bool, + /// Generate a directory, where each file contains only one subscription. + #[arg(long, short, conflicts_with = "use_last_selection")] + split: bool, + /// Use the last selection file (useful if you've spend time on it and want to get it again) #[arg(long, short, conflicts_with = "done")] use_last_selection: bool, @@ -344,6 +348,7 @@ impl Default for SelectCommand { Self::File { done: false, use_last_selection: false, + split: false, } } } diff --git a/crates/yt/src/main.rs b/crates/yt/src/main.rs index 39f52f4..e17f958 100644 --- a/crates/yt/src/main.rs +++ b/crates/yt/src/main.rs @@ -115,12 +115,20 @@ async fn main() -> Result<()> { SelectCommand::File { done, use_last_selection, - } => Box::pin(select::select(&app, done, use_last_selection)).await?, + split, + } => { + if split { + assert!(!use_last_selection); + Box::pin(select::select_split(&app, done)).await? + } else { + Box::pin(select::select_file(&app, done, use_last_selection)).await? + } + } _ => Box::pin(handle_select_cmd(&app, cmd, None)).await?, } } Command::Sedowa {} => { - Box::pin(select::select(&app, false, false)).await?; + Box::pin(select::select_file(&app, false, false)).await?; let arc_app = Arc::new(app); dowa(arc_app).await?; diff --git a/crates/yt/src/select/mod.rs b/crates/yt/src/select/mod.rs index 45aa05c..668ab02 100644 --- a/crates/yt/src/select/mod.rs +++ b/crates/yt/src/select/mod.rs @@ -10,9 +10,12 @@ // If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. use std::{ + collections::HashMap, env::{self}, - fs, + fs::{self, File}, io::{BufRead, BufReader, BufWriter, Write}, + iter, + path::Path, string::String, }; @@ -28,6 +31,7 @@ use anyhow::{Context, Result, bail}; use clap::Parser; use cmds::handle_select_cmd; use futures::{TryStreamExt, stream::FuturesOrdered}; +use log::info; use selection_file::process_line; use tempfile::Builder; use tokio::process::Command; @@ -35,11 +39,92 @@ use tokio::process::Command; pub mod cmds; pub mod selection_file; -async fn to_select_file_display_owned(video: Video, app: &App) -> Result<String> { - video.to_select_file_display(app).await +pub async fn select_split(app: &App, done: bool) -> Result<()> { + let temp_dir = Builder::new() + .prefix("yt_video_select-") + .rand_bytes(6) + .tempdir() + .context("Failed to get tempdir")?; + + let matching_videos = get_videos(app, done).await?; + + let mut no_author = vec![]; + let mut author_map = HashMap::new(); + for video in matching_videos { + if let Some(sub) = &video.parent_subscription_name { + if author_map.contains_key(sub) { + let vec: &mut Vec<_> = author_map + .get_mut(sub) + .unreachable("This key is set, we checked in the if above"); + + vec.push(video); + } else { + author_map.insert(sub.to_owned(), vec![video]); + } + } else { + no_author.push(video); + } + } + + let author_map = { + let mut temp_vec: Vec<_> = author_map.into_iter().collect(); + + // PERFORMANCE: The clone here should not be neeed. <2025-06-15> + temp_vec.sort_by_key(|(name, _)| name.to_owned()); + + temp_vec + }; + + for (index, (name, videos)) in author_map + .into_iter() + .chain(iter::once(( + "<No parent subscription>".to_owned(), + no_author, + ))) + .enumerate() + { + let mut file_path = temp_dir.path().join(format!("{index:02}_{name}")); + file_path.set_extension("yts"); + + let tmp_file = File::create(&file_path) + .with_context(|| format!("Falied to create file at: {}", file_path.display()))?; + + write_videos_to_file(app, &tmp_file, &videos) + .await + .with_context(|| format!("Falied to populate file at: {}", file_path.display()))?; + } + + open_editor_at(temp_dir.path()).await?; + + let mut paths = vec![]; + for maybe_entry in temp_dir + .path() + .read_dir() + .context("Failed to open temp dir for reading")? + { + let entry = maybe_entry.context("Failed to read entry in temp dir")?; + + if !entry.file_type()?.is_file() { + bail!("Found non-file entry: {}", entry.path().display()); + } + + paths.push(entry.path()); + } + + paths.sort(); + + let mut processed = 0; + for path in paths { + let read_file = File::open(path)?; + processed = process_file(app, &read_file, processed).await?; + } + + info!("Processed {processed} records."); + temp_dir.close().context("Failed to close the temp dir")?; + Ok(()) } -pub async fn select(app: &App, done: bool, use_last_selection: bool) -> Result<()> { +pub async fn select_file(app: &App, done: bool, use_last_selection: bool) -> Result<()> { let temp_file = Builder::new() .prefix("yt_video_select-") .suffix(".yts") @@ -61,7 +146,8 @@ pub async fn select(app: &App, done: bool, use_last_selection: bool) -> Result<( fs::copy(temp_file.path(), &app.config.paths.last_selection_path) .context("Failed to persist selection file")?; - process_file(app, &read_file).await?; + let processed = process_file(app, &read_file, 0).await?; + info!("Processed {processed} records."); Ok(()) } @@ -113,10 +199,11 @@ async fn write_videos_to_file(app: &App, file: &File, videos: &[Video]) -> Resul Ok(()) } -async fn process_file(app: &App, file: &File) -> Result<()> { +async fn process_file(app: &App, file: &File, processed: i64) -> Result<i64> { let reader = BufReader::new(file); - let mut line_number = 0; + let mut line_number = -processed; + for line in reader.lines() { let line = line.context("Failed to read a line")?; @@ -156,7 +243,7 @@ async fn process_file(app: &App, file: &File) -> Result<()> { } } - Ok(()) + Ok(line_number * -1) } async fn open_editor_at(path: &Path) -> Result<()> { |