aboutsummaryrefslogtreecommitdiffstats
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/yt/src/cli.rs5
-rw-r--r--crates/yt/src/main.rs12
-rw-r--r--crates/yt/src/select/mod.rs103
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<()> {