<commit_before>use std::collections::HashMap;
use std::env::{current_dir, vars};
use std::io::{self, Write};

use anyhow::{bail, Context, Result};
use log::error;

use pueue::message::*;
use pueue::platform::socket::*;
use pueue::protocol::*;
use pueue::settings::Settings;

use crate::cli::{CliArguments, SubCommand};
use crate::commands::edit::*;
use crate::commands::local_follow::*;
use crate::commands::restart::*;
use crate::output::*;

/// This struct contains the base logic for the client.
/// The client is responsible for connecting to the daemon, sending instructions
/// and interpreting their responses.
///
/// Most commands are a simple ping-pong. However, some commands require a more complex
/// communication pattern, such as the `follow` command, which can read local files,
/// or the `edit` command, which needs to open an editor locally.
pub struct Client {
    opt: CliArguments,
    settings: Settings,
    socket: Socket,
}

impl Client {
    /// Connect to the daemon, authorize via secret and return a new initialized Client.
    pub async fn new(settings: Settings, opt: CliArguments) -> Result<Self> {
        // // Commandline argument overwrites the configuration files values for address
        // let address = if let Some(address) = opt.address.clone() {
        //     address
        // } else {
        //     settings.client.daemon_address
        // };

        // Commandline argument overwrites the configuration files values for port
        let (unix_socket_path, port) = {
            // Always prefer commandline options
            if let Some(path) = opt.unix_socket_path.clone() {
                (Some(path), None)
            } else if let Some(port) = opt.port.clone() {
                (None, Some(port))
            } else if settings.shared.use_unix_socket {
                (Some(settings.shared.unix_socket_path.clone()), None)
            } else {
                (None, Some(settings.shared.port.clone()))
            }
        };

        let mut socket = get_client(unix_socket_path, port).await?;

        // Send the secret to the daemon
        // In case everything was successful, we get a short `hello` response from the daemon.
        let secret = settings.shared.secret.clone().into_bytes();
        send_bytes(&secret, &mut socket).await?;
        let hello = receive_bytes(&mut socket).await?;
        if hello != b"hello" {
            bail!("Daemon went away after initial connection. Did you use the correct secret?")
        }

        Ok(Client {
            opt,
            settings,
            socket,
        })
    }

    /// This is the function where the actual communication and logic starts.
    /// At this point everything is initialized, the connection is up and
    /// we can finally start doing stuff.
    ///
    /// The command handling is splitted into "simple" and "complex" commands.
    pub async fn start(&mut self) -> Result<()> {
        // Return early, if the command has already been handled.
        if self.handle_complex_command().await? {
            return Ok(());
        }

        // The handling of "generic" commands is encapsulated in this function.
        self.handle_simple_command().await?;

        Ok(())
    }

    /// Handle all complex client-side functionalities.
    /// Complex functionalities need some special handling and are contained
    /// in their own functions with their own communication code.
    /// Such functionalities includes reading local filestand sending multiple messages.
    ///
    /// Returns `Ok(true)`, if the current command has been handled by this function.
    /// This indicates that the client can now shut down.
    /// If `Ok(false)` is returned, the client will continue and handle the Subcommand in the
    /// [handle_simple_command] function.
    async fn handle_complex_command(&mut self) -> Result<bool> {
        // This match handles all "complex" commands.
        match &self.opt.cmd {
            SubCommand::Edit { task_id, path } => {
                let message = edit(&mut self.socket, *task_id, *path).await?;
                self.handle_response(message);
                Ok(true)
            }
            SubCommand::Restart {
                task_ids,
                start_immediately,
                stashed,
                edit,
                path,
            } => {
                restart(
                    &mut self.socket,
                    task_ids.clone(),
                    *start_immediately,
                    *stashed,
                    *edit,
                    *path,
                )
                .await?;
                Ok(true)
            }
            SubCommand::Follow { task_id, err } => {
                // Simple log output follows for local logs don't need any communication with the daemon.
                // Thereby we handle this separately over here.
                if self.settings.client.read_local_logs {
                    local_follow(
                        &mut self.socket,
                        self.settings.shared.pueue_directory.clone(),
                        task_id,
                        *err,
                    )
                    .await?;
                    return Ok(true);
                }
                Ok(false)
            }
            _ => Ok(false),
        }
    }

