// yt - A fully featured command line YouTube client // // Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de> // SPDX-License-Identifier: GPL-3.0-or-later // // This file is part of Yt. // // You should have received a copy of the License along with this program. // If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. use std::{ env::{self}, fs, io::{BufRead, Write}, io::{BufReader, BufWriter}, }; use crate::{ app::App, cli::CliArgs, constants::HELP_STR, storage::video_database::{getters::get_videos, VideoStatus}, }; use anyhow::{bail, Context, Result}; use clap::Parser; use cmds::handle_select_cmd; use futures::future::join_all; use selection_file::process_line; use tempfile::Builder; use tokio::process::Command; pub mod cmds; pub mod selection_file; pub async fn select(app: &App, done: bool, use_last_selection: bool) -> Result<()> { let temp_file = Builder::new() .prefix("yt_video_select-") .suffix(".yts") .rand_bytes(6) .tempfile() .context("Failed to get tempfile")?; if use_last_selection { fs::copy(&app.config.paths.last_selection_path, &temp_file)?; } else { let matching_videos = if done { get_videos(app, VideoStatus::ALL, None).await? } else { get_videos( app, &[ VideoStatus::Pick, // VideoStatus::Watch, VideoStatus::Cached, ], None, ) .await? }; // Warmup the cache for the display rendering of the videos. // Otherwise the futures would all try to warm it up at the same time. if let Some(vid) = matching_videos.get(0) { let _ = vid.to_select_file_display(app).await?; } let mut edit_file = BufWriter::new(&temp_file); join_all( matching_videos .iter() .map(|vid| async { vid.to_select_file_display(app).await }) .collect::<Vec<_>>(), ) .await .into_iter() .try_for_each(|line| -> Result<()> { let line = line?; edit_file .write_all(line.as_bytes()) .expect("This write should not fail"); Ok(()) })?; edit_file.write_all(HELP_STR.as_bytes())?; edit_file.flush().context("Failed to flush edit file")?; }; { let editor = env::var("EDITOR").unwrap_or("nvim".to_owned()); let mut nvim = Command::new(editor); nvim.arg(temp_file.path()); let status = nvim.status().await.context("Falied to run nvim")?; if !status.success() { bail!("nvim exited with error status: {}", status) } } let read_file = temp_file.reopen()?; fs::copy(temp_file.path(), &app.config.paths.last_selection_path) .context("Failed to persist selection file")?; let reader = BufReader::new(&read_file); let mut line_number = 0; for line in reader.lines() { let line = line.context("Failed to read a line")?; if let Some(line) = process_line(&line)? { line_number -= 1; // debug!( // "Parsed command: `{}`", // line.iter() // .map(|val| format!("\"{}\"", val)) // .collect::<Vec<String>>() // .join(" ") // ); let arg_line = ["yt", "select"] .into_iter() .chain(line.iter().map(|val| val.as_str())); let args = CliArgs::parse_from(arg_line); let cmd = if let crate::cli::Command::Select { cmd } = args.command.expect("This will be some") { cmd } else { unreachable!("This is checked in the `filter_line` function") }; handle_select_cmd( &app, cmd.expect("This value should always be some here"), Some(line_number), ) .await? } } Ok(()) } // // FIXME: There should be no reason why we need to re-run yt, just to get the help string. But I've // // yet to find a way to do it with out the extra exec <2024-08-20> // async fn get_help() -> Result<String> { // let binary_name = current_exe()?; // let cmd = Command::new(binary_name) // .args(&["select", "--help"]) // .output() // .await?; // // assert_eq!(cmd.status.code(), Some(0)); // // let output = String::from_utf8(cmd.stdout).expect("Our help output was not utf8?"); // // let out = output // .lines() // .map(|line| format!("# {}\n", line)) // .collect::<String>(); // // debug!("Returning help: '{}'", &out); // // Ok(out) // }