kftray-portforward 0.27.30

KFtray library with port forwarding logic
Documentation
use std::path::PathBuf;

use kftray_commons::models::hostfile::HostEntry;
use kftray_helper::HelperError;
use log::{
    debug,
    error,
};
use thiserror::Error;

#[derive(Error, Debug)]
pub enum HostfileHelperError {
    #[error("Helper error: {0}")]
    Helper(#[from] HelperError),
    #[error("Communication error: {0}")]
    Communication(String),
    #[error("Invalid response: {0}")]
    InvalidResponse(String),
}

pub struct HostfileHelperClient {
    app_id: String,
    socket_path: PathBuf,
}

impl HostfileHelperClient {
    pub fn new() -> Result<Self, HostfileHelperError> {
        debug!("Creating new HostfileHelperClient");

        let socket_path = kftray_helper::communication::get_default_socket_path()
            .map_err(HostfileHelperError::Helper)?;

        Ok(Self {
            app_id: "com.kftray.app".to_string(),
            socket_path,
        })
    }

    pub fn add_host_entry(&self, id: String, entry: HostEntry) -> Result<(), HostfileHelperError> {
        debug!("Adding host entry via helper for ID {id}: {entry:?}");

        if !self.is_available() {
            return Err(HostfileHelperError::Communication(
                "Helper service is not available".to_string(),
            ));
        }

        let command = kftray_helper::messages::RequestCommand::Host(
            kftray_helper::messages::HostCommand::Add {
                id: id.clone(),
                entry,
            },
        );

        match kftray_helper::client::socket_comm::send_request(
            &self.socket_path,
            &self.app_id,
            command,
        ) {
            Ok(response) => match response.result {
                kftray_helper::messages::RequestResult::Success => {
                    debug!("Successfully added host entry for ID: {id}");
                    Ok(())
                }
                kftray_helper::messages::RequestResult::Error(err) => {
                    error!("Helper returned error for add_host_entry: {err}");
                    Err(HostfileHelperError::Communication(err))
                }
                _ => {
                    error!("Unexpected response type for add_host_entry");
                    Err(HostfileHelperError::InvalidResponse(
                        "Expected Success or Error response".to_string(),
                    ))
                }
            },
            Err(e) => {
                error!("Failed to send add_host_entry request to helper: {e}");
                Err(HostfileHelperError::Helper(e))
            }
        }
    }

    pub fn remove_host_entry(&self, id: &str) -> Result<(), HostfileHelperError> {
        debug!("Removing host entry via helper for ID: {id}");

        if !self.is_available() {
            return Err(HostfileHelperError::Communication(
                "Helper service is not available".to_string(),
            ));
        }

        let command = kftray_helper::messages::RequestCommand::Host(
            kftray_helper::messages::HostCommand::Remove { id: id.to_string() },
        );

        match kftray_helper::client::socket_comm::send_request(
            &self.socket_path,
            &self.app_id,
            command,
        ) {
            Ok(response) => match response.result {
                kftray_helper::messages::RequestResult::Success => {
                    debug!("Successfully removed host entry for ID: {id}");
                    Ok(())
                }
                kftray_helper::messages::RequestResult::Error(err) => {
                    error!("Helper returned error for remove_host_entry: {err}");
                    Err(HostfileHelperError::Communication(err))
                }
                _ => {
                    error!("Unexpected response type for remove_host_entry");
                    Err(HostfileHelperError::InvalidResponse(
                        "Expected Success or Error response".to_string(),
                    ))
                }
            },
            Err(e) => {
                error!("Failed to send remove_host_entry request to helper: {e}");
                Err(HostfileHelperError::Helper(e))
            }
        }
    }

    pub fn remove_all_host_entries(&self) -> Result<(), HostfileHelperError> {
        debug!("Removing all host entries via helper");

        if !self.is_available() {
            return Err(HostfileHelperError::Communication(
                "Helper service is not available".to_string(),
            ));
        }

        let command = kftray_helper::messages::RequestCommand::Host(
            kftray_helper::messages::HostCommand::RemoveAll,
        );

        match kftray_helper::client::socket_comm::send_request(
            &self.socket_path,
            &self.app_id,
            command,
        ) {
            Ok(response) => match response.result {
                kftray_helper::messages::RequestResult::Success => {
                    debug!("Successfully removed all host entries");
                    Ok(())
                }
                kftray_helper::messages::RequestResult::Error(err) => {
                    error!("Helper returned error for remove_all_host_entries: {err}");
                    Err(HostfileHelperError::Communication(err))
                }
                _ => {
                    error!("Unexpected response type for remove_all_host_entries");
                    Err(HostfileHelperError::InvalidResponse(
                        "Expected Success or Error response".to_string(),
                    ))
                }
            },
            Err(e) => {
                error!("Failed to send remove_all_host_entries request to helper: {e}");
                Err(HostfileHelperError::Helper(e))
            }
        }
    }

    pub fn list_host_entries(&self) -> Result<Vec<(String, HostEntry)>, HostfileHelperError> {
        debug!("Listing host entries via helper");

        if !self.is_available() {
            return Err(HostfileHelperError::Communication(
                "Helper service is not available".to_string(),
            ));
        }

        let command = kftray_helper::messages::RequestCommand::Host(
            kftray_helper::messages::HostCommand::List,
        );

        match kftray_helper::client::socket_comm::send_request(
            &self.socket_path,
            &self.app_id,
            command,
        ) {
            Ok(response) => match response.result {
                kftray_helper::messages::RequestResult::HostEntriesSuccess(entries) => {
                    debug!("Successfully listed {} host entries", entries.len());
                    Ok(entries)
                }
                kftray_helper::messages::RequestResult::Error(err) => {
                    error!("Helper returned error for list_host_entries: {err}");
                    Err(HostfileHelperError::Communication(err))
                }
                _ => {
                    error!("Unexpected response type for list_host_entries");
                    Err(HostfileHelperError::InvalidResponse(
                        "Expected HostEntriesSuccess or Error response".to_string(),
                    ))
                }
            },
            Err(e) => {
                error!("Failed to send list_host_entries request to helper: {e}");
                Err(HostfileHelperError::Helper(e))
            }
        }
    }

    pub fn is_available(&self) -> bool {
        debug!("Checking if helper service is available");

        kftray_helper::client::socket_comm::is_socket_available(&self.socket_path)
    }
}

impl Default for HostfileHelperClient {
    fn default() -> Self {
        Self::new().unwrap_or_else(|e| {
            panic!("Failed to create default HostfileHelperClient: {e:?}");
        })
    }
}

#[cfg(test)]
mod tests {

    use super::*;
    #[test]
    fn test_client_creation() {
        let result = HostfileHelperClient::new();

        match result {
            Ok(_) => println!("Helper client created successfully"),
            Err(e) => println!("Helper client creation failed (expected in tests): {e}"),
        }
    }

    #[test]
    fn test_client_availability_check() {
        if let Ok(client) = HostfileHelperClient::new() {
            let _is_available = client.is_available();
            println!("Helper client availability check completed");
        } else {
            println!("Helper client creation failed (expected in tests without running service)");
        }
    }
}