about summary refs log tree commit diff stats
path: root/src/select/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/select/mod.rs')
-rw-r--r--src/select/mod.rs184
1 files changed, 184 insertions, 0 deletions
diff --git a/src/select/mod.rs b/src/select/mod.rs
new file mode 100644
index 0000000..6774ce6
--- /dev/null
+++ b/src/select/mod.rs
@@ -0,0 +1,184 @@
+// 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)
+// }