use crate::{
errors::{APIError, CatBridgeError, NetworkError},
mion::{
cgis::{do_simple_request, encode_url_parameters, parse_result_from_body},
proto::cgis::{ControlOperation, MionCGIErrors, SetParameter},
},
};
use fnv::FnvHashMap;
use local_ip_address::local_ip;
use reqwest::{Client, Method};
use std::{fmt::Display, net::Ipv4Addr, ops::Deref};
use tracing::warn;
pub async fn get_info(
mion_ip: Ipv4Addr,
name: &str,
) -> Result<FnvHashMap<String, String>, NetworkError> {
get_info_with_raw_client(&Client::default(), mion_ip, name).await
}
pub async fn get_info_with_raw_client(
client: &Client,
mion_ip: Ipv4Addr,
name: &str,
) -> Result<FnvHashMap<String, String>, NetworkError> {
let body_as_string = do_raw_control_request(
client,
mion_ip,
&[
("operation", Into::<&str>::into(ControlOperation::GetInfo)),
(
"host",
&format!("{}", local_ip().map_err(NetworkError::LocalIp)?),
),
("shutdown", "1"),
("name", name),
],
)
.await?;
Ok(extract_body_tags(
&body_as_string,
ControlOperation::GetInfo.into(),
)?)
}
pub async fn set_param(
mion_ip: Ipv4Addr,
parameter_to_set: SetParameter,
) -> Result<bool, NetworkError> {
set_param_with_raw_client(&Client::default(), mion_ip, parameter_to_set).await
}
pub async fn set_param_with_raw_client(
client: &Client,
mion_ip: Ipv4Addr,
parameter_to_set: SetParameter,
) -> Result<bool, NetworkError> {
let body_as_string = do_raw_control_request(
client,
mion_ip,
&[
(
"operation".to_owned(),
Into::<&str>::into(ControlOperation::SetParam).to_owned(),
),
(
format!("{parameter_to_set}"),
parameter_to_set.get_value_as_string(),
),
],
)
.await?;
Ok(parse_result_from_body(
&body_as_string,
Into::<&str>::into(ControlOperation::SetParam),
)?)
}
pub async fn power_on(mion_ip: Ipv4Addr) -> Result<bool, NetworkError> {
power_on_with_raw_client(&Client::default(), mion_ip).await
}
pub async fn power_on_with_raw_client(
client: &Client,
mion_ip: Ipv4Addr,
) -> Result<bool, NetworkError> {
let body_as_string = do_raw_control_request(
client,
mion_ip,
&[(
"operation",
Into::<&str>::into(ControlOperation::PowerOn).to_owned(),
)],
)
.await?;
Ok(parse_result_from_body(
&body_as_string,
Into::<&str>::into(ControlOperation::PowerOnV2),
)?)
}
pub async fn power_on_v2(
mion_ip: Ipv4Addr,
host_ip: Option<Ipv4Addr>,
atapi_port: Option<u16>,
pcfs_port: Option<u16>,
emulate_fs: bool,
) -> Result<bool, CatBridgeError> {
power_on_v2_with_raw_client(
&Client::default(),
mion_ip,
host_ip,
atapi_port,
pcfs_port,
emulate_fs,
)
.await
}
pub async fn power_on_v2_with_raw_client(
client: &Client,
mion_ip: Ipv4Addr,
host_ip: Option<Ipv4Addr>,
atapi_port: Option<u16>,
pcfs_port: Option<u16>,
emulate_fs: bool,
) -> Result<bool, CatBridgeError> {
let host_ip_as_str = if let Some(ip) = host_ip {
format!("{ip}")
} else {
let ip = local_ip().map_err(|_| APIError::NoHostIpFound)?;
format!("{ip}")
};
let mut parameters = vec![
(
"operation",
Into::<&str>::into(ControlOperation::PowerOnV2).to_owned(),
),
(
"emulation",
if emulate_fs {
"on".to_owned()
} else {
"off".to_owned()
},
),
("host", host_ip_as_str),
];
if let Some(port) = atapi_port {
parameters.push(("atapi_port", format!("{port}")));
}
if let Some(port) = pcfs_port {
parameters.push(("pcfs_port", format!("{port}")));
}
let body_as_string = do_raw_control_request(client, mion_ip, ¶meters).await?;
Ok(parse_result_from_body(
&body_as_string,
Into::<&str>::into(ControlOperation::PowerOnV2),
)?)
}
pub async fn do_raw_control_request(
client: &Client,
mion_ip: Ipv4Addr,
url_parameters: &[(impl Deref<Target = str>, impl Display)],
) -> Result<String, NetworkError> {
do_simple_request::<String>(
client,
Method::POST,
format!("http://{mion_ip}/mion/control.cgi"),
Some(encode_url_parameters(url_parameters)),
None,
)
.await
}
fn extract_body_tags(
body: &str,
operation_name: &str,
) -> Result<FnvHashMap<String, String>, MionCGIErrors> {
let start_tag_location = body
.find("<body>")
.map(|num| num + 6)
.ok_or_else(|| MionCGIErrors::HtmlResponseMissingBody(body.to_owned()))?;
let body_without_start_tag = body.split_at(start_tag_location).1;
let end_tag_location = body_without_start_tag
.find("</body>")
.ok_or_else(|| MionCGIErrors::HtmlResponseMissingBody(body.to_owned()))?;
let just_inner_body = body_without_start_tag.split_at(end_tag_location).0;
let without_newlines = just_inner_body.replace('\n', "");
let fields = without_newlines
.split("<br>")
.filter_map(|line| {
if line.is_empty()
|| line.trim().is_empty()
|| line.starts_with("INFO:")
|| line.starts_with("WARN:")
|| line.starts_with("ERROR:")
{
None
} else if let Some(location) = line.find(':') {
let (key, value) = line.split_at(location);
Some((key.to_owned(), value.trim_start_matches(':').to_owned()))
} else {
warn!(%operation_name, "Unparsable line from body on mion/control.cgi: {line}");
None
}
})
.collect::<FnvHashMap<String, String>>();
Ok(fields)
}