// 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::{last_select, 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) -> Result<()> {
let matching_videos = if done {
get_videos(
app,
&[
VideoStatus::Pick,
//
VideoStatus::Watch,
VideoStatus::Cached,
VideoStatus::Watched,
//
VideoStatus::Drop,
VideoStatus::Dropped,
],
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 lines: Vec<String> = join_all(
matching_videos
.iter()
.map(|vid| async { vid.to_select_file_display(app).await })
.collect::<Vec<_>>(),
)
.await
.into_iter()
.collect::<Result<Vec<String>>>()?;
let temp_file = Builder::new()
.prefix("yt_video_select-")
.suffix(".yts")
.rand_bytes(6)
.tempfile()
.context("Failed to get tempfile")?;
{
let mut edit_file = BufWriter::new(&temp_file);
lines.iter().for_each(|line| {
edit_file
.write_all(line.as_bytes())
.expect("This write should not fail");
});
// edit_file.write_all(get_help().await?.as_bytes())?;
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(),
last_select().context("Failed to get the persistent selection file 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
// // jet 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)
// }