    /// Handle logic that's super generic on the client-side.
    /// This (almost) always follows a singular ping-pong pattern.
    /// One message to the daemon, one response, Done.<commit_msg>Handle state on handle_complex_command. If there are no running tasks, or the user confirmed the removal of these manually handle reset with handle_simple_command.<commit_after>use std::collections::HashMap;
use std::env::{current_dir, vars};
use std::io::{self, Write};

use anyhow::{bail, Context, Result};
use log::error;

use pueue::message::*;
use pueue::platform::socket::*;
use pueue::protocol::*;
use pueue::settings::Settings;

use crate::cli::{CliArguments, SubCommand};
use crate::commands::local_follow::*;
use crate::commands::restart::*;
use crate::commands::{edit::*, get_state};
use crate::output::*;

/// This struct contains the base logic for the client.
/// The client is responsible for connecting to the daemon, sending instructions
/// and interpreting their responses.
///
/// Most commands are a simple ping-pong. However, some commands require a more complex
/// communication pattern, such as the `follow` command, which can read local files,
/// or the `edit` command, which needs to open an editor locally.
pub struct Client {
    opt: CliArguments,
    settings: Settings,
    socket: Socket,
}

impl Client {
    /// Connect to the daemon, authorize via secret and return a new initialized Client.
    pub async fn new(settings: Settings, opt: CliArguments) -> Result<Self> {
        // // Commandline argument overwrites the configuration files values for address
        // let address = if let Some(address) = opt.address.clone() {
        //     address
        // } else {
        //     settings.client.daemon_address
        // };

        // Commandline argument overwrites the configuration files values for port
        let (unix_socket_path, port) = {
            // Always prefer commandline options
            if let Some(path) = opt.unix_socket_path.clone() {
                (Some(path), None)
            } else if let Some(port) = opt.port.clone() {
                (None, Some(port))
            } else if settings.shared.use_unix_socket {
                (Some(settings.shared.unix_socket_path.clone()), None)
            } else {
                (None, Some(settings.shared.port.clone()))
            }
        };

        let mut socket = get_client(unix_socket_path, port).await?;

        // Send the secret to the daemon
        // In case everything was successful, we get a short `hello` response from the daemon.
        let secret = settings.shared.secret.clone().into_bytes();
        send_bytes(&secret, &mut socket).await?;
        let hello = receive_bytes(&mut socket).await?;
        if hello != b"hello" {
            bail!("Daemon went away after initial connection. Did you use the correct secret?")
        }

        Ok(Client {
            opt,
            settings,
            socket,
        })
    }

    /// This is the function where the actual communication and logic starts.
    /// At this point everything is initialized, the connection is up and
    /// we can finally start doing stuff.
    ///
    /// The command handling is splitted into "simple" and "complex" commands.
    pub async fn start(&mut self) -> Result<()> {
        // Return early, if the command has already been handled.
        if self.handle_complex_command().await? {
            return Ok(());
        }

        // The handling of "generic" commands is encapsulated in this function.
        self.handle_simple_command().await?;

        Ok(())
    }

    /// Handle all complex client-side functionalities.
    /// Complex functionalities need some special handling and are contained
    /// in their own functions with their own communication code.
    /// Such functionalities includes reading local filestand sending multiple messages.
    ///
    /// Returns `Ok(true)`, if the current command has been handled by this function.
    /// This indicates that the client can now shut down.
    /// If `Ok(false)` is returned, the client will continue and handle the Subcommand in the
    /// [handle_simple_command] function.
    async fn handle_complex_command(&mut self) -> Result<bool> {
        // This match handles all "complex" commands.
        match &self.opt.cmd {
            SubCommand::Reset { force, .. } => {
                let state = get_state(&mut self.socket).await?;
                let running_tasks = state
                    .tasks
                    .iter()
                    .filter_map(|(id, task)| if task.is_running() { Some(*id) } else { None })
                    .collect::<Vec<_>>();

                if !running_tasks.is_empty() && !force {
                    self.handle_user_confirmation("remove running tasks", &running_tasks)?;
                }

                // Let handle_simple_command to handle `reset` after getting user behalf
                Ok(false)
            }

            SubCommand::Edit { task_id, path } => {
                let message = edit(&mut self.socket, *task_id, *path).await?;
                self.handle_response(message);
                Ok(true)
            }

            SubCommand::Restart {
                task_ids,
                start_immediately,
                stashed,
                edit,
                path,
            } => {
                restart(
                    &mut self.socket,
                    task_ids.clone(),
                    *start_immediately,
                    *stashed,
                    *edit,
                    *path,
                )
                .await?;
                Ok(true)
            }

            SubCommand::Follow { task_id, err } => {
                // Simple log output follows for local logs don't need any communication with the daemon.
                // Thereby we handle this separately over here.
                if self.settings.client.read_local_logs {
                    local_follow(
                        &mut self.socket,
                        self.settings.shared.pueue_directory.clone(),
                        task_id,
                        *err,
                    )
                    .await?;
                    return Ok(true);
                }
                Ok(false)
            }

            _ => Ok(false),
        }
    }

    /// Handle logic that's super generic on the client-side.
    /// This (almost) always follows a singular ping-pong pattern.
    /// One message to the daemon, one response, Done.
{'fraction_non_alphanumeric': 0.10305985205110961, 'fraction_numerical': 0.0, 'mean_word_length': 1.7894490035169988, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 26, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1406489', 'n_tokens_mistral': 3036, 'n_tokens_neox': 2944, 'n_words': 1004}