1use chrono::{Datelike, Local, Timelike};
7use parking_lot::RwLock;
8
9pub use crate::protocol::{BandwidthLimits, ScheduleRule};
11
12pub struct BandwidthScheduler {
14 rules: Vec<ScheduleRule>,
16 default_limits: BandwidthLimits,
18 current_limits: RwLock<BandwidthLimits>,
20}
21
22impl BandwidthScheduler {
23 pub fn new(rules: Vec<ScheduleRule>, default_limits: BandwidthLimits) -> Self {
25 let current = Self::evaluate_rules(&rules, &default_limits);
26 Self {
27 rules,
28 default_limits,
29 current_limits: RwLock::new(current),
30 }
31 }
32
33 pub fn with_defaults(download: Option<u64>, upload: Option<u64>) -> Self {
35 Self::new(Vec::new(), BandwidthLimits { download, upload })
36 }
37
38 pub fn get_limits(&self) -> BandwidthLimits {
40 *self.current_limits.read()
41 }
42
43 pub fn update(&self) -> bool {
48 let new_limits = Self::evaluate_rules(&self.rules, &self.default_limits);
49 let mut current = self.current_limits.write();
50 if *current != new_limits {
51 tracing::info!(
52 "Bandwidth limits changed: download={:?} upload={:?}",
53 new_limits.download,
54 new_limits.upload
55 );
56 *current = new_limits;
57 true
58 } else {
59 false
60 }
61 }
62
63 fn evaluate_rules(rules: &[ScheduleRule], default: &BandwidthLimits) -> BandwidthLimits {
65 let now = Local::now();
66 let hour = now.hour() as u8;
67 let weekday = now.weekday();
68
69 for rule in rules {
70 if rule.matches(hour, weekday) {
71 return BandwidthLimits {
72 download: rule.download_limit,
73 upload: rule.upload_limit,
74 };
75 }
76 }
77
78 *default
79 }
80
81 pub fn rules(&self) -> &[ScheduleRule] {
83 &self.rules
84 }
85
86 pub fn add_rule(&mut self, rule: ScheduleRule) {
88 self.rules.push(rule);
89 self.update();
90 }
91
92 pub fn clear_rules(&mut self) {
94 self.rules.clear();
95 self.update();
96 }
97
98 pub fn set_rules(&mut self, rules: Vec<ScheduleRule>) {
100 self.rules = rules;
101 self.update();
102 }
103
104 pub fn set_defaults(&mut self, limits: BandwidthLimits) {
106 self.default_limits = limits;
107 self.update();
108 }
109}
110
111impl Default for BandwidthScheduler {
112 fn default() -> Self {
113 Self::with_defaults(None, None)
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120 use chrono::Weekday;
121
122 #[test]
123 fn test_schedule_rule_match_simple() {
124 let rule = ScheduleRule::all_days(9, 17, Some(1_000_000), None);
125
126 assert!(rule.matches(9, Weekday::Mon));
128 assert!(rule.matches(12, Weekday::Wed));
129 assert!(rule.matches(17, Weekday::Fri));
130
131 assert!(!rule.matches(8, Weekday::Mon));
133 assert!(!rule.matches(18, Weekday::Wed));
134 assert!(!rule.matches(0, Weekday::Fri));
135 }
136
137 #[test]
138 fn test_schedule_rule_match_overnight() {
139 let rule = ScheduleRule::all_days(22, 6, Some(10_000_000), None);
141
142 assert!(rule.matches(22, Weekday::Mon));
144 assert!(rule.matches(23, Weekday::Mon));
145 assert!(rule.matches(0, Weekday::Tue));
146 assert!(rule.matches(3, Weekday::Tue));
147 assert!(rule.matches(6, Weekday::Tue));
148
149 assert!(!rule.matches(7, Weekday::Mon));
151 assert!(!rule.matches(12, Weekday::Mon));
152 assert!(!rule.matches(21, Weekday::Mon));
153 }
154
155 #[test]
156 fn test_schedule_rule_weekdays() {
157 let rule = ScheduleRule::weekdays(9, 17, Some(500_000), None);
158
159 assert!(rule.matches(12, Weekday::Mon));
161 assert!(rule.matches(12, Weekday::Tue));
162 assert!(rule.matches(12, Weekday::Wed));
163 assert!(rule.matches(12, Weekday::Thu));
164 assert!(rule.matches(12, Weekday::Fri));
165
166 assert!(!rule.matches(12, Weekday::Sat));
168 assert!(!rule.matches(12, Weekday::Sun));
169 }
170
171 #[test]
172 fn test_schedule_rule_weekends() {
173 let rule = ScheduleRule::weekends(0, 23, None, None);
174
175 assert!(rule.matches(12, Weekday::Sat));
177 assert!(rule.matches(12, Weekday::Sun));
178
179 assert!(!rule.matches(12, Weekday::Mon));
181 assert!(!rule.matches(12, Weekday::Fri));
182 }
183
184 #[test]
185 fn test_scheduler_no_rules() {
186 let scheduler = BandwidthScheduler::with_defaults(Some(1_000_000), Some(500_000));
187 let limits = scheduler.get_limits();
188
189 assert_eq!(limits.download, Some(1_000_000));
190 assert_eq!(limits.upload, Some(500_000));
191 }
192
193 #[test]
194 fn test_scheduler_first_match_wins() {
195 let rules = vec![
199 ScheduleRule::all_days(0, 23, Some(1_000_000), None),
200 ScheduleRule::all_days(0, 23, Some(2_000_000), None),
201 ];
202 let scheduler = BandwidthScheduler::new(rules, BandwidthLimits::default());
203 let limits = scheduler.get_limits();
204
205 assert_eq!(limits.download, Some(1_000_000));
206 }
207
208 #[test]
209 fn test_bandwidth_limits_default() {
210 let limits = BandwidthLimits::default();
211 assert_eq!(limits.download, None);
212 assert_eq!(limits.upload, None);
213 }
214}