1use std::fs::File;
5use std::sync::Arc;
6use std::time::Instant;
7
8use crate::connection::{ConnectionId, Outlet};
9use crate::http::ByteRange;
10
11pub(crate) const MAX_CONNECTIONS: usize = 16;
14pub(crate) const MIN_PART_SIZE: u64 = 64 * 1024;
16
17pub(crate) fn connection_count(asked: i64) -> usize {
20 usize::try_from(asked).map_or(1, |asked| asked.clamp(1, MAX_CONNECTIONS))
21}
22
23#[derive(Debug)]
25pub(crate) struct Part {
26 pub start: u64,
27 pub end: Option<u64>,
29 pub saved: u64,
31 pub connection: Option<Live>,
33 pub failures: u32,
35 pub retry_at: Option<Instant>,
37}
38
39#[derive(Debug)]
41pub(crate) struct Live {
42 pub id: ConnectionId,
43 pub outlet: Arc<Outlet>,
44 pub writes_from: Option<u64>,
46}
47
48impl Part {
49 pub(crate) fn whole() -> Self {
51 Self {
52 start: 0,
53 end: None,
54 saved: 0,
55 connection: None,
56 failures: 0,
57 retry_at: None,
58 }
59 }
60
61 pub(crate) fn position(&self) -> u64 {
63 self.start + self.saved
64 }
65
66 pub(crate) fn rest(&self) -> ByteRange {
68 ByteRange {
69 from: self.position(),
70 to: self.end,
71 }
72 }
73
74 pub(crate) fn room(&self) -> Option<u64> {
76 self.end.map(|end| end - self.position())
77 }
78
79 pub(crate) fn is_complete(&self, total: Option<u64>) -> bool {
81 match self.end {
82 Some(end) => self.position() == end,
83 None => total == Some(self.position()),
84 }
85 }
86
87 pub(crate) fn hang_up(&mut self) -> Option<File> {
90 let live = self.connection.take()?;
91 let closed = live.outlet.close();
92 if let Some(from) = live.writes_from {
93 self.saved = from + closed.written - self.start;
94 }
95 closed.file
96 }
97}
98
99pub(crate) fn split(total: u64, connections: usize) -> Vec<Part> {
102 let most = u64::try_from(connections).unwrap_or(u64::MAX);
103 let count = (total / MIN_PART_SIZE).clamp(1, most);
104 let boundary = |index: u64| {
105 let at = u128::from(total) * u128::from(index) / u128::from(count);
106 u64::try_from(at).expect("a boundary is at most the total")
107 };
108 (0..count)
109 .map(|index| Part {
110 start: boundary(index),
111 end: Some(boundary(index + 1)),
112 saved: 0,
113 connection: None,
114 failures: 0,
115 retry_at: None,
116 })
117 .collect()
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 fn bounds(parts: &[Part]) -> Vec<(u64, Option<u64>)> {
125 parts.iter().map(|part| (part.start, part.end)).collect()
126 }
127
128 #[test]
129 fn splits_into_nearly_equal_parts_that_meet() {
130 let total = 4 * MIN_PART_SIZE + 3;
131 let parts = split(total, 4);
132 assert_eq!(
133 bounds(&parts),
134 [
135 (0, Some(MIN_PART_SIZE)),
136 (MIN_PART_SIZE, Some(2 * MIN_PART_SIZE + 1)),
137 (2 * MIN_PART_SIZE + 1, Some(3 * MIN_PART_SIZE + 2)),
138 (3 * MIN_PART_SIZE + 2, Some(total)),
139 ]
140 );
141 }
142
143 #[test]
144 fn keeps_parts_large_enough_to_be_worth_a_connection() {
145 assert_eq!(bounds(&split(100, 8)), [(0, Some(100))]);
146 assert_eq!(split(3 * MIN_PART_SIZE, 8).len(), 3);
147 assert_eq!(bounds(&split(0, 4)), [(0, Some(0))]);
148 }
149
150 #[test]
151 fn opens_between_one_and_the_most_connections() {
152 assert_eq!(connection_count(-3), 1);
153 assert_eq!(connection_count(0), 1);
154 assert_eq!(connection_count(4), 4);
155 assert_eq!(connection_count(1_000), MAX_CONNECTIONS);
156 }
157
158 #[test]
159 fn a_part_is_complete_at_its_end_or_the_files() {
160 let mut part = split(10, 1).remove(0);
161 part.saved = 10;
162 assert!(part.is_complete(None));
163 let mut whole = Part::whole();
164 whole.saved = 10;
165 assert!(!whole.is_complete(None), "a file of unknown size");
166 assert!(whole.is_complete(Some(10)));
167 assert_eq!(whole.rest(), ByteRange { from: 10, to: None });
168 }
169}