1use std::num::{NonZeroU8, NonZeroU32};
7use std::time::Duration;
8
9use derive_deftly::Deftly;
10use tor_basic_utils::retry::RetryDelay;
11use tor_config::ConfigBuildError;
12use tor_config::derive::prelude::*;
13
14#[derive(Debug, Deftly, Copy, Clone, Eq, PartialEq)]
17#[derive_deftly(TorConfig)]
18pub struct DownloadSchedule {
19 #[deftly(tor_config(default = r#"NonZeroU32::new(3).expect("Somehow 3==0")"#))]
21 attempts: NonZeroU32,
22
23 #[deftly(tor_config(default = "Duration::from_millis(1000)"))]
26 initial_delay: Duration,
27
28 #[deftly(tor_config(default = r#"NonZeroU8::new(1).expect("Somehow 1==0")"#))]
31 parallelism: NonZeroU8,
32}
33
34impl DownloadScheduleBuilder {
35 pub fn build_retry_bootstrap(&self) -> Result<DownloadSchedule, ConfigBuildError> {
37 let mut bld = self.clone();
38 bld.attempts.get_or_insert(128);
39 bld.initial_delay.get_or_insert_with(|| Duration::new(1, 0));
40 bld.parallelism.get_or_insert(1);
41 bld.build()
42 }
43
44 pub fn build_retry_microdescs(&self) -> Result<DownloadSchedule, ConfigBuildError> {
46 let mut bld = self.clone();
47 bld.attempts.get_or_insert(3);
48 bld.initial_delay.get_or_insert_with(|| Duration::new(1, 0));
49 bld.parallelism.get_or_insert(4);
50 bld.build()
51 }
52}
53
54impl DownloadSchedule {
55 pub fn attempts(&self) -> impl Iterator<Item = u32> + use<> {
58 0..(self.attempts.into())
59 }
60
61 pub fn n_attempts(&self) -> u32 {
64 self.attempts.into()
65 }
66
67 pub fn parallelism(&self) -> u8 {
70 self.parallelism.into()
71 }
72
73 pub fn schedule(&self) -> RetryDelay {
77 RetryDelay::from_duration(self.initial_delay)
78 }
79}
80
81#[cfg(test)]
82mod test {
83 #![allow(clippy::bool_assert_comparison)]
85 #![allow(clippy::clone_on_copy)]
86 #![allow(clippy::dbg_macro)]
87 #![allow(clippy::mixed_attributes_style)]
88 #![allow(clippy::print_stderr)]
89 #![allow(clippy::print_stdout)]
90 #![allow(clippy::single_char_pattern)]
91 #![allow(clippy::unwrap_used)]
92 #![allow(clippy::unchecked_time_subtraction)]
93 #![allow(clippy::useless_vec)]
94 #![allow(clippy::needless_pass_by_value)]
95 use super::*;
97 use tor_basic_utils::test_rng::testing_rng;
98
99 #[test]
100 fn config() {
101 let cfg = DownloadSchedule::default();
103 let one_sec = Duration::from_secs(1);
104 let mut rng = testing_rng();
105
106 assert_eq!(cfg.n_attempts(), 3);
107 let v: Vec<_> = cfg.attempts().collect();
108 assert_eq!(&v[..], &[0, 1, 2]);
109
110 assert_eq!(cfg.initial_delay, one_sec);
111 let mut sched = cfg.schedule();
112 assert_eq!(sched.next_delay(&mut rng), one_sec);
113
114 DownloadSchedule::builder()
116 .attempts(0)
117 .build()
118 .expect_err("built with 0 retries");
119 DownloadSchedule::builder()
120 .parallelism(0)
121 .build()
122 .expect_err("built with 0 parallelism");
123 }
124}