1use dashmap::DashMap;
2use std::collections::HashMap;
3use std::sync::Arc;
4use std::sync::atomic::{AtomicU64, Ordering};
5use tokio::sync::RwLock;
6use tracing::{debug, info};
7
8use super::request_group::{DownloadOptions, DownloadStatus, GroupId, RequestGroup};
9use crate::error::Result;
10
11pub struct RequestGroupMan {
25 groups: DashMap<GroupId, Arc<RwLock<RequestGroup>>>,
26 next_gid: AtomicU64,
27 global_download_limit: Arc<RwLock<Option<u64>>>,
28 global_upload_limit: Arc<RwLock<Option<u64>>>,
29}
30
31impl RequestGroupMan {
32 pub fn new() -> Self {
33 info!("Initializing request group manager");
34
35 RequestGroupMan {
36 groups: DashMap::new(),
37 next_gid: AtomicU64::new(1),
38 global_download_limit: Arc::new(RwLock::new(None)),
39 global_upload_limit: Arc::new(RwLock::new(None)),
40 }
41 }
42
43 pub async fn add_group(&self, uris: Vec<String>, options: DownloadOptions) -> Result<GroupId> {
44 let gid = self.generate_gid();
45 let group = RequestGroup::new(gid, uris, options);
46
47 self.groups.insert(gid, Arc::new(RwLock::new(group)));
48
49 info!("Adding download task #{}", gid.value());
50 debug!("Current total tasks: {}", self.groups.len());
51
52 Ok(gid)
53 }
54
55 pub async fn add_group_with_gid(
58 &self,
59 gid: GroupId,
60 uris: Vec<String>,
61 options: DownloadOptions,
62 ) -> Result<()> {
63 if self.groups.contains_key(&gid) {
64 return Err(crate::error::Aria2Error::DownloadFailed(format!(
65 "GID {} already exists",
66 gid.to_hex_string()
67 )));
68 }
69 let group = RequestGroup::new(gid, uris, options);
70 self.groups.insert(gid, Arc::new(RwLock::new(group)));
71 info!("Adding download task (RPC) #{}", gid.to_hex_string());
72 Ok(())
73 }
74
75 pub fn group_by_hex(&self, hex: &str) -> Option<Arc<RwLock<RequestGroup>>> {
78 let gid = GroupId::from_hex_string(hex)?;
79 self.groups.get(&gid).map(|v| v.clone())
80 }
81
82 pub fn group_by_id(&self, gid: GroupId) -> Option<Arc<RwLock<RequestGroup>>> {
84 self.groups.get(&gid).map(|v| v.clone())
85 }
86
87 pub fn all_groups(&self) -> Vec<(GroupId, Arc<RwLock<RequestGroup>>)> {
90 self.groups
91 .iter()
92 .map(|entry| (*entry.key(), entry.value().clone()))
93 .collect()
94 }
95
96 pub fn remove_group_by_id(&self, gid: GroupId) -> Option<Arc<RwLock<RequestGroup>>> {
98 self.groups.remove(&gid).map(|(_, v)| v)
99 }
100
101 pub async fn remove_group(&self, gid: GroupId) -> Result<()> {
102 if let Some((_, group_lock)) = self.groups.remove(&gid) {
103 let mut group = group_lock.write().await;
104 group.remove().await?;
105 info!("Removing download task #{}", gid.value());
106 debug!("Remaining tasks: {}", self.groups.len());
107 }
108
109 Ok(())
110 }
111
112 pub async fn pause_group(&self, gid: GroupId) -> Result<()> {
113 if let Some(group_lock) = self.groups.get(&gid) {
114 let mut group = group_lock.write().await;
115 group.pause().await?;
116 info!("Pausing download task #{}", gid.value());
117 }
118
119 Ok(())
120 }
121
122 pub async fn unpause_group(&self, gid: GroupId) -> Result<()> {
123 if let Some(group_lock) = self.groups.get(&gid) {
124 let mut group = group_lock.write().await;
125 if group.status().await.is_paused() {
126 group.start().await?;
127 info!("Resuming download task #{}", gid.value());
128 }
129 }
130
131 Ok(())
132 }
133
134 pub async fn update_group_options(
148 &self,
149 gid_hex: &str,
150 changes: HashMap<String, serde_json::Value>,
151 ) -> std::result::Result<(), String> {
152 let group = self
153 .group_by_hex(gid_hex)
154 .ok_or_else(|| format!("GID {} not found", gid_hex))?;
155
156 let mut g = group.write().await;
157 for (key, value) in changes {
158 let applied = g.update_option(&key, value).await;
159 if !applied {
160 return Err(format!("Option '{}' cannot be changed at runtime", key));
162 }
163 }
164 Ok(())
165 }
166
167 pub async fn get_group(&self, gid: GroupId) -> Option<Arc<RwLock<RequestGroup>>> {
168 self.groups.get(&gid).map(|v| v.clone())
169 }
170
171 pub async fn list_groups(&self) -> Vec<Arc<RwLock<RequestGroup>>> {
172 self.groups.iter().map(|v| v.clone()).collect()
173 }
174
175 pub async fn get_active_groups(&self) -> Vec<Arc<RwLock<RequestGroup>>> {
176 let mut active = Vec::new();
177
178 for entry in self.groups.iter() {
179 let group = entry.read().await;
180 if group.status().await.is_active() {
181 active.push(entry.clone());
182 }
183 }
184
185 active
186 }
187
188 pub async fn get_waiting_groups(&self) -> Vec<Arc<RwLock<RequestGroup>>> {
189 let mut waiting = Vec::new();
190
191 for entry in self.groups.iter() {
192 let group = entry.read().await;
193 if matches!(group.status().await, DownloadStatus::Waiting) {
194 waiting.push(entry.clone());
195 }
196 }
197
198 waiting
199 }
200
201 pub async fn count(&self) -> usize {
202 self.groups.len()
203 }
204
205 pub async fn active_count(&self) -> usize {
206 self.get_active_groups().await.len()
207 }
208
209 pub async fn set_global_speed_limit(
210 &self,
211 download_limit: Option<u64>,
212 upload_limit: Option<u64>,
213 ) {
214 *self.global_download_limit.write().await = download_limit;
215 *self.global_upload_limit.write().await = upload_limit;
216
217 debug!(
218 "Setting global speed limit - download: {:?}, upload: {:?}",
219 download_limit, upload_limit
220 );
221 }
222
223 pub async fn global_download_limit(&self) -> Option<u64> {
224 *self.global_download_limit.read().await
225 }
226
227 pub async fn global_upload_limit(&self) -> Option<u64> {
228 *self.global_upload_limit.read().await
229 }
230
231 fn generate_gid(&self) -> GroupId {
232 let gid = self.next_gid.fetch_add(1, Ordering::SeqCst);
233 GroupId(gid)
234 }
235
236 pub async fn clear_completed(&self) -> Result<usize> {
237 let to_remove: Vec<GroupId> = self
238 .groups
239 .iter()
240 .filter_map(|entry| {
241 let group_lock = entry.value();
242 futures::executor::block_on(async {
244 let group = group_lock.read().await;
245 if matches!(
246 group.status().await,
247 DownloadStatus::Complete | DownloadStatus::Error(_)
248 ) {
249 Some(*entry.key())
250 } else {
251 None
252 }
253 })
254 })
255 .collect();
256
257 let count = to_remove.len();
258 for gid in &to_remove {
259 self.groups.remove(gid);
260 }
261
262 info!("Cleared {} completed tasks", count);
263 Ok(count)
264 }
265}
266
267impl Default for RequestGroupMan {
268 fn default() -> Self {
269 Self::new()
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276 use std::time::Instant;
277
278 #[tokio::test]
280 async fn test_concurrent_add_groups() {
281 let man = Arc::new(RequestGroupMan::new());
282 let num_tasks = 100;
283 let mut handles = vec![];
284
285 for i in 0..num_tasks {
287 let man_clone = man.clone();
288 let handle = tokio::spawn(async move {
289 let uri = format!("http://example.com/file{}.bin", i);
290 let options = DownloadOptions::default();
291 man_clone.add_group(vec![uri], options).await
292 });
293 handles.push(handle);
294 }
295
296 let results: Vec<_> = futures::future::join_all(handles).await;
298
299 for result in results {
301 assert!(result.is_ok());
302 let gid = result.unwrap().unwrap();
303 assert!(gid.value() > 0);
304 }
305
306 assert_eq!(man.count().await, num_tasks);
308 }
309
310 #[tokio::test]
312 async fn test_concurrent_get_groups() {
313 let man = Arc::new(RequestGroupMan::new());
314 let num_groups = 50;
315
316 for i in 0..num_groups {
318 let uri = format!("http://example.com/file{}.bin", i);
319 let options = DownloadOptions::default();
320 man.add_group(vec![uri], options).await.unwrap();
321 }
322
323 let mut handles = vec![];
324
325 for i in 1..=num_groups {
327 let man_clone = man.clone();
328 let handle = tokio::spawn(async move {
329 let gid = GroupId(i);
330 man_clone.get_group(gid).await
331 });
332 handles.push(handle);
333 }
334
335 let results: Vec<_> = futures::future::join_all(handles).await;
337
338 for result in results {
340 assert!(result.is_ok());
341 let group = result.unwrap();
342 assert!(group.is_some());
343 }
344 }
345
346 #[tokio::test]
348 async fn test_concurrent_add_remove() {
349 let man = Arc::new(RequestGroupMan::new());
350 let num_tasks = 50;
351 let mut add_handles = vec![];
352
353 for i in 0..num_tasks {
355 let man_clone = man.clone();
356 let handle = tokio::spawn(async move {
357 let uri = format!("http://example.com/file{}.bin", i);
358 let options = DownloadOptions::default();
359 man_clone.add_group(vec![uri], options).await
360 });
361 add_handles.push(handle);
362 }
363
364 let add_results: Vec<_> = futures::future::join_all(add_handles).await;
365 let gids: Vec<_> = add_results
366 .into_iter()
367 .map(|r| r.unwrap().unwrap())
368 .collect();
369
370 let mut remove_handles = vec![];
371
372 for gid in gids {
374 let man_clone = man.clone();
375 let handle = tokio::spawn(async move { man_clone.remove_group(gid).await });
376 remove_handles.push(handle);
377 }
378
379 let remove_results: Vec<_> = futures::future::join_all(remove_handles).await;
380
381 for result in remove_results {
383 assert!(result.is_ok());
384 }
385
386 assert_eq!(man.count().await, 0);
388 }
389
390 #[tokio::test]
393 async fn test_lock_contention_reduction() {
394 let man = Arc::new(RequestGroupMan::new());
395 let num_operations = 100;
396
397 for i in 0..num_operations {
399 let uri = format!("http://example.com/file{}.bin", i);
400 let options = DownloadOptions::default();
401 man.add_group(vec![uri], options).await.unwrap();
402 }
403
404 let start = Instant::now();
405 let mut handles = vec![];
406
407 for i in 1..=num_operations {
409 let man_clone = man.clone();
410 let handle = tokio::spawn(async move {
411 for _ in 0..10 {
412 let gid = GroupId(i);
413 let _ = man_clone.get_group(gid).await;
414 }
415 });
416 handles.push(handle);
417 }
418
419 futures::future::join_all(handles).await;
420 let duration = start.elapsed();
421
422 println!("Concurrent read operations took: {:?}", duration);
425 assert!(
426 duration.as_millis() < 1000,
427 "Concurrent operations should be fast"
428 );
429 }
430
431 #[tokio::test]
433 async fn test_concurrent_list_groups() {
434 let man = Arc::new(RequestGroupMan::new());
435 let num_groups = 30;
436
437 for i in 0..num_groups {
439 let uri = format!("http://example.com/file{}.bin", i);
440 let options = DownloadOptions::default();
441 man.add_group(vec![uri], options).await.unwrap();
442 }
443
444 let man_clone = man.clone();
445 let list_handle = tokio::spawn(async move {
446 let mut counts = vec![];
447 for _ in 0..10 {
448 let groups = man_clone.list_groups().await;
449 counts.push(groups.len());
450 }
451 counts
452 });
453
454 for i in num_groups..num_groups + 10 {
456 let uri = format!("http://example.com/file{}.bin", i);
457 let options = DownloadOptions::default();
458 man.add_group(vec![uri], options).await.unwrap();
459 }
460
461 let counts = list_handle.await.unwrap();
462
463 for count in counts {
465 assert!(count >= num_groups);
466 }
467 }
468
469 #[tokio::test]
471 async fn test_atomic_gid_generation() {
472 let man = Arc::new(RequestGroupMan::new());
473 let num_tasks = 1000;
474 let mut handles = vec![];
475
476 for _ in 0..num_tasks {
478 let man_clone = man.clone();
479 let handle = tokio::spawn(async move {
480 let uri = "http://example.com/file.bin".to_string();
481 let options = DownloadOptions::default();
482 man_clone.add_group(vec![uri], options).await
483 });
484 handles.push(handle);
485 }
486
487 let results: Vec<_> = futures::future::join_all(handles).await;
488 let gids: Vec<_> = results.into_iter().map(|r| r.unwrap().unwrap()).collect();
489
490 let mut gid_values: Vec<_> = gids.iter().map(|g| g.value()).collect();
492 gid_values.sort();
493 gid_values.dedup();
494
495 assert_eq!(gid_values.len(), num_tasks, "All GIDs should be unique");
496 }
497
498 #[tokio::test]
500 async fn test_dashmap_iteration_non_blocking() {
501 let man = Arc::new(RequestGroupMan::new());
502 let num_groups = 50;
503
504 for i in 0..num_groups {
506 let uri = format!("http://example.com/file{}.bin", i);
507 let options = DownloadOptions::default();
508 man.add_group(vec![uri], options).await.unwrap();
509 }
510
511 let man_clone = man.clone();
512 let iter_handle = tokio::spawn(async move {
513 let mut sum = 0;
514 for _ in 0..10 {
515 let groups = man_clone.list_groups().await;
516 sum += groups.len();
517 }
518 sum
519 });
520
521 for i in num_groups..num_groups + 20 {
523 let uri = format!("http://example.com/file{}.bin", i);
524 let options = DownloadOptions::default();
525 man.add_group(vec![uri], options).await.unwrap();
526 }
527
528 let sum = iter_handle.await.unwrap();
529
530 assert!(sum > 0);
532 }
533}