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,
})
}
fn send_unit(
&self, command: kftray_helper::messages::RequestCommand,
) -> Result<(), HostfileHelperError> {
if !self.is_available() {
return Err(HostfileHelperError::Communication(
"Helper service is not available".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 => Ok(()),
kftray_helper::messages::RequestResult::Error(err) => {
Err(HostfileHelperError::Communication(err))
}
_ => Err(HostfileHelperError::InvalidResponse(
"Expected Success or Error response".to_string(),
)),
},
Err(e) => Err(HostfileHelperError::Helper(e)),
}
}
pub fn add_host_entry(&self, id: String, entry: HostEntry) -> Result<(), HostfileHelperError> {
debug!("Adding host entry via helper for ID {id}: {entry:?}");
let command = kftray_helper::messages::RequestCommand::Host(
kftray_helper::messages::HostCommand::Add {
id: id.clone(),
entry,
},
);
self.send_unit(command)
.inspect(|_| debug!("Successfully added host entry for ID: {id}"))
.inspect_err(|e| error!("Failed to add host entry for ID {id} via helper: {e}"))
}
pub fn remove_host_entry(&self, id: &str) -> Result<(), HostfileHelperError> {
debug!("Removing host entry via helper for ID: {id}");
let command = kftray_helper::messages::RequestCommand::Host(
kftray_helper::messages::HostCommand::Remove { id: id.to_string() },
);
self.send_unit(command)
.inspect(|_| debug!("Successfully removed host entry for ID: {id}"))
.inspect_err(|e| error!("Failed to remove host entry for ID {id} via helper: {e}"))
}
pub fn remove_unowned_host_entries(
&self, entries: Vec<HostEntry>,
) -> Result<(), HostfileHelperError> {
debug!(
"Removing {} unmarked host entries via helper",
entries.len()
);
let command = kftray_helper::messages::RequestCommand::Host(
kftray_helper::messages::HostCommand::RemoveUnowned { entries },
);
self.send_unit(command)
}
pub fn remove_direct_owned_entries(
&self, ids: &[&str], legacy: Vec<HostEntry>,
) -> Result<(), HostfileHelperError> {
debug!("Removing direct host entries for {ids:?} via helper");
let command = kftray_helper::messages::RequestCommand::Host(
kftray_helper::messages::HostCommand::RemoveDirectOwned {
ids: ids.iter().map(|id| (*id).to_owned()).collect(),
legacy,
},
);
self.send_unit(command)
}
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)");
}
}
}