use std::sync::{Arc, Mutex, RwLock};
use crate::dflt::default_http_transfer::default_breakpoint_arcs;
use crate::down_pounce_builder::DownloadPounceBuilder;
use crate::file_transfer_record::FileTransferRecord;
use crate::http_breakpoint::BreakpointDownloadHttpConfig;
use crate::ids::GlobalProgressListenerId;
use crate::inner::cb_dispatcher;
use crate::inner::group_state::{GroupState, RecordEntry};
use crate::inner::inner_task::InnerTask;
use crate::inner::scheduler_state::SchedulerState;
use crate::inner::task_callbacks::{ProgressCb, TaskCallbacks};
use crate::inner::UniqueId;
pub(crate) async fn live_download_state(url_tag: &str) -> (SchedulerState, UniqueId) {
live_download_state_inner(url_tag, None).await
}
pub(crate) async fn live_download_state_with_preset(
url_tag: &str,
preset: u64,
) -> (SchedulerState, UniqueId) {
live_download_state_inner(url_tag, Some(preset)).await
}
async fn live_download_state_inner(
url_tag: &str,
preset: Option<u64>,
) -> (SchedulerState, UniqueId) {
let mut path = std::env::temp_dir();
let ts = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("clock")
.as_nanos();
path.push(format!("rusty_cat_known_totals_{url_tag}_{ts}.bin"));
let mut builder = DownloadPounceBuilder::new(
"known-totals.bin",
&path,
1024,
format!("https://placeholder.invalid/{url_tag}/{ts}"),
);
if let Some(preset) = preset {
builder = builder.with_total_size(preset);
}
let pounce = builder.build();
let (def_up, def_down) = default_breakpoint_arcs();
let inner = InnerTask::from_pounce(
pounce,
BreakpointDownloadHttpConfig::default(),
None,
def_up,
def_down,
)
.await
.expect("from_pounce");
let key = inner.dedupe_key();
let (cb_submit, cb_join) = cb_dispatcher::start().expect("start dispatcher");
std::mem::forget(cb_join);
let mut state = SchedulerState::new(1, 1, Arc::new(RwLock::new(Vec::new())), cb_submit);
state
.task_id_to_dedupe_mut()
.insert(inner.task_id(), key.clone());
state.offsets_mut().insert(key.clone(), 0);
let entry = RecordEntry::new(inner.clone(), TaskCallbacks::empty());
state
.groups_mut()
.insert(key.clone(), GroupState::new(inner.clone(), entry));
(state, key)
}
pub(crate) fn attach_capture(state: &SchedulerState) -> Arc<Mutex<Vec<FileTransferRecord>>> {
let records: Arc<Mutex<Vec<FileTransferRecord>>> = Arc::new(Mutex::new(Vec::new()));
let sink = records.clone();
let cb: ProgressCb = Arc::new(move |rec: FileTransferRecord| {
sink.lock().expect("records lock").push(rec);
});
state
.global_progress_listener()
.write()
.expect("listener lock")
.push((GlobalProgressListenerId::new(), cb));
records
}
pub(crate) async fn wait_for_record<F>(
records: &Arc<Mutex<Vec<FileTransferRecord>>>,
mut pred: F,
) -> FileTransferRecord
where
F: FnMut(&FileTransferRecord) -> bool,
{
for _ in 0..200 {
if let Some(rec) = records
.lock()
.expect("records lock")
.iter()
.find(|r| pred(r))
.cloned()
{
return rec;
}
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
}
panic!("expected record did not arrive within 2s");
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn download_harness_builds_group_with_zero_preset_total() {
let (state, key) = live_download_state("smoke").await;
let group = state.groups().get(&key).expect("group exists");
assert_eq!(group.entry().inner().total_size(), 0);
let records = attach_capture(&state);
assert!(records.lock().expect("lock").is_empty());
}
}