#![doc(hidden)] #![expect( clippy::needless_pass_by_value, clippy::redundant_pub_crate, reason = "this private runtime-planning module shares parent-only planning structs and helpers across the split CLI runtime" )] use std::path::PathBuf; use aria2_rust_pro_compat::{ConfigParseError, ConfigProfile}; use aria2_rust_pro_core::RuntimeConfig; use aria2_rust_pro_protocol::{Downloader, HttpSessionModel, Protocol}; use super::{ CliError, CliTransferSource, ConfigLoadReport, DispatcherRegistrationKind, InProcessRpcDispatcher, StartupProfile, TransferInputEntry, TransferSelection, classify_transfer, derive_http_session, derive_runtime_config, expand_transfer_entries, load_config_report, merged_profile, parse_protocol, register_resolved_entry_with_dispatcher, resolve_transfer_entry_with_downloader, }; #[derive(Clone, Debug)] pub(super) struct RuntimeInputContext { pub(super) config_report: Option, pub(super) effective_profile: Option, pub(super) recognized_schemes: Vec, pub(super) transfer_kinds: Vec, pub(super) derived_runtime: RuntimeConfig, pub(super) http_session: HttpSessionModel, pub(super) resolved_entries: Vec, } #[derive(Clone, Debug)] pub(super) struct ResolvedRuntimeEntry { pub(super) entry: TransferInputEntry, pub(super) entry_profile: Option, pub(super) runtime: RuntimeConfig, pub(super) http_session: HttpSessionModel, pub(super) uri: String, } #[derive(Clone, Debug)] pub(super) struct RegisteredRuntimeEntry { pub(super) resolved: ResolvedRuntimeEntry, pub(super) gid: String, pub(super) registration_kind: DispatcherRegistrationKind, } #[derive(Clone, Debug)] struct DerivedEntryContext { runtime: RuntimeConfig, http_session: HttpSessionModel, } pub(super) fn build_runtime_input_context( config_path: Option, uris: Vec, startup: &StartupProfile, cli_profile: Option<&ConfigProfile>, cli_transfer_sources: Option<&[CliTransferSource]>, downloader: &D, ) -> Result { 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, cli_profile); let profile = effective_profile.as_ref(); let input_entries = expand_transfer_entries(&uris, profile, cli_transfer_sources)?; let first_entry_profile = input_entries.first().and_then(|entry| { let implied = merged_profile(entry.implied_profile.as_ref(), profile); merged_profile(implied.as_ref(), entry.profile.as_ref()) }); let report_profile = first_entry_profile.as_ref().or(profile); let derived_runtime = derive_runtime_config(report_profile, startup); let http_session = derive_http_session(report_profile, startup); let recognized_schemes = input_entries .iter() .filter_map(|entry| entry.uris.first()) .filter_map(|uri| parse_protocol(uri).map(Protocol::as_str)) .map(str::to_owned) .collect::>(); let transfer_kinds = input_entries .iter() .filter_map(|entry| entry.uris.first()) .map(String::as_str) .map(classify_transfer) .collect::>(); let resolved_entries = resolve_runtime_entries(&input_entries, startup, profile, downloader)?; Ok(RuntimeInputContext { config_report, effective_profile, recognized_schemes, transfer_kinds, derived_runtime, http_session, resolved_entries, }) } pub(super) fn register_runtime_entries( dispatcher: &mut InProcessRpcDispatcher, downloader: &D, entries: Vec, base_profile: Option<&ConfigProfile>, ) -> Result, CliError> { let mut registered_entries = Vec::with_capacity(entries.len()); for resolved in entries { let (gid, registration_kind) = register_resolved_entry_with_dispatcher( dispatcher, downloader, &resolved.entry, &resolved.uri, resolved.entry_profile.as_ref().or(base_profile), &resolved.http_session, &resolved.runtime, )?; registered_entries.push(RegisteredRuntimeEntry { resolved, gid, registration_kind, }); } Ok(registered_entries) } fn resolve_runtime_entries( input_entries: &[TransferInputEntry], startup: &StartupProfile, profile: Option<&ConfigProfile>, downloader: &D, ) -> Result, CliError> { let mut resolved_entries = Vec::new(); let mut derived_context_cache = Vec::new(); for entry in input_entries { let direct_passthrough = entry .uris .first() .is_some_and(|uri| classify_transfer(uri) != TransferSelection::Metalink); let pre_resolve_profile = merged_profile( merged_profile(entry.implied_profile.as_ref(), profile).as_ref(), entry.profile.as_ref(), ); let pre_resolve_context = derived_entry_context( &mut derived_context_cache, pre_resolve_profile.as_ref(), startup, ); let expanded_entries = resolve_transfer_entry_with_downloader( entry, downloader, &pre_resolve_context.http_session, &pre_resolve_context.runtime, )?; if direct_passthrough && expanded_entries.len() == 1 { let resolved_uri = entry.uris.first().cloned().ok_or_else(|| { CliError::Config(ConfigParseError::InvalidDirective( "input-file entry missing URI".to_owned(), )) })?; let expanded_entry = expanded_entries .into_iter() .next() .expect("direct entry fast path should preserve one resolved entry"); resolved_entries.push(ResolvedRuntimeEntry { entry: expanded_entry, entry_profile: pre_resolve_profile, runtime: pre_resolve_context.runtime, http_session: pre_resolve_context.http_session, uri: resolved_uri, }); continue; } for expanded_entry in expanded_entries { let entry_profile = merged_profile( merged_profile(expanded_entry.implied_profile.as_ref(), profile).as_ref(), expanded_entry.profile.as_ref(), ); let entry_context = derived_entry_context(&mut derived_context_cache, entry_profile.as_ref(), startup); let resolved_uri = expanded_entry.uris.first().cloned().ok_or_else(|| { CliError::Config(ConfigParseError::InvalidDirective( "input-file entry missing URI".to_owned(), )) })?; resolved_entries.push(ResolvedRuntimeEntry { entry: expanded_entry, entry_profile, runtime: entry_context.runtime, http_session: entry_context.http_session, uri: resolved_uri, }); } } Ok(resolved_entries) } fn derived_entry_context( cache: &mut Vec<(Option, DerivedEntryContext)>, profile: Option<&ConfigProfile>, startup: &StartupProfile, ) -> DerivedEntryContext { if let Some((_, cached)) = cache .iter() .find(|(cached_profile, _)| cached_profile.as_ref() == profile) { return cached.clone(); } let derived = DerivedEntryContext { runtime: derive_runtime_config(profile, startup), http_session: derive_http_session(profile, startup), }; cache.push((profile.cloned(), derived.clone())); derived }