Files
aria2-rust-pro/crates/aria2-rust-pro-cli/src/rpc_daemon.rs
T

127 lines
4.3 KiB
Rust

#![doc(hidden)]
#![expect(
clippy::needless_pass_by_value,
clippy::redundant_pub_crate,
reason = "this private daemon-launch module keeps ownership explicit across listener bootstrap helpers"
)]
use std::{
net::{IpAddr, Ipv4Addr, TcpListener},
sync::{Arc, Mutex},
};
use aria2_rust_pro_core::RuntimeConfig;
use aria2_rust_pro_rpc::{
InProcessRpcDispatcher, JsonRpcRequest, RpcMeta, RpcMethod, RpcServerConfig, RpcValue,
serve_rpc_listener,
};
use super::{
CliError, ParsedArguments, TransferInputEntry, derive_rpc_listen_host, derive_rpc_secret,
derive_runtime_config, expand_transfer_entries, load_config_report, merged_profile,
rpc_option_object,
};
use crate::Invocation;
#[derive(Debug)]
struct RpcDaemonLaunch {
runtime: RuntimeConfig,
listen_ip: IpAddr,
secret: Option<String>,
input_entries: Vec<TransferInputEntry>,
}
/// Runs the RPC daemon surface implied by a parsed CLI invocation.
pub(super) fn run_rpc_daemon(parsed: ParsedArguments) -> Result<(), CliError> {
let (config_path, inputs) = match &parsed.invocation {
Invocation::Run { config_path, uris } => (config_path.clone(), uris.clone()),
Invocation::Version | Invocation::Help { .. } => {
return Err(CliError::Io(
"rpc daemon launch requires a run invocation".to_owned(),
));
}
};
let launch = build_rpc_daemon_launch(&parsed, config_path, inputs)?;
let listener = bind_rpc_listener(launch.listen_ip, launch.runtime.rpc_port)?;
let mut dispatcher = InProcessRpcDispatcher::with_runtime(launch.runtime);
seed_dispatcher_with_inputs(&mut dispatcher, launch.input_entries);
let server_config = build_rpc_server_config(&listener, launch.secret)?;
println!(
"rpc daemon listening on {}:{}",
launch.listen_ip,
server_config.listen_addr.port()
);
serve_rpc_listener(listener, server_config, Arc::new(Mutex::new(dispatcher)))
.map_err(|error| CliError::Io(format!("rpc daemon serve failed: {error}")))
}
fn build_rpc_daemon_launch(
parsed: &ParsedArguments,
config_path: Option<std::path::PathBuf>,
inputs: Vec<String>,
) -> Result<RpcDaemonLaunch, CliError> {
let config_report = config_path
.as_ref()
.map(|path| load_config_report(path, true))
.transpose()?;
let file_profile = config_report.as_ref().map(|report| &report.profile);
let effective_profile = merged_profile(file_profile, parsed.cli_profile.as_ref());
let profile = effective_profile.as_ref();
let runtime = derive_runtime_config(profile, &parsed.profile);
let listen_host = derive_rpc_listen_host(profile, &parsed.profile);
let secret = derive_rpc_secret(profile, &parsed.profile);
let listen_ip = listen_host
.parse::<IpAddr>()
.unwrap_or(IpAddr::V4(Ipv4Addr::LOCALHOST));
let input_entries =
expand_transfer_entries(&inputs, profile, Some(&parsed.cli_transfer_sources))?;
Ok(RpcDaemonLaunch {
runtime,
listen_ip,
secret,
input_entries,
})
}
fn bind_rpc_listener(listen_ip: IpAddr, listen_port: u16) -> Result<TcpListener, CliError> {
TcpListener::bind((listen_ip, listen_port))
.map_err(|error| CliError::Io(format!("rpc daemon bind failed: {error}")))
}
fn build_rpc_server_config(
listener: &TcpListener,
secret: Option<String>,
) -> Result<RpcServerConfig, CliError> {
Ok(RpcServerConfig {
listen_addr: listener
.local_addr()
.map_err(|error| CliError::Io(format!("rpc daemon local addr failed: {error}")))?,
secret_token: secret,
..RpcServerConfig::default()
})
}
fn seed_dispatcher_with_inputs(
dispatcher: &mut InProcessRpcDispatcher,
input_entries: Vec<TransferInputEntry>,
) {
for entry in input_entries {
let mut params = vec![RpcValue::Array(
entry.uris.into_iter().map(RpcValue::String).collect(),
)];
if let Some(options) = rpc_option_object(entry.profile.as_ref()) {
params.push(options);
}
let _ = dispatcher.dispatch_json(JsonRpcRequest {
jsonrpc: Some("2.0".to_owned()),
id: None,
method: RpcMethod::Aria2AddUri.as_str().to_owned(),
params,
meta: RpcMeta::default(),
});
}
}