chore: initial sanitized public snapshot

This commit is contained in:
Aria2 Rust Pro Contributors
2026-07-18 14:04:26 +08:00
commit 7b3816441c
320 changed files with 76813 additions and 0 deletions
+930
View File
@@ -0,0 +1,930 @@
use std::{
collections::VecDeque,
ffi::OsString,
fs,
io::{BufRead, BufReader, Read, Write},
net::{TcpListener, TcpStream},
process::{Child, Command, ExitStatus, Stdio},
sync::{
Arc, Mutex,
atomic::{AtomicUsize, Ordering},
},
thread,
time::{Duration, Instant},
};
use aria2_rust_pro_core::RuntimeConfig;
use aria2_rust_pro_protocol::{
Downloader, FixtureHttpDownloader, FtpConfigModel, FtpRequestModel, FtpResponseModel,
HttpResponseHeaders, HttpResponseModel, HttpVersion, Protocol, ReqwestHttpConnector,
ResponseBody, SftpConfigModel, SftpRequestModel, SftpResponseModel,
transport::{TransportError, TransportErrorKind},
};
use aria2_rust_pro_rpc::{InProcessRpcDispatcher, JsonRpcRequest, RpcMethod, RpcValue};
use super::http_runtime::partition_indexed_work_evenly;
use super::{
CliError, CliTransferSource, CommandSurface, Invocation, RuntimeMode, StartupProfile,
TransferSelection, build_ftp_transfer_parts, build_http_transfer_task, classify_transfer,
command_surface, derive_http_session, derive_runtime_config, execute,
execute_http_transfer_with_retry, execute_runtime, execute_runtime_with_context,
execute_runtime_with_downloader, execute_segment_transfers, load_config_report, merged_profile,
parse_args, parse_cli, parse_protocol, planned_segment_span, profile_option_map, render_help,
render_version,
};
fn percent_encode_uri_component(value: &str) -> String {
let mut encoded = String::with_capacity(value.len());
for byte in value.bytes() {
if matches!(byte, b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~') {
encoded.push(char::from(byte));
} else {
encoded.push('%');
std::fmt::Write::write_fmt(&mut encoded, format_args!("{byte:02X}"))
.expect("writing percent-encoded byte into string must succeed");
}
}
encoded
}
fn start_live_bt_tracker_fixture() -> (String, thread::JoinHandle<()>) {
let listener = TcpListener::bind("127.0.0.1:0").expect("local listener should bind");
let addr = listener.local_addr().expect("local addr should exist");
let handle = thread::spawn(move || {
for _ in 0..2 {
let (mut stream, _) = listener.accept().expect("tracker client should connect");
let mut request = [0_u8; 2048];
let read = stream.read(&mut request).expect("request should read");
let request_slice = request
.get(..read)
.expect("read length must stay within request buffer");
let request_text = String::from_utf8_lossy(request_slice);
let (payload, path) = if request_text.starts_with("GET /announce?") {
(
b"d8:intervali600e10:tracker id12:cli-live-0015:peers6:\x7f\x00\x00\x01\x1a\xe1e"
.to_vec(),
"/announce",
)
} else {
(
b"d8:completei4e10:downloadedi9e10:incompletei2ee".to_vec(),
"/scrape",
)
};
assert!(request_text.contains(path));
let response = format!(
"HTTP/1.1 200 OK\r\nContent-Length: {}\r\nContent-Type: text/plain\r\n\r\n",
payload.len()
);
stream
.write_all(response.as_bytes())
.expect("headers should write");
stream.write_all(&payload).expect("payload should write");
}
});
(format!("http://{addr}/announce"), handle)
}
fn build_single_file_torrent_bytes(
announce_url: &str,
file_name: &str,
total_length: u64,
piece_length: u64,
) -> Vec<u8> {
fn bencode_bytes(value: &[u8]) -> Vec<u8> {
let mut encoded = format!("{}:", value.len()).into_bytes();
encoded.extend_from_slice(value);
encoded
}
fn bencode_int(value: u64) -> Vec<u8> {
format!("i{value}e").into_bytes()
}
let mut torrent = Vec::new();
torrent.extend_from_slice(b"d8:announce");
torrent.extend_from_slice(&bencode_bytes(announce_url.as_bytes()));
torrent.extend_from_slice(b"4:infod6:length");
torrent.extend_from_slice(&bencode_int(total_length));
torrent.extend_from_slice(b"4:name");
torrent.extend_from_slice(&bencode_bytes(file_name.as_bytes()));
torrent.extend_from_slice(b"12:piece length");
torrent.extend_from_slice(&bencode_int(piece_length));
torrent.extend_from_slice(b"6:pieces20:");
torrent.extend_from_slice(&[0_u8; 20]);
torrent.extend_from_slice(b"ee");
torrent
}
#[test]
fn execute_runtime_with_local_metalink_fixture_expands_multiple_files_and_uses_implied_output_names()
{
let mut downloader = FixtureHttpDownloader::new();
downloader.register("http://example.com/alpha.bin", b"abc");
downloader.register("http://example.com/beta.bin", b"hello");
let temp_dir = std::env::temp_dir().join("aria2-rust-pro-cli-metalink-multifile-test");
let _ = fs::create_dir_all(&temp_dir);
let download_dir = temp_dir.join("downloads");
let metalink_path = temp_dir.join("fixture.meta4");
let config_path = temp_dir.join("aria2.conf");
fs::write(
&metalink_path,
r#"<?xml version="1.0" encoding="utf-8"?>
<metalink version="4.0">
<file name="alpha.bin">
<hash type="md5">900150983cd24fb0d6963f7d28e17f72</hash>
<url priority="1">http://example.com/alpha.bin</url>
</file>
<file name="beta.bin">
<url priority="1">http://example.com/beta.bin</url>
</file>
</metalink>"#,
)
.expect("metalink file should write");
fs::write(&config_path, format!("dir={}\n", download_dir.display()))
.expect("config should write");
let report = execute_runtime_with_downloader(
Invocation::Run {
config_path: Some(config_path),
uris: vec![metalink_path.to_string_lossy().into_owned()],
},
&downloader,
)
.expect("runtime should execute with expanded metalink fixture");
assert_eq!(report.accepted_uri_count, 2);
assert_eq!(report.first_status.as_deref(), Some("complete"));
assert_eq!(report.first_total_length, Some(3));
assert_eq!(report.first_completed_length, Some(3));
assert_eq!(report.transfer_kinds, vec![TransferSelection::Metalink]);
assert_eq!(report.completed_download_count, 2);
assert_eq!(
fs::read(download_dir.join("alpha.bin")).expect("alpha output should exist"),
b"abc"
);
assert_eq!(
fs::read(download_dir.join("beta.bin")).expect("beta output should exist"),
b"hello"
);
let _ = fs::remove_dir_all(temp_dir);
}
#[derive(Clone, Debug)]
struct SequencedHttpDownloader {
responses: Arc<Mutex<Vec<Result<HttpResponseModel, TransportError>>>>,
requests: Arc<Mutex<Vec<aria2_rust_pro_protocol::HttpTransferTaskModel>>>,
}
struct LocalFtpTestServer {
control_port: u16,
join: Option<thread::JoinHandle<()>>,
}
impl LocalFtpTestServer {
fn spawn(payload: Vec<u8>) -> Self {
let control_listener =
TcpListener::bind("127.0.0.1:0").expect("control listener should bind");
let control_port = control_listener
.local_addr()
.expect("control addr should exist")
.port();
let data_listener = TcpListener::bind("127.0.0.1:0").expect("data listener should bind");
let data_addr = data_listener.local_addr().expect("data addr should exist");
let join = thread::spawn(move || {
let (mut control_stream, _) = control_listener
.accept()
.expect("control connection should arrive");
control_stream
.write_all(b"220 local ftp ready\r\n")
.expect("welcome should write");
let mut control_reader =
BufReader::new(control_stream.try_clone().expect("clone should work"));
loop {
let mut line = String::new();
let read = control_reader
.read_line(&mut line)
.expect("control line should read");
if read == 0 {
break;
}
if line.starts_with("USER ") {
control_stream
.write_all(b"331 password required\r\n")
.expect("USER response should write");
} else if line.starts_with("PASS ") {
control_stream
.write_all(b"230 login ok\r\n")
.expect("PASS response should write");
} else if line.starts_with("TYPE ") {
control_stream
.write_all(b"200 type ok\r\n")
.expect("TYPE response should write");
} else if line.starts_with("PASV") {
let port_hi = data_addr.port().div_euclid(256);
let port_lo = data_addr.port().rem_euclid(256);
let response =
format!("227 Entering Passive Mode (127,0,0,1,{port_hi},{port_lo})\r\n");
control_stream
.write_all(response.as_bytes())
.expect("PASV response should write");
} else if line.starts_with("RETR ") {
control_stream
.write_all(b"150 opening data\r\n")
.expect("RETR prelim response should write");
let (mut data_stream, _) = data_listener
.accept()
.expect("data connection should arrive");
data_stream
.write_all(&payload)
.expect("payload should write");
drop(data_stream);
control_stream
.write_all(b"226 transfer complete\r\n")
.expect("RETR completion should write");
} else if line.starts_with("QUIT") {
control_stream
.write_all(b"221 bye\r\n")
.expect("QUIT response should write");
break;
} else {
control_stream
.write_all(b"500 unsupported\r\n")
.expect("fallback response should write");
}
}
});
Self {
control_port,
join: Some(join),
}
}
fn control_port(&self) -> u16 {
self.control_port
}
fn join(mut self) {
if let Some(join) = self.join.take() {
join.join().expect("ftp server thread should join");
}
}
}
struct LiveFtpSmokeDownloader;
struct LiveSftpSmokeDownloader;
#[derive(Debug)]
struct LocalSftpDockerServer {
container_name: String,
port: u16,
payload_len: usize,
}
impl LocalSftpDockerServer {
const DOCKER_EXEC_TIMEOUT: Duration = Duration::from_secs(5);
const DOCKER_RM_TIMEOUT: Duration = Duration::from_secs(5);
const DOCKER_RUN_TIMEOUT: Duration = Duration::from_secs(30);
fn spawn() -> Option<Self> {
let docker = Command::new("docker")
.arg("--version")
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.ok()?;
if !docker.success() {
return None;
}
let port = Self::pick_port();
let container_name = format!("aria2-rust-pro-sftp-smoke-{port}");
let payload = "hello-from-live-sftp\n";
let payload_len = payload.len();
let port_binding = format!("{port}:22");
let run = Self::docker_status_with_timeout(
&[
"run",
"--rm",
"-d",
"--name",
&container_name,
"-p",
&port_binding,
"atmoz/sftp:debian",
"foo:pass:1001::upload",
],
Self::DOCKER_RUN_TIMEOUT,
)?;
if !run.success() {
return None;
}
for _ in 0..20 {
if Self::docker_status_with_timeout(
&[
"exec",
&container_name,
"sh",
"-lc",
"echo 'hello-from-live-sftp' > /home/foo/upload/hello.txt",
],
Self::DOCKER_EXEC_TIMEOUT,
)
.is_some_and(|status| status.success())
{
if Self::wait_until_ready(port) {
return Some(Self {
container_name,
port,
payload_len,
});
}
break;
}
thread::sleep(Duration::from_millis(250));
}
let _ = Self::docker_status_with_timeout(
&["rm", "-f", &container_name],
Self::DOCKER_RM_TIMEOUT,
);
None
}
fn pick_port() -> u16 {
TcpListener::bind("127.0.0.1:0")
.expect("test should pick local port")
.local_addr()
.expect("local addr should exist")
.port()
}
fn host_port(&self) -> u16 {
self.port
}
fn payload_len(&self) -> usize {
self.payload_len
}
fn wait_until_ready(port: u16) -> bool {
for _ in 0..40 {
if let Ok(stream) = TcpStream::connect(("127.0.0.1", port)) {
let _ = stream.set_read_timeout(Some(Duration::from_secs(2)));
let _ = stream.set_write_timeout(Some(Duration::from_secs(2)));
if let Ok(mut session) = ssh2::Session::new() {
session.set_tcp_stream(stream);
if session.handshake().is_ok()
&& session.userauth_password("foo", "pass").is_ok()
{
return true;
}
}
}
thread::sleep(Duration::from_millis(250));
}
false
}
fn docker_status_with_timeout(args: &[&str], timeout: Duration) -> Option<ExitStatus> {
let mut child = Command::new("docker")
.args(args)
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.ok()?;
Self::wait_child_with_timeout(&mut child, timeout)
}
fn wait_child_with_timeout(child: &mut Child, timeout: Duration) -> Option<ExitStatus> {
let started_at = Instant::now();
loop {
if let Some(status) = child.try_wait().ok()? {
return Some(status);
}
if started_at.elapsed() >= timeout {
let _ = child.kill();
let _ = child.wait();
return None;
}
thread::sleep(Duration::from_millis(100));
}
}
}
impl Drop for LocalSftpDockerServer {
fn drop(&mut self) {
let _ = Self::docker_status_with_timeout(
&["rm", "-f", &self.container_name],
Self::DOCKER_RM_TIMEOUT,
);
}
}
impl LiveFtpSmokeDownloader {
#[expect(
clippy::result_large_err,
reason = "test-only live FTP smoke helpers bubble full transport context for assertions"
)]
fn read_response_line(reader: &mut BufReader<TcpStream>) -> Result<String, TransportError> {
let mut line = String::new();
reader
.read_line(&mut line)
.map_err(|error| TransportError {
kind: TransportErrorKind::Io,
message: format!("failed to read ftp response: {error}"),
source: Some(error.to_string()),
context: None,
})?;
Ok(line)
}
#[expect(
clippy::result_large_err,
reason = "test-only live FTP smoke helpers bubble full transport context for assertions"
)]
fn expect_code(
reader: &mut BufReader<TcpStream>,
expected: u16,
) -> Result<String, TransportError> {
let line = Self::read_response_line(reader)?;
let code = line
.get(0..3)
.and_then(|digits| digits.parse::<u16>().ok())
.ok_or_else(|| TransportError {
kind: TransportErrorKind::ProtocolViolation,
message: format!("invalid ftp response line: {line:?}"),
source: None,
context: None,
})?;
if code != expected {
return Err(TransportError {
kind: TransportErrorKind::ProtocolViolation,
message: format!("expected ftp code {expected}, got {code}: {line}"),
source: None,
context: None,
});
}
Ok(line)
}
#[expect(
clippy::result_large_err,
reason = "test-only live FTP smoke helpers bubble full transport context for assertions"
)]
fn write_command(stream: &mut TcpStream, command: &str) -> Result<(), TransportError> {
stream
.write_all(command.as_bytes())
.map_err(|error| TransportError {
kind: TransportErrorKind::Io,
message: format!("failed to write ftp command {command:?}: {error}"),
source: Some(error.to_string()),
context: None,
})
}
#[expect(
clippy::result_large_err,
reason = "test-only live FTP smoke helpers bubble full transport context for assertions"
)]
fn parse_pasv_addr(line: &str) -> Result<(String, u16), TransportError> {
let (_, after_open) = line.split_once('(').ok_or_else(|| TransportError {
kind: TransportErrorKind::ProtocolViolation,
message: format!("missing PASV tuple in response: {line}"),
source: None,
context: None,
})?;
let (tuple_text, _) = after_open.split_once(')').ok_or_else(|| TransportError {
kind: TransportErrorKind::ProtocolViolation,
message: format!("unterminated PASV tuple in response: {line}"),
source: None,
context: None,
})?;
let parts = tuple_text
.split(',')
.map(str::trim)
.map(str::parse::<u16>)
.collect::<Result<Vec<_>, _>>()
.map_err(|error| TransportError {
kind: TransportErrorKind::ProtocolViolation,
message: format!("invalid PASV tuple in response: {line}: {error}"),
source: Some(error.to_string()),
context: None,
})?;
let [a, b, c, d, hi, lo]: [u16; 6] =
parts.try_into().map_err(|parts: Vec<u16>| TransportError {
kind: TransportErrorKind::ProtocolViolation,
message: format!("expected 6 PASV tuple parts, got {}: {line}", parts.len()),
source: None,
context: None,
})?;
let host = format!("{a}.{b}.{c}.{d}");
let port = hi
.checked_mul(256)
.and_then(|value| value.checked_add(lo))
.ok_or_else(|| TransportError {
kind: TransportErrorKind::ProtocolViolation,
message: format!("invalid PASV port tuple in response: {line}"),
source: None,
context: None,
})?;
Ok((host, port))
}
}
impl Downloader for LiveFtpSmokeDownloader {
fn start_http_transfer(
&self,
_task: &aria2_rust_pro_protocol::HttpTransferTaskModel,
) -> Result<HttpResponseModel, TransportError> {
Err(TransportError {
kind: TransportErrorKind::UnsupportedScheme,
message: "http unused in live ftp smoke".to_owned(),
source: None,
context: None,
})
}
fn start_ftp_transfer(
&self,
config: &FtpConfigModel,
request: &FtpRequestModel,
) -> Result<FtpResponseModel, TransportError> {
let mut control_stream =
TcpStream::connect((config.host.as_str(), config.port)).map_err(|error| {
TransportError {
kind: TransportErrorKind::NotConnected,
message: format!("failed to connect to live ftp smoke server: {error}"),
source: Some(error.to_string()),
context: None,
}
})?;
control_stream
.set_read_timeout(Some(Duration::from_secs(5)))
.expect("read timeout should set");
control_stream
.set_write_timeout(Some(Duration::from_secs(5)))
.expect("write timeout should set");
let mut control_reader =
BufReader::new(control_stream.try_clone().expect("clone should work"));
let _ = Self::expect_code(&mut control_reader, 220)?;
Self::write_command(
&mut control_stream,
&format!(
"USER {}\r\n",
config.username.as_deref().unwrap_or("anonymous")
),
)?;
let _ = Self::expect_code(&mut control_reader, 331)?;
Self::write_command(
&mut control_stream,
&format!("PASS {}\r\n", config.password.as_deref().unwrap_or("")),
)?;
let _ = Self::expect_code(&mut control_reader, 230)?;
Self::write_command(&mut control_stream, "TYPE I\r\n")?;
let _ = Self::expect_code(&mut control_reader, 200)?;
Self::write_command(&mut control_stream, "PASV\r\n")?;
let pasv = Self::expect_code(&mut control_reader, 227)?;
let (data_host, data_port) = Self::parse_pasv_addr(&pasv)?;
let mut data_stream =
TcpStream::connect((data_host.as_str(), data_port)).map_err(|error| {
TransportError {
kind: TransportErrorKind::NotConnected,
message: format!("failed to connect ftp data socket: {error}"),
source: Some(error.to_string()),
context: None,
}
})?;
let retr_path = request.path.as_deref().unwrap_or("/file.bin");
Self::write_command(&mut control_stream, &format!("RETR {retr_path}\r\n"))?;
let _ = Self::expect_code(&mut control_reader, 150)?;
let mut payload = Vec::new();
data_stream
.read_to_end(&mut payload)
.map_err(|error| TransportError {
kind: TransportErrorKind::Io,
message: format!("failed to read ftp data payload: {error}"),
source: Some(error.to_string()),
context: None,
})?;
let completion = Self::expect_code(&mut control_reader, 226)?;
let _ = Self::write_command(&mut control_stream, "QUIT\r\n");
Ok(FtpResponseModel {
code: 226,
message: completion.trim().to_owned(),
data: Some(payload),
path: request.path.clone(),
transferable: true,
})
}
fn start_sftp_transfer(
&self,
_config: &SftpConfigModel,
_request: &SftpRequestModel,
) -> Result<SftpResponseModel, TransportError> {
Err(TransportError {
kind: TransportErrorKind::UnsupportedScheme,
message: "sftp unused in live ftp smoke".to_owned(),
source: None,
context: None,
})
}
}
impl Downloader for LiveSftpSmokeDownloader {
fn start_http_transfer(
&self,
_task: &aria2_rust_pro_protocol::HttpTransferTaskModel,
) -> Result<HttpResponseModel, TransportError> {
Err(TransportError {
kind: TransportErrorKind::UnsupportedScheme,
message: "http unused in live sftp smoke".to_owned(),
source: None,
context: None,
})
}
fn start_ftp_transfer(
&self,
_config: &FtpConfigModel,
_request: &FtpRequestModel,
) -> Result<FtpResponseModel, TransportError> {
Err(TransportError {
kind: TransportErrorKind::UnsupportedScheme,
message: "ftp unused in live sftp smoke".to_owned(),
source: None,
context: None,
})
}
fn start_sftp_transfer(
&self,
config: &SftpConfigModel,
request: &SftpRequestModel,
) -> Result<SftpResponseModel, TransportError> {
let tcp = TcpStream::connect((config.host.as_str(), config.port)).map_err(|error| {
TransportError {
kind: TransportErrorKind::NotConnected,
message: format!("failed to connect live sftp smoke server: {error}"),
source: Some(error.to_string()),
context: None,
}
})?;
tcp.set_read_timeout(Some(Duration::from_secs(8))).ok();
tcp.set_write_timeout(Some(Duration::from_secs(8))).ok();
let mut session = ssh2::Session::new().map_err(|error| TransportError {
kind: TransportErrorKind::ProtocolViolation,
message: format!("failed to build ssh2 session: {error}"),
source: Some(error.to_string()),
context: None,
})?;
session.set_tcp_stream(tcp);
session.handshake().map_err(|error| TransportError {
kind: TransportErrorKind::ProtocolViolation,
message: format!("ssh handshake failed: {error}"),
source: Some(error.to_string()),
context: None,
})?;
let user = config.username.as_deref().unwrap_or("foo");
let pass = config.password.as_deref().unwrap_or("pass");
session
.userauth_password(user, pass)
.map_err(|error| TransportError {
kind: TransportErrorKind::ProtocolViolation,
message: format!("ssh auth failed: {error}"),
source: Some(error.to_string()),
context: None,
})?;
let sftp = session.sftp().map_err(|error| TransportError {
kind: TransportErrorKind::ProtocolViolation,
message: format!("failed to start sftp subsystem: {error}"),
source: Some(error.to_string()),
context: None,
})?;
let path = request.path.as_deref().unwrap_or("/upload/hello.txt");
let mut remote_file = sftp.open(path).map_err(|error| TransportError {
kind: TransportErrorKind::Io,
message: format!("failed to open remote path {path}: {error}"),
source: Some(error.to_string()),
context: None,
})?;
let mut payload = Vec::new();
remote_file
.read_to_end(&mut payload)
.map_err(|error| TransportError {
kind: TransportErrorKind::Io,
message: format!("failed to read remote payload: {error}"),
source: Some(error.to_string()),
context: None,
})?;
Ok(SftpResponseModel {
ok: true,
message: "sftp read ok".to_owned(),
payload: Some(payload),
path: request.path.clone(),
transferable: true,
})
}
}
impl SequencedHttpDownloader {
fn new(responses: Vec<Result<HttpResponseModel, TransportError>>) -> Self {
Self {
responses: Arc::new(Mutex::new(responses)),
requests: Arc::new(Mutex::new(Vec::new())),
}
}
fn recorded_requests(&self) -> Vec<aria2_rust_pro_protocol::HttpTransferTaskModel> {
self.requests.lock().expect("lock should work").clone()
}
}
impl Downloader for SequencedHttpDownloader {
fn start_http_transfer(
&self,
task: &aria2_rust_pro_protocol::HttpTransferTaskModel,
) -> Result<HttpResponseModel, TransportError> {
self.requests
.lock()
.expect("lock should work")
.push(task.clone());
let mut guard = self.responses.lock().expect("lock should work");
if guard.is_empty() {
return Err(TransportError {
kind: TransportErrorKind::Io,
message: "no scripted HTTP response remaining".to_owned(),
source: None,
context: None,
});
}
guard.remove(0)
}
fn start_ftp_transfer(
&self,
_config: &FtpConfigModel,
_request: &FtpRequestModel,
) -> Result<FtpResponseModel, TransportError> {
Err(TransportError {
kind: TransportErrorKind::UnsupportedScheme,
message: "unused in test".to_owned(),
source: None,
context: None,
})
}
fn start_sftp_transfer(
&self,
_config: &SftpConfigModel,
_request: &SftpRequestModel,
) -> Result<SftpResponseModel, TransportError> {
Err(TransportError {
kind: TransportErrorKind::UnsupportedScheme,
message: "unused in test".to_owned(),
source: None,
context: None,
})
}
}
#[derive(Clone, Debug)]
struct ConcurrentProbeDownloader {
bootstrap: HttpResponseModel,
bootstrap_delay: Duration,
ranged_responses: Arc<Mutex<VecDeque<(u64, HttpResponseModel)>>>,
requests: Arc<Mutex<Vec<aria2_rust_pro_protocol::HttpTransferTaskModel>>>,
active_calls: Arc<AtomicUsize>,
max_concurrent_calls: Arc<AtomicUsize>,
ranged_delay: Duration,
}
impl ConcurrentProbeDownloader {
fn new(
bootstrap: HttpResponseModel,
ranged_responses: Vec<(u64, HttpResponseModel)>,
ranged_delay: Duration,
) -> Self {
Self {
bootstrap,
bootstrap_delay: Duration::ZERO,
ranged_responses: Arc::new(Mutex::new(VecDeque::from(ranged_responses))),
requests: Arc::new(Mutex::new(Vec::new())),
active_calls: Arc::new(AtomicUsize::new(0)),
max_concurrent_calls: Arc::new(AtomicUsize::new(0)),
ranged_delay,
}
}
fn with_bootstrap_delay(
bootstrap: HttpResponseModel,
bootstrap_delay: Duration,
ranged_responses: Vec<(u64, HttpResponseModel)>,
ranged_delay: Duration,
) -> Self {
Self {
bootstrap,
bootstrap_delay,
ranged_responses: Arc::new(Mutex::new(VecDeque::from(ranged_responses))),
requests: Arc::new(Mutex::new(Vec::new())),
active_calls: Arc::new(AtomicUsize::new(0)),
max_concurrent_calls: Arc::new(AtomicUsize::new(0)),
ranged_delay,
}
}
fn recorded_requests(&self) -> Vec<aria2_rust_pro_protocol::HttpTransferTaskModel> {
self.requests.lock().expect("lock should work").clone()
}
fn max_concurrent_calls(&self) -> usize {
self.max_concurrent_calls.load(Ordering::SeqCst)
}
fn note_active_call(&self) {
let current = self
.active_calls
.fetch_add(1, Ordering::SeqCst)
.saturating_add(1);
let _ =
self.max_concurrent_calls
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |seen| {
(current > seen).then_some(current)
});
}
fn finish_active_call(&self) {
self.active_calls.fetch_sub(1, Ordering::SeqCst);
}
}
impl Downloader for ConcurrentProbeDownloader {
fn start_http_transfer(
&self,
task: &aria2_rust_pro_protocol::HttpTransferTaskModel,
) -> Result<HttpResponseModel, TransportError> {
self.requests
.lock()
.expect("lock should work")
.push(task.clone());
self.note_active_call();
let response = task.request.range.as_ref().map_or_else(
|| {
if !self.bootstrap_delay.is_zero() {
thread::sleep(self.bootstrap_delay);
}
Ok(self.bootstrap.clone())
},
|range| {
thread::sleep(self.ranged_delay);
let mut queued = self.ranged_responses.lock().expect("lock should work");
let index = queued
.iter()
.position(|(start, _)| *start == range.start)
.expect("matching ranged response should exist");
Ok(queued
.remove(index)
.expect("queued ranged response should exist")
.1)
},
);
self.finish_active_call();
response
}
fn start_ftp_transfer(
&self,
_config: &FtpConfigModel,
_request: &FtpRequestModel,
) -> Result<FtpResponseModel, TransportError> {
Err(TransportError {
kind: TransportErrorKind::UnsupportedScheme,
message: "probe downloader does not implement ftp".to_owned(),
source: None,
context: None,
})
}
fn start_sftp_transfer(
&self,
_config: &SftpConfigModel,
_request: &SftpRequestModel,
) -> Result<SftpResponseModel, TransportError> {
Err(TransportError {
kind: TransportErrorKind::UnsupportedScheme,
message: "probe downloader does not implement sftp".to_owned(),
source: None,
context: None,
})
}
}
mod command_surface;
mod integration_surface;
mod runtime_execution;