use crate::models::ComponentId; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use std::path::{Path, PathBuf}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum HelperAction { #[serde(rename = "install-control-app")] InstallControlApp, #[serde(rename = "install-proxyfier")] InstallProxyfier, #[serde(rename = "install-singbox")] InstallSingbox, #[serde(rename = "proxyfier.apply")] ProxyfierApply, #[serde(rename = "service.status")] ServiceStatus, #[serde(rename = "service.start")] ServiceStart, #[serde(rename = "service.stop")] ServiceStop, #[serde(rename = "service.restart")] ServiceRestart, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct HelperRequest { pub action: HelperAction, #[serde(skip_serializing_if = "Option::is_none")] pub component: Option, #[serde(default)] pub payload: Value, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct HelperResponse { pub success: bool, pub action: HelperAction, pub changed: bool, pub message: String, #[serde(default)] pub details: Value, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct HelperCommandSpec { pub program: PathBuf, pub args: Vec, pub stdin: String, pub requires_elevation: bool, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct HelperCommandOutput { pub status_code: i32, pub stdout: String, pub stderr: String, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct HelperError { pub code: String, pub message: String, } impl HelperError { pub fn new(code: impl Into, message: impl Into) -> Self { Self { code: code.into(), message: message.into(), } } } pub trait HelperCommandRunner { fn run(&self, spec: &HelperCommandSpec) -> Result; } #[derive(Debug, Clone)] pub struct StructuredHelper { helper_program: PathBuf, runner: R, } impl StructuredHelper where R: HelperCommandRunner, { pub fn new(helper_program: impl Into, runner: R) -> Self { Self { helper_program: helper_program.into(), runner, } } pub fn runner(&self) -> &R { &self.runner } pub fn execute(&self, request: &HelperRequest) -> Result { let stdin = serde_json::to_string(request) .map_err(|error| HelperError::new("helper_request_encode", error.to_string()))?; let spec = HelperCommandSpec { program: self.helper_program.clone(), args: vec!["--json".to_string()], stdin, requires_elevation: helper_action_requires_elevation(&request.action), }; let output = self.runner.run(&spec)?; if output.status_code != 0 { return Err(HelperError::new( "helper_exit", format!( "Помощник завершился с кодом {}: {}", output.status_code, output.stderr ), )); } parse_helper_response(&output.stdout) } } pub fn parse_helper_response(stdout: &str) -> Result { serde_json::from_str(stdout).map_err(|error| { HelperError::new( "helper_response_decode", format!("Помощник вернул не JSON или некорректный JSON: {error}"), ) }) } pub fn install_request(component: ComponentId) -> HelperRequest { let action = match component { ComponentId::ControlApp => HelperAction::InstallControlApp, ComponentId::Proxyfier => HelperAction::InstallProxyfier, ComponentId::Singbox => HelperAction::InstallSingbox, }; HelperRequest { action, component: Some(component), payload: json!({}), } } pub fn service_request(component: ComponentId, action: HelperAction) -> HelperRequest { HelperRequest { action, component: Some(component), payload: json!({}), } } pub fn proxifyre_apply_request( config_path: impl AsRef, service_name: impl Into, ) -> HelperRequest { HelperRequest { action: HelperAction::ProxyfierApply, component: Some(ComponentId::Proxyfier), payload: json!({ "configPath": config_path.as_ref().display().to_string(), "serviceName": service_name.into(), }), } } pub fn helper_action_requires_elevation(action: &HelperAction) -> bool { matches!( action, HelperAction::InstallControlApp | HelperAction::InstallProxyfier | HelperAction::InstallSingbox | HelperAction::ProxyfierApply | HelperAction::ServiceStart | HelperAction::ServiceStop | HelperAction::ServiceRestart ) }