mirror of
https://github.com/nimbold/Firelink.git
synced 2026-08-11 03:59:09 +00:00
feat(torrents): add live peer controls
This commit is contained in:
@@ -185,6 +185,12 @@ pub struct DownloadItem {
|
||||
#[serde(default)]
|
||||
#[ts(optional)]
|
||||
pub torrent_upload_limit: Option<String>,
|
||||
#[serde(default)]
|
||||
#[ts(optional)]
|
||||
pub torrent_max_peers: Option<u32>,
|
||||
#[serde(default)]
|
||||
#[ts(optional)]
|
||||
pub torrent_peer_speed_limit: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, TS)]
|
||||
|
||||
+14
-1
@@ -6300,6 +6300,19 @@ async fn set_torrent_upload_limit(
|
||||
.await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn set_torrent_peer_options(
|
||||
state: tauri::State<'_, AppState>,
|
||||
id: String,
|
||||
max_peers: Option<i64>,
|
||||
peer_speed_limit: Option<String>,
|
||||
) -> Result<(), String> {
|
||||
state
|
||||
.queue_manager
|
||||
.set_aria2_torrent_peer_options(&id, max_peers, peer_speed_limit)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) fn normalize_speed_limit_for_aria2(limit: &str) -> Option<String> {
|
||||
let trimmed = limit.trim();
|
||||
if trimmed.is_empty() {
|
||||
@@ -10679,7 +10692,7 @@ pub fn run() {
|
||||
authorize_keychain_access,
|
||||
acknowledge_pairing_token_change,
|
||||
check_file_exists, toggle_tray_icon, set_extension_pairing_token,
|
||||
get_extension_server_port, set_extension_frontend_ready, ack_extension_download, set_concurrent_limit, set_queue_concurrency_limits, set_download_speed_limit, set_torrent_upload_limit, set_global_speed_limit, remove_download, get_download_primary_path,
|
||||
get_extension_server_port, set_extension_frontend_ready, ack_extension_download, set_concurrent_limit, set_queue_concurrency_limits, set_download_speed_limit, set_torrent_upload_limit, set_torrent_peer_options, set_global_speed_limit, remove_download, get_download_primary_path,
|
||||
detach_download_for_reconfigure,
|
||||
enqueue_download, enqueue_many, cancel_enqueue_generation, move_in_queue, move_many_in_queue, remove_from_queue, get_pending_order,
|
||||
commands::reveal_in_file_manager, commands::open_downloaded_file,
|
||||
|
||||
@@ -212,6 +212,8 @@ pub struct SpawnPayload {
|
||||
pub torrent_seed_time: Option<f64>,
|
||||
pub torrent_seed_ratio: Option<f64>,
|
||||
pub torrent_upload_limit: Option<String>,
|
||||
pub torrent_max_peers: Option<u32>,
|
||||
pub torrent_peer_speed_limit: Option<String>,
|
||||
}
|
||||
|
||||
/// A sidecar spawner. In production this calls the real aria2/yt-dlp
|
||||
@@ -268,6 +270,17 @@ pub trait SidecarSpawner: Send + Sync + 'static {
|
||||
Err("live torrent upload limits are unavailable".to_string())
|
||||
}
|
||||
|
||||
/// Change the peer cap and low-speed peer expansion threshold without
|
||||
/// replacing the Torrent GID or queue permit.
|
||||
async fn set_torrent_peer_options(
|
||||
&self,
|
||||
_gid: &str,
|
||||
_max_peers: u32,
|
||||
_peer_speed_limit: &str,
|
||||
) -> Result<(), String> {
|
||||
Err("live torrent peer options are unavailable".to_string())
|
||||
}
|
||||
|
||||
/// Run a media download to completion. The permit is parked for the full
|
||||
/// duration; release is handled by QueueManager on the runner's exit.
|
||||
async fn run_media(
|
||||
@@ -877,6 +890,78 @@ impl<R: tauri::Runtime> QueueManager<R> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Change active Torrent peer settings without replacing the GID or queue
|
||||
/// permit. Clearing a setting restores Aria2's documented default while
|
||||
/// keeping the persisted payload override-free for future retries.
|
||||
pub async fn set_aria2_torrent_peer_options(
|
||||
&self,
|
||||
id: &str,
|
||||
max_peers: Option<i64>,
|
||||
peer_speed_limit: Option<String>,
|
||||
) -> Result<(), String> {
|
||||
let normalized_max_peers = normalize_torrent_max_peers(max_peers)?;
|
||||
let normalized_peer_speed_limit =
|
||||
normalize_torrent_peer_speed_limit(peer_speed_limit.as_deref())?;
|
||||
let rpc_max_peers = normalized_max_peers.unwrap_or(ARIA2_DEFAULT_TORRENT_MAX_PEERS);
|
||||
let rpc_peer_speed_limit = normalized_peer_speed_limit
|
||||
.as_deref()
|
||||
.unwrap_or(ARIA2_DEFAULT_TORRENT_PEER_SPEED_LIMIT);
|
||||
let _control_guard = self.acquire_aria2_control(id).await;
|
||||
|
||||
if !self.is_registered(id).await
|
||||
|| !matches!(self.active_kind(id).await, Some(TaskKind::Aria2))
|
||||
{
|
||||
return Err("download is not an active aria2 transfer".to_string());
|
||||
}
|
||||
let is_torrent = self
|
||||
.aria2_payloads
|
||||
.lock()
|
||||
.await
|
||||
.get(id)
|
||||
.is_some_and(|payload| payload.is_torrent);
|
||||
if !is_torrent {
|
||||
return Err("download is not a Torrent transfer".to_string());
|
||||
}
|
||||
let gid = self
|
||||
.aria2_gid_for_download(id)
|
||||
.ok_or_else(|| "active Torrent transfer has no gid".to_string())?;
|
||||
let expected_mapping = self
|
||||
.aria2_gid_mapping(&gid)
|
||||
.ok_or_else(|| "active Torrent transfer has no current gid mapping".to_string())?;
|
||||
if expected_mapping.id != id {
|
||||
return Err("aria2 gid belongs to another download".to_string());
|
||||
}
|
||||
if !self
|
||||
.is_aria2_control_epoch_current(id, expected_mapping.epoch)
|
||||
.await
|
||||
{
|
||||
return Err("active Torrent transfer has a stale control epoch".to_string());
|
||||
}
|
||||
|
||||
self.spawner
|
||||
.set_torrent_peer_options(&gid, rpc_max_peers, rpc_peer_speed_limit)
|
||||
.await?;
|
||||
|
||||
let still_current = self.is_registered(id).await
|
||||
&& matches!(self.active_kind(id).await, Some(TaskKind::Aria2))
|
||||
&& self
|
||||
.is_aria2_control_epoch_current(id, expected_mapping.epoch)
|
||||
.await
|
||||
&& self.is_current_aria2_gid_mapping(&gid, &expected_mapping)
|
||||
&& self.aria2_gid_for_download(id).as_deref() == Some(gid.as_str());
|
||||
if !still_current {
|
||||
return Err("Torrent lifecycle changed while setting peer options".to_string());
|
||||
}
|
||||
|
||||
let mut payloads = self.aria2_payloads.lock().await;
|
||||
let payload = payloads
|
||||
.get_mut(id)
|
||||
.ok_or_else(|| "active Torrent transfer payload is unavailable".to_string())?;
|
||||
payload.torrent_max_peers = normalized_max_peers;
|
||||
payload.torrent_peer_speed_limit = normalized_peer_speed_limit;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pop the next task, or None if empty.
|
||||
pub async fn pop_front(&self) -> Option<QueuedTask> {
|
||||
self.pending.lock().await.pop_front()
|
||||
@@ -2958,6 +3043,9 @@ pub struct ProductionSpawner {
|
||||
|
||||
const ARIA2_MIN_SPLIT_SIZE: &str = "1M";
|
||||
const ARIA2_STREAM_PIECE_SELECTOR: &str = "inorder";
|
||||
const ARIA2_DEFAULT_TORRENT_MAX_PEERS: u32 = 55;
|
||||
const ARIA2_DEFAULT_TORRENT_PEER_SPEED_LIMIT: &str = "50K";
|
||||
const MAX_TORRENT_MAX_PEERS: u32 = 1000;
|
||||
|
||||
fn apply_aria2_connection_options(
|
||||
options: &mut serde_json::Map<String, serde_json::Value>,
|
||||
@@ -2996,6 +3084,27 @@ fn format_aria2_torrent_number(value: f64, field: &str) -> Result<String, String
|
||||
Ok(value.to_string())
|
||||
}
|
||||
|
||||
fn normalize_torrent_max_peers(value: Option<i64>) -> Result<Option<u32>, String> {
|
||||
let Some(value) = value else {
|
||||
return Ok(None);
|
||||
};
|
||||
if !(0..=i64::from(MAX_TORRENT_MAX_PEERS)).contains(&value) {
|
||||
return Err(format!(
|
||||
"torrent maximum peers must be between 0 and {MAX_TORRENT_MAX_PEERS}"
|
||||
));
|
||||
}
|
||||
Ok(Some(value as u32))
|
||||
}
|
||||
|
||||
fn normalize_torrent_peer_speed_limit(value: Option<&str>) -> Result<Option<String>, String> {
|
||||
let Some(value) = value.map(str::trim).filter(|value| !value.is_empty()) else {
|
||||
return Ok(None);
|
||||
};
|
||||
crate::normalize_speed_limit_for_aria2(value)
|
||||
.map(Some)
|
||||
.ok_or_else(|| "torrent peer speed limit must be greater than zero".to_string())
|
||||
}
|
||||
|
||||
fn apply_aria2_torrent_options(
|
||||
options: &mut serde_json::Map<String, serde_json::Value>,
|
||||
payload: &SpawnPayload,
|
||||
@@ -3033,6 +3142,27 @@ fn apply_aria2_torrent_options(
|
||||
.ok_or_else(|| "torrent upload limit must be greater than zero".to_string())?;
|
||||
options.insert("max-upload-limit".to_string(), serde_json::json!(normalized));
|
||||
}
|
||||
|
||||
if let Some(max_peers) = payload.torrent_max_peers {
|
||||
if max_peers > MAX_TORRENT_MAX_PEERS {
|
||||
return Err(format!(
|
||||
"torrent maximum peers must be between 0 and {MAX_TORRENT_MAX_PEERS}"
|
||||
));
|
||||
}
|
||||
options.insert(
|
||||
"bt-max-peers".to_string(),
|
||||
serde_json::json!(max_peers.to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(peer_speed_limit) = payload.torrent_peer_speed_limit.as_deref() {
|
||||
let normalized = normalize_torrent_peer_speed_limit(Some(peer_speed_limit))
|
||||
?.ok_or_else(|| "torrent peer speed limit must be greater than zero".to_string())?;
|
||||
options.insert(
|
||||
"bt-request-peer-speed-limit".to_string(),
|
||||
serde_json::json!(normalized),
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -3289,6 +3419,33 @@ impl SidecarSpawner for ProductionSpawner {
|
||||
}
|
||||
}
|
||||
|
||||
async fn set_torrent_peer_options(
|
||||
&self,
|
||||
gid: &str,
|
||||
max_peers: u32,
|
||||
peer_speed_limit: &str,
|
||||
) -> Result<(), String> {
|
||||
let state = self.app_handle.state::<crate::AppState>();
|
||||
let result = crate::rpc_call(
|
||||
state.aria2_port.load(std::sync::atomic::Ordering::Relaxed),
|
||||
&state.aria2_secret,
|
||||
"aria2.changeOption",
|
||||
serde_json::json!([gid, {
|
||||
"bt-max-peers": max_peers.to_string(),
|
||||
"bt-request-peer-speed-limit": peer_speed_limit,
|
||||
}]),
|
||||
)
|
||||
.await
|
||||
.map_err(|error| format!("aria2.changeOption failed for gid {gid}: {error}"))?;
|
||||
match result.as_str() {
|
||||
Some("OK") => Ok(()),
|
||||
Some(value) => Err(format!(
|
||||
"aria2.changeOption returned unexpected result {value} for gid {gid}"
|
||||
)),
|
||||
None => Err("aria2.changeOption returned a non-string result".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn recreate_uri(
|
||||
&self,
|
||||
id: &str,
|
||||
@@ -3589,6 +3746,12 @@ pub struct EnqueueItem {
|
||||
pub torrent_upload_limit: Option<String>,
|
||||
#[serde(default)]
|
||||
#[ts(optional)]
|
||||
pub torrent_max_peers: Option<u32>,
|
||||
#[serde(default)]
|
||||
#[ts(optional)]
|
||||
pub torrent_peer_speed_limit: Option<String>,
|
||||
#[serde(default)]
|
||||
#[ts(optional)]
|
||||
pub lifecycle_generation: Option<String>,
|
||||
}
|
||||
|
||||
@@ -3634,6 +3797,8 @@ impl EnqueueItem {
|
||||
torrent_seed_time: self.torrent_seed_time,
|
||||
torrent_seed_ratio: self.torrent_seed_ratio,
|
||||
torrent_upload_limit: self.torrent_upload_limit,
|
||||
torrent_max_peers: self.torrent_max_peers,
|
||||
torrent_peer_speed_limit: self.torrent_peer_speed_limit,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -3708,6 +3873,8 @@ mod tests {
|
||||
torrent_seed_time: Some(30.0),
|
||||
torrent_seed_ratio: Some(1.5),
|
||||
torrent_upload_limit: Some("2 MiB/s".to_string()),
|
||||
torrent_max_peers: Some(120),
|
||||
torrent_peer_speed_limit: Some("2M".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -3719,6 +3886,14 @@ mod tests {
|
||||
options.get("max-upload-limit"),
|
||||
Some(&serde_json::json!("2M"))
|
||||
);
|
||||
assert_eq!(
|
||||
options.get("bt-max-peers"),
|
||||
Some(&serde_json::json!("120"))
|
||||
);
|
||||
assert_eq!(
|
||||
options.get("bt-request-peer-speed-limit"),
|
||||
Some(&serde_json::json!("2M"))
|
||||
);
|
||||
assert!(torrent_seeding_requested(&payload));
|
||||
}
|
||||
|
||||
@@ -3751,6 +3926,27 @@ mod tests {
|
||||
assert!(error.contains("seed time"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn torrent_options_reject_invalid_peer_values() {
|
||||
let mut options = serde_json::Map::new();
|
||||
let payload = SpawnPayload {
|
||||
is_torrent: true,
|
||||
torrent_max_peers: Some(MAX_TORRENT_MAX_PEERS + 1),
|
||||
..Default::default()
|
||||
};
|
||||
let error = apply_aria2_torrent_options(&mut options, &payload).unwrap_err();
|
||||
assert!(error.contains("maximum peers"));
|
||||
|
||||
let mut options = serde_json::Map::new();
|
||||
let payload = SpawnPayload {
|
||||
is_torrent: true,
|
||||
torrent_peer_speed_limit: Some("0".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
let error = apply_aria2_torrent_options(&mut options, &payload).unwrap_err();
|
||||
assert!(error.contains("peer speed limit"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn seeding_outcome_keeps_torrent_ownership_and_permit_live() {
|
||||
let app = tauri::test::mock_builder()
|
||||
|
||||
@@ -20,7 +20,13 @@ struct CountingSpawner {
|
||||
block_torrent_upload_limit: std::sync::atomic::AtomicBool,
|
||||
torrent_upload_limit_started: tokio::sync::Notify,
|
||||
torrent_upload_limit_release: tokio::sync::Notify,
|
||||
torrent_peer_options_calls: AtomicUsize,
|
||||
last_torrent_peer_options: std::sync::Mutex<Option<(u32, String)>>,
|
||||
block_torrent_peer_options: std::sync::atomic::AtomicBool,
|
||||
torrent_peer_options_started: tokio::sync::Notify,
|
||||
torrent_peer_options_release: tokio::sync::Notify,
|
||||
add_speed_limits: std::sync::Mutex<Vec<Option<String>>>,
|
||||
add_peer_options: std::sync::Mutex<Vec<(Option<u32>, Option<String>)>>,
|
||||
block_speed_limit: std::sync::atomic::AtomicBool,
|
||||
speed_limit_started: tokio::sync::Notify,
|
||||
speed_limit_release: tokio::sync::Notify,
|
||||
@@ -198,7 +204,13 @@ impl CountingSpawner {
|
||||
block_torrent_upload_limit: std::sync::atomic::AtomicBool::new(false),
|
||||
torrent_upload_limit_started: tokio::sync::Notify::new(),
|
||||
torrent_upload_limit_release: tokio::sync::Notify::new(),
|
||||
torrent_peer_options_calls: AtomicUsize::new(0),
|
||||
last_torrent_peer_options: std::sync::Mutex::new(None),
|
||||
block_torrent_peer_options: std::sync::atomic::AtomicBool::new(false),
|
||||
torrent_peer_options_started: tokio::sync::Notify::new(),
|
||||
torrent_peer_options_release: tokio::sync::Notify::new(),
|
||||
add_speed_limits: std::sync::Mutex::new(Vec::new()),
|
||||
add_peer_options: std::sync::Mutex::new(Vec::new()),
|
||||
block_speed_limit: std::sync::atomic::AtomicBool::new(false),
|
||||
speed_limit_started: tokio::sync::Notify::new(),
|
||||
speed_limit_release: tokio::sync::Notify::new(),
|
||||
@@ -275,6 +287,13 @@ impl firelink_lib::queue::SidecarSpawner for CountingSpawner {
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(payload.speed_limit.clone());
|
||||
self.add_peer_options
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push((
|
||||
payload.torrent_max_peers,
|
||||
payload.torrent_peer_speed_limit.clone(),
|
||||
));
|
||||
Ok(format!("gid-{call}"))
|
||||
}
|
||||
async fn remove_uri(&self, _gid: &str) -> Result<(), String> {
|
||||
@@ -312,6 +331,24 @@ impl firelink_lib::queue::SidecarSpawner for CountingSpawner {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
async fn set_torrent_peer_options(
|
||||
&self,
|
||||
_gid: &str,
|
||||
max_peers: u32,
|
||||
peer_speed_limit: &str,
|
||||
) -> Result<(), String> {
|
||||
self.torrent_peer_options_calls.fetch_add(1, Ordering::SeqCst);
|
||||
*self.last_torrent_peer_options.lock().unwrap() =
|
||||
Some((max_peers, peer_speed_limit.to_string()));
|
||||
if self
|
||||
.block_torrent_peer_options
|
||||
.load(std::sync::atomic::Ordering::SeqCst)
|
||||
{
|
||||
self.torrent_peer_options_started.notify_one();
|
||||
self.torrent_peer_options_release.notified().await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
async fn run_media(&self, _id: &str, _payload: &SpawnPayload, _generation: u64) -> Result<(), String> {
|
||||
self.media_calls.fetch_add(1, Ordering::SeqCst);
|
||||
Ok(())
|
||||
@@ -736,6 +773,137 @@ async fn live_torrent_upload_limit_does_not_update_after_gid_replacement() {
|
||||
dispatcher.abort();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn live_torrent_peer_options_update_current_gid_and_payload() {
|
||||
let (manager, spawner) = make_manager(1);
|
||||
let manager = Arc::new(manager);
|
||||
let mut task = aria2_task("torrent-peer-options");
|
||||
task.payload.is_torrent = true;
|
||||
manager.push(task).await.unwrap();
|
||||
|
||||
let dispatcher = {
|
||||
let manager = Arc::clone(&manager);
|
||||
tokio::spawn(async move { manager.run_dispatcher().await })
|
||||
};
|
||||
timeout(Duration::from_secs(1), async {
|
||||
loop {
|
||||
if manager.aria2_gid_for_download("torrent-peer-options").is_some() {
|
||||
break;
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||
}
|
||||
})
|
||||
.await
|
||||
.expect("aria2 dispatch should register a Torrent gid");
|
||||
|
||||
manager
|
||||
.set_aria2_torrent_peer_options(
|
||||
"torrent-peer-options",
|
||||
Some(120),
|
||||
Some("2M".to_string()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(spawner.torrent_peer_options_calls.load(Ordering::SeqCst), 1);
|
||||
assert_eq!(
|
||||
spawner.last_torrent_peer_options.lock().unwrap().as_ref(),
|
||||
Some(&(120, "2M".to_string()))
|
||||
);
|
||||
manager
|
||||
.set_aria2_torrent_peer_options("torrent-peer-options", None, None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(spawner.torrent_peer_options_calls.load(Ordering::SeqCst), 2);
|
||||
assert_eq!(
|
||||
spawner.last_torrent_peer_options.lock().unwrap().as_ref(),
|
||||
Some(&(55, "50K".to_string()))
|
||||
);
|
||||
manager.clear_aria2_retry_state("torrent-peer-options").await;
|
||||
manager.forget_aria2_gid("torrent-peer-options").await;
|
||||
manager.release_permit("torrent-peer-options").await;
|
||||
manager.release_registered_id("torrent-peer-options").await;
|
||||
dispatcher.abort();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn live_torrent_peer_options_reject_invalid_values_and_stale_gid() {
|
||||
let (manager, spawner) = make_manager(1);
|
||||
let manager = Arc::new(manager);
|
||||
let mut task = aria2_task("torrent-peer-options-stale");
|
||||
task.payload.is_torrent = true;
|
||||
manager.push(task).await.unwrap();
|
||||
let dispatcher = {
|
||||
let manager = Arc::clone(&manager);
|
||||
tokio::spawn(async move { manager.run_dispatcher().await })
|
||||
};
|
||||
timeout(Duration::from_secs(1), async {
|
||||
loop {
|
||||
if manager
|
||||
.aria2_gid_for_download("torrent-peer-options-stale")
|
||||
.is_some()
|
||||
{
|
||||
break;
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||
}
|
||||
})
|
||||
.await
|
||||
.expect("aria2 dispatch should register a Torrent gid");
|
||||
|
||||
assert!(manager
|
||||
.set_aria2_torrent_peer_options(
|
||||
"torrent-peer-options-stale",
|
||||
Some(1001),
|
||||
Some("2M".to_string()),
|
||||
)
|
||||
.await
|
||||
.is_err());
|
||||
assert!(manager
|
||||
.set_aria2_torrent_peer_options(
|
||||
"torrent-peer-options-stale",
|
||||
Some(100),
|
||||
Some("not-a-rate".to_string()),
|
||||
)
|
||||
.await
|
||||
.is_err());
|
||||
assert_eq!(spawner.torrent_peer_options_calls.load(Ordering::SeqCst), 0);
|
||||
|
||||
spawner
|
||||
.block_torrent_peer_options
|
||||
.store(true, std::sync::atomic::Ordering::SeqCst);
|
||||
let started = spawner.torrent_peer_options_started.notified();
|
||||
let setter = {
|
||||
let manager = Arc::clone(&manager);
|
||||
tokio::spawn(async move {
|
||||
manager
|
||||
.set_aria2_torrent_peer_options(
|
||||
"torrent-peer-options-stale",
|
||||
Some(100),
|
||||
Some("2M".to_string()),
|
||||
)
|
||||
.await
|
||||
})
|
||||
};
|
||||
timeout(Duration::from_secs(1), started)
|
||||
.await
|
||||
.expect("Torrent peer options RPC should start");
|
||||
|
||||
manager
|
||||
.remember_gid(
|
||||
"torrent-peer-options-stale".to_string(),
|
||||
"gid-replaced".to_string(),
|
||||
)
|
||||
.await;
|
||||
spawner.torrent_peer_options_release.notify_one();
|
||||
assert!(setter.await.unwrap().is_err());
|
||||
|
||||
manager.clear_aria2_retry_state("torrent-peer-options-stale").await;
|
||||
manager.forget_aria2_gid("torrent-peer-options-stale").await;
|
||||
manager.release_permit("torrent-peer-options-stale").await;
|
||||
manager.release_registered_id("torrent-peer-options-stale").await;
|
||||
dispatcher.abort();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn retry_readds_aria2_with_the_latest_live_speed_limit() {
|
||||
use firelink_lib::queue::PendingOutcome;
|
||||
@@ -798,6 +966,80 @@ async fn retry_readds_aria2_with_the_latest_live_speed_limit() {
|
||||
dispatcher.abort();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn retry_readds_torrent_with_the_latest_live_peer_options() {
|
||||
use firelink_lib::queue::PendingOutcome;
|
||||
|
||||
let (mgr, spawner) = make_manager(1);
|
||||
let manager = Arc::new(mgr);
|
||||
let mut task = aria2_task("peer-options-retry");
|
||||
task.payload.is_torrent = true;
|
||||
task.payload.max_tries = Some(1);
|
||||
task.payload.torrent_max_peers = Some(120);
|
||||
task.payload.torrent_peer_speed_limit = Some("1M".to_string());
|
||||
manager.push(task).await.unwrap();
|
||||
let dispatcher = {
|
||||
let manager = Arc::clone(&manager);
|
||||
tokio::spawn(async move { manager.run_dispatcher().await })
|
||||
};
|
||||
|
||||
timeout(Duration::from_secs(1), async {
|
||||
loop {
|
||||
if spawner.add_uri_calls.load(Ordering::SeqCst) >= 1 {
|
||||
break;
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||
}
|
||||
})
|
||||
.await
|
||||
.expect("initial Torrent add should run");
|
||||
|
||||
manager
|
||||
.handle_aria2_event(
|
||||
"gid-1",
|
||||
PendingOutcome::Error(
|
||||
"aria2 error code 1: Failed to receive data, cause: protocol error".to_string(),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
manager
|
||||
.set_aria2_torrent_peer_options(
|
||||
"peer-options-retry",
|
||||
Some(240),
|
||||
Some("2M".to_string()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
timeout(Duration::from_secs(4), async {
|
||||
loop {
|
||||
if spawner.add_uri_calls.load(Ordering::SeqCst) >= 2 {
|
||||
break;
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(25)).await;
|
||||
}
|
||||
})
|
||||
.await
|
||||
.expect("Torrent retry should re-add after backoff");
|
||||
assert_eq!(
|
||||
spawner.add_peer_options.lock().unwrap().as_slice(),
|
||||
&[
|
||||
(Some(120), Some("1M".to_string())),
|
||||
(Some(240), Some("2M".to_string()))
|
||||
]
|
||||
);
|
||||
|
||||
manager
|
||||
.clear_aria2_retry_state("peer-options-retry")
|
||||
.await;
|
||||
manager
|
||||
.forget_aria2_gid("peer-options-retry")
|
||||
.await;
|
||||
manager.release_permit("peer-options-retry").await;
|
||||
manager.release_registered_id("peer-options-retry").await;
|
||||
dispatcher.abort();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn stale_aria2_permit_candidate_cannot_replace_current_permit() {
|
||||
let (mgr, _spawner) = make_manager(2);
|
||||
|
||||
Reference in New Issue
Block a user