185 lines
5.0 KiB
Rust
185 lines
5.0 KiB
Rust
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<ComponentId>,
|
|
#[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<String>,
|
|
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<String>, message: impl Into<String>) -> Self {
|
|
Self {
|
|
code: code.into(),
|
|
message: message.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub trait HelperCommandRunner {
|
|
fn run(&self, spec: &HelperCommandSpec) -> Result<HelperCommandOutput, HelperError>;
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct StructuredHelper<R> {
|
|
helper_program: PathBuf,
|
|
runner: R,
|
|
}
|
|
|
|
impl<R> StructuredHelper<R>
|
|
where
|
|
R: HelperCommandRunner,
|
|
{
|
|
pub fn new(helper_program: impl Into<PathBuf>, runner: R) -> Self {
|
|
Self {
|
|
helper_program: helper_program.into(),
|
|
runner,
|
|
}
|
|
}
|
|
|
|
pub fn runner(&self) -> &R {
|
|
&self.runner
|
|
}
|
|
|
|
pub fn execute(&self, request: &HelperRequest) -> Result<HelperResponse, HelperError> {
|
|
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<HelperResponse, HelperError> {
|
|
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<Path>,
|
|
service_name: impl Into<String>,
|
|
) -> 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
|
|
)
|
|
}
|