Files
harbor-net/apps/windows-client/src-tauri/src/helper.rs

185 lines
5.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
)
}