gooty_proxy/orchestration/manager.rs
1//! # Manager Module
2//!
3//! Provides the `Manager` struct and related functionality for orchestrating tasks and resources.
4//!
5//! ## Overview
6//!
7//! This module includes:
8//! - Initialization and configuration of the manager
9//! - Task scheduling and execution
10//! - Resource monitoring and reporting
11//!
12//! ## Examples
13//!
14//! ```
15//! use gooty_proxy::orchestration::manager::Manager;
16//!
17//! // Create a new manager
18//! let manager = Manager::new();
19//! assert!(manager.is_ok());
20//! ```
21
22use crate::{
23 definitions::{
24 enums::{AnonymityLevel, ProxyType},
25 errors::{JudgementError, ManagerError, ManagerResult, SleuthError},
26 proxy::Proxy,
27 source::Source,
28 },
29 inspection::{ipinfo::Sleuth, judgement::Judge},
30 io::http::Requestor,
31 orchestration::processes,
32};
33use ahash::AHashMap;
34use chrono::{DateTime, Utc};
35use log::{debug, info, warn};
36use std::collections::HashMap;
37use std::sync::Arc;
38
39/// Statistics about proxies managed by `ProxyManager`
40#[derive(Debug, Clone)]
41pub struct ProxyStats {
42 /// Total number of proxies
43 pub total: usize,
44
45 /// Number of working proxies (successfully judged)
46 pub working: usize,
47
48 /// Number of proxies by anonymity level
49 pub by_anonymity: HashMap<AnonymityLevel, usize>,
50
51 /// Number of proxies by type
52 pub by_type: HashMap<ProxyType, usize>,
53
54 /// Number of proxies by country
55 pub by_country: HashMap<String, usize>,
56
57 /// Average latency of working proxies
58 pub avg_latency: Option<u128>,
59}
60
61/// Statistics about sources managed by `ProxyManager`
62#[derive(Debug, Clone)]
63pub struct SourceStats {
64 /// Total number of sources
65 pub total: usize,
66
67 /// Number of active sources
68 pub active: usize,
69
70 /// Total proxies found from all sources
71 pub total_proxies_found: usize,
72
73 /// Proxies found per source
74 pub proxies_by_source: HashMap<String, usize>,
75}
76
77/// Manager for proxy and source collections with testing and enrichment capabilities.
78///
79/// `ProxyManager` is the central component for managing proxies and sources. It provides:
80/// - Storage and retrieval of proxies and sources
81/// - Testing proxies for anonymity and performance
82/// - Enriching proxies with metadata (country, organization, ASN)
83/// - Fetching new proxies from sources
84/// - Filtering and selection of proxies based on various criteria
85///
86/// # Examples
87///
88/// ```
89/// use gooty_proxy::orchestration::manager::ProxyManager;
90///
91/// #[tokio::main]
92/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
93/// // Create a new manager
94/// let mut manager = ProxyManager::new()?;
95///
96/// // Initialize services
97/// manager.init_judge().await?;
98/// manager.init_sleuth()?;
99///
100/// // Get stats about managed proxies
101/// let stats = manager.get_proxy_stats();
102/// println!("Managing {} proxies, {} working", stats.total, stats.working);
103///
104/// Ok(())
105/// }
106/// ```
107pub struct ProxyManager {
108 /// Collection of proxies keyed by their string representation
109 proxies: AHashMap<String, Proxy>,
110
111 /// Collection of sources keyed by their identifier
112 sources: AHashMap<String, Source>,
113
114 /// HTTP request client for making requests
115 requestor: Requestor,
116
117 /// Judge for checking proxy anonymity
118 judge: Option<Arc<Judge>>,
119
120 /// IP lookup tool
121 sleuth: Option<Arc<Sleuth>>,
122
123 /// Last time the manager state was updated
124 last_update_time: Option<DateTime<Utc>>,
125}
126
127impl ProxyManager {
128 /// Create a new proxy manager with default configuration.
129 ///
130 /// This initializes the manager with empty collections and a default requestor.
131 /// The judge and sleuth services must be initialized separately.
132 ///
133 /// # Returns
134 ///
135 /// A new `ProxyManager` instance.
136 ///
137 /// # Errors
138 ///
139 /// Returns an error if the requestor cannot be initialized.
140 pub fn new() -> ManagerResult<Self> {
141 let requestor = Requestor::new().map_err(ManagerError::RequestorError)?;
142
143 Ok(ProxyManager {
144 proxies: AHashMap::new(),
145 sources: AHashMap::new(),
146 requestor,
147 judge: None,
148 sleuth: None,
149 last_update_time: None,
150 })
151 }
152
153 /// Initialize the judge for proxy testing.
154 ///
155 /// The judge service is used to test proxies and determine their anonymity level.
156 /// This must be called before using methods that test proxies.
157 ///
158 /// # Returns
159 ///
160 /// Ok(()) if the judge was successfully initialized.
161 ///
162 /// # Errors
163 ///
164 /// Returns an error if the judge service cannot be initialized.
165 pub fn init_judge(&mut self) -> ManagerResult<()> {
166 let judge = Judge::new().map_err(ManagerError::JudgementError)?;
167 self.judge = Some(Arc::new(judge));
168 Ok(())
169 }
170
171 /// Initialize the sleuth for IP lookups.
172 ///
173 /// The sleuth service is used to lookup IP metadata such as country,
174 /// organization, and ASN. This must be called before using methods
175 /// that enrich proxy information.
176 ///
177 /// # Returns
178 ///
179 /// Ok(()) if the sleuth was successfully initialized.
180 ///
181 /// # Errors
182 ///
183 /// Returns an error if the sleuth service cannot be initialized.
184 pub fn init_sleuth(&mut self) -> ManagerResult<()> {
185 let sleuth = Sleuth::new();
186 self.sleuth = Some(Arc::new(sleuth));
187 Ok(())
188 }
189
190 /// Add a proxy to the manager.
191 ///
192 /// # Arguments
193 ///
194 /// * `proxy` - The proxy to add
195 ///
196 /// # Returns
197 ///
198 /// Returns true if the proxy was added, false if it already existed.
199 ///
200 /// # Errors
201 ///
202 /// Returns an error if the proxy is invalid.
203 pub fn add_proxy(&mut self, proxy: Proxy) -> ManagerResult<bool> {
204 // Validate the proxy
205 proxy.validate().map_err(ManagerError::ProxyError)?;
206
207 // Use the connection string as a unique key
208 let key = proxy.to_connection_string();
209
210 // Check if this proxy already exists
211 if self.proxies.contains_key(&key) {
212 return Ok(false);
213 }
214
215 // Add the proxy
216 self.proxies.insert(key, proxy);
217 self.last_update_time = Some(Utc::now());
218 Ok(true)
219 }
220
221 /// Add multiple proxies to the manager.
222 ///
223 /// # Arguments
224 ///
225 /// * `proxies` - Vector of proxies to add
226 ///
227 /// # Returns
228 ///
229 /// Returns the number of new proxies that were added.
230 ///
231 /// # Errors
232 ///
233 /// Returns an error if any proxy is invalid.
234 pub fn add_proxies(&mut self, proxies: Vec<Proxy>) -> ManagerResult<usize> {
235 let mut added_count = 0;
236
237 for proxy in proxies {
238 if self.add_proxy(proxy)? {
239 added_count += 1;
240 }
241 }
242
243 if added_count > 0 {
244 self.last_update_time = Some(Utc::now());
245 }
246
247 Ok(added_count)
248 }
249
250 /// Get a proxy by its connection string.
251 ///
252 /// # Arguments
253 ///
254 /// * `id` - Connection string identifier of the proxy
255 ///
256 /// # Returns
257 ///
258 /// An Option containing a reference to the proxy if found, or None if not found.
259 #[must_use]
260 pub fn get_proxy(&self, id: &str) -> Option<&Proxy> {
261 self.proxies.get(id)
262 }
263
264 /// Get a mutable reference to a proxy by its connection string.
265 ///
266 /// # Arguments
267 ///
268 /// * `id` - Connection string identifier of the proxy
269 ///
270 /// # Returns
271 ///
272 /// An Option containing a mutable reference to the proxy if found, or None if not found.
273 pub fn get_proxy_mut(&mut self, id: &str) -> Option<&mut Proxy> {
274 self.proxies.get_mut(id)
275 }
276
277 /// Remove a proxy by its connection string.
278 ///
279 /// # Arguments
280 ///
281 /// * `id` - Connection string identifier of the proxy to remove
282 ///
283 /// # Returns
284 ///
285 /// An Option containing the removed proxy if found, or None if not found.
286 pub fn remove_proxy(&mut self, id: &str) -> Option<Proxy> {
287 let result = self.proxies.remove(id);
288 if result.is_some() {
289 self.last_update_time = Some(Utc::now());
290 }
291 result
292 }
293
294 /// Get the total number of proxies managed.
295 ///
296 /// # Returns
297 ///
298 /// The number of proxies in the manager.
299 #[must_use]
300 pub fn proxy_count(&self) -> usize {
301 self.proxies.len()
302 }
303
304 /// Get all proxies as a vector of references.
305 ///
306 /// # Returns
307 ///
308 /// A vector containing references to all proxies.
309 #[must_use]
310 pub fn get_all_proxies(&self) -> Vec<&Proxy> {
311 self.proxies.values().collect()
312 }
313
314 /// Get all proxies as owned values.
315 ///
316 /// # Returns
317 ///
318 /// A vector containing clones of all proxies.
319 #[must_use]
320 pub fn get_all_proxies_owned(&self) -> Vec<Proxy> {
321 self.proxies.values().cloned().collect()
322 }
323
324 /// Get all proxies that match certain criteria.
325 ///
326 /// # Arguments
327 ///
328 /// * `filter_fn` - A function that returns true for proxies that should be included
329 ///
330 /// # Returns
331 ///
332 /// A vector of references to proxies that match the filter criteria.
333 ///
334 /// # Examples
335 ///
336 /// ```
337 /// // Get all elite proxies
338 /// let elite_proxies = manager.filter_proxies(|p| p.anonymity == AnonymityLevel::Elite);
339 ///
340 /// // Get all proxies with latency under 500ms
341 /// let fast_proxies = manager.filter_proxies(|p| p.latency_ms.unwrap_or(u32::MAX) < 500);
342 /// ```
343 pub fn filter_proxies<F>(&self, filter_fn: F) -> Vec<&Proxy>
344 where
345 F: Fn(&Proxy) -> bool,
346 {
347 self.proxies.values().filter(|p| filter_fn(p)).collect()
348 }
349
350 /// Add a source to the manager.
351 ///
352 /// # Arguments
353 ///
354 /// * `source` - The source to add
355 ///
356 /// # Returns
357 ///
358 /// Returns true if the source was added, false if it already existed.
359 ///
360 /// # Errors
361 ///
362 /// Returns an error if the source is invalid.
363 pub fn add_source(&mut self, source: Source) -> ManagerResult<bool> {
364 // Use the source URL as a unique key
365 let key = source.url.clone();
366
367 // Check if this source already exists
368 if self.sources.contains_key(&key) {
369 return Ok(false);
370 }
371
372 // Add the source
373 self.sources.insert(key, source);
374 self.last_update_time = Some(Utc::now());
375 Ok(true)
376 }
377
378 /// Add multiple sources to the manager.
379 ///
380 /// # Arguments
381 ///
382 /// * `sources` - Vector of sources to add
383 ///
384 /// # Returns
385 ///
386 /// Returns the number of new sources that were added.
387 ///
388 /// # Errors
389 ///
390 /// Returns an error if any source is invalid.
391 pub fn add_sources(&mut self, sources: Vec<Source>) -> ManagerResult<usize> {
392 let mut added_count = 0;
393
394 for source in sources {
395 if self.add_source(source)? {
396 added_count += 1;
397 }
398 }
399
400 if added_count > 0 {
401 self.last_update_time = Some(Utc::now());
402 }
403
404 Ok(added_count)
405 }
406
407 /// Get a source by its URL.
408 ///
409 /// # Arguments
410 ///
411 /// * `url` - URL identifier of the source
412 ///
413 /// # Returns
414 ///
415 /// An Option containing a reference to the source if found, or None if not found.
416 #[must_use]
417 pub fn get_source(&self, url: &str) -> Option<&Source> {
418 self.sources.get(url)
419 }
420
421 /// Get a mutable reference to a source by its URL.
422 ///
423 /// # Arguments
424 ///
425 /// * `url` - URL identifier of the source
426 ///
427 /// # Returns
428 ///
429 /// An Option containing a mutable reference to the source if found, or None if not found.
430 pub fn get_source_mut(&mut self, url: &str) -> Option<&mut Source> {
431 self.sources.get_mut(url)
432 }
433
434 /// Remove a source by its URL.
435 ///
436 /// # Arguments
437 ///
438 /// * `url` - URL identifier of the source to remove
439 ///
440 /// # Returns
441 ///
442 /// An Option containing the removed source if found, or None if not found.
443 pub fn remove_source(&mut self, url: &str) -> Option<Source> {
444 let result = self.sources.remove(url);
445 if result.is_some() {
446 self.last_update_time = Some(Utc::now());
447 }
448 result
449 }
450
451 /// Get the total number of sources.
452 ///
453 /// # Returns
454 ///
455 /// The number of sources in the manager.
456 #[must_use]
457 pub fn source_count(&self) -> usize {
458 self.sources.len()
459 }
460
461 /// Get all sources as a vector of references.
462 ///
463 /// # Returns
464 ///
465 /// A vector containing references to all sources.
466 #[must_use]
467 pub fn get_all_sources(&self) -> Vec<&Source> {
468 self.sources.values().collect()
469 }
470
471 /// Get all sources as owned values.
472 ///
473 /// # Returns
474 ///
475 /// A vector containing clones of all sources.
476 #[must_use]
477 pub fn get_all_sources_owned(&self) -> Vec<Source> {
478 self.sources.values().cloned().collect()
479 }
480
481 /// Get statistics about the managed proxies.
482 ///
483 /// This method calculates counts, distributions, and performance metrics
484 /// for the proxies currently in the manager.
485 ///
486 /// # Returns
487 ///
488 /// A `ProxyStats` struct containing the calculated statistics.
489 #[must_use]
490 pub fn get_proxy_stats(&self) -> ProxyStats {
491 let total = self.proxies.len();
492 let mut working = 0;
493 let mut by_anonymity = HashMap::new();
494 let mut by_type = HashMap::new();
495 let mut by_country = HashMap::new();
496 let mut latency_sum = 0;
497 let mut latency_count = 0;
498
499 for proxy in self.proxies.values() {
500 // Count proxies with successful checks as working
501 if proxy.check_count > 0 && proxy.check_failure_count < proxy.check_count {
502 working += 1;
503 }
504
505 // Count by anonymity
506 *by_anonymity.entry(proxy.anonymity).or_insert(0) += 1;
507
508 // Count by type
509 *by_type.entry(proxy.proxy_type).or_insert(0) += 1;
510
511 // Count by country
512 if let Some(country) = &proxy.country {
513 *by_country.entry(country.clone()).or_insert(0) += 1;
514 }
515
516 // Calculate average latency
517 if let Some(latency) = proxy.latency_ms {
518 latency_sum += latency;
519 latency_count += 1;
520 }
521 }
522
523 // Calculate average latency
524 let avg_latency = if latency_count > 0 {
525 Some(latency_sum / latency_count)
526 } else {
527 None
528 };
529
530 ProxyStats {
531 total,
532 working,
533 by_anonymity,
534 by_type,
535 by_country,
536 avg_latency,
537 }
538 }
539
540 /// Get statistics about the managed sources.
541 ///
542 /// This method calculates counts and performance metrics for the
543 /// sources currently in the manager.
544 ///
545 /// # Returns
546 ///
547 /// A `SourceStats` struct containing the calculated statistics.
548 #[must_use]
549 pub fn get_source_stats(&self) -> SourceStats {
550 let total = self.sources.len();
551 let mut active = 0;
552 let mut total_proxies_found: usize = 0;
553 let mut proxies_by_source: HashMap<String, usize> = HashMap::new();
554
555 for source in self.sources.values() {
556 if source.last_failure_reason.is_none() || source.failure_count < source.use_count / 2 {
557 active += 1;
558 }
559
560 let found = source.proxies_found;
561 total_proxies_found += found;
562 proxies_by_source.insert(source.url.clone(), found);
563 }
564
565 SourceStats {
566 total,
567 active,
568 total_proxies_found,
569 proxies_by_source,
570 }
571 }
572
573 /// Check a proxy by testing its connectivity and anonymity.
574 ///
575 /// # Arguments
576 ///
577 /// * `proxy_id` - The connection string identifier of the proxy to check
578 ///
579 /// # Returns
580 ///
581 /// Ok(()) if the check was performed (regardless of the proxy's status).
582 ///
583 /// # Errors
584 ///
585 /// Returns an error if:
586 /// * The proxy ID is invalid
587 /// * The judge service is not initialized
588 /// * There's a critical failure in the checking process
589 pub async fn check_proxy(&mut self, proxy_id: &str) -> ManagerResult<()> {
590 let judge = self.judge.clone().ok_or_else(|| {
591 ManagerError::JudgementError(JudgementError::Other("Judge not initialized".to_string()))
592 })?;
593
594 let proxy = self
595 .get_proxy_mut(proxy_id)
596 .ok_or_else(|| ManagerError::InvalidProxyId(proxy_id.to_string()))?;
597
598 // Create a clone of the proxy to pass to the judge
599 let mut proxy_clone = proxy.clone();
600
601 // Try to judge the proxy
602 match judge.judge_proxy(&mut proxy_clone).await {
603 Ok(anonymity) => {
604 // Record a successful check
605 proxy.record_check(proxy_clone.latency_ms.unwrap_or(0));
606
607 // Update proxy metadata
608 proxy.update_metadata(
609 proxy_clone.country,
610 proxy_clone.organization,
611 proxy_clone.hostname,
612 Some(anonymity),
613 );
614
615 self.last_update_time = Some(Utc::now());
616 }
617 Err(e) => {
618 // Record a failed check
619 proxy.record_check_failure();
620 self.last_update_time = Some(Utc::now());
621 warn!("Failed to judge proxy {proxy_id}: {e}");
622 }
623 }
624
625 Ok(())
626 }
627
628 /// Fetch proxies from a source.
629 ///
630 /// # Arguments
631 ///
632 /// * `source_url` - The URL identifier of the source to fetch from
633 ///
634 /// # Returns
635 ///
636 /// A vector of proxies fetched from the source.
637 ///
638 /// # Errors
639 ///
640 /// Returns an error if:
641 /// * The source URL is invalid
642 /// * The source fails to fetch proxies
643 pub async fn fetch_from_source(&mut self, source_url: &str) -> ManagerResult<Vec<Proxy>> {
644 let source = self
645 .get_source_mut(source_url)
646 .ok_or_else(|| ManagerError::InvalidSourceId(source_url.to_string()))?;
647
648 // Create a clone of the source to work with
649 let source_clone = source.clone();
650
651 // Use the requestor directly
652 let proxies = source_clone
653 .fetch_proxies(&self.requestor)
654 .await
655 .map_err(ManagerError::SourceError)?;
656
657 // Update source metadata in the original source
658 let source = self
659 .get_source_mut(source_url)
660 .ok_or_else(|| ManagerError::InvalidSourceId(source_url.to_string()))?;
661 source.last_used_at = Some(Utc::now());
662 source.record_use();
663 source.proxies_found += proxies.len();
664
665 // Add proxies to the manager
666 let added_count = self.add_proxies(proxies.clone())?;
667 info!("Added {added_count} new proxies from source {source_url}");
668
669 self.last_update_time = Some(Utc::now());
670 Ok(proxies)
671 }
672
673 /// Enrich a proxy with IP metadata.
674 ///
675 /// # Arguments
676 ///
677 /// * `proxy_id` - The connection string identifier of the proxy to enrich
678 ///
679 /// # Returns
680 ///
681 /// Ok(()) if the enrichment was performed.
682 ///
683 /// # Errors
684 ///
685 /// Returns an error if:
686 /// * The proxy ID is invalid
687 /// * The sleuth service is not initialized
688 /// * There's a failure in the enrichment process
689 pub async fn enrich_proxy(&mut self, proxy_id: &str) -> ManagerResult<()> {
690 let sleuth = self.sleuth.clone().ok_or_else(|| {
691 ManagerError::SleuthError(SleuthError::ApiError("Sleuth not initialized".into()))
692 })?;
693
694 let proxy = self
695 .get_proxy_mut(proxy_id)
696 .ok_or_else(|| ManagerError::InvalidProxyId(proxy_id.to_string()))?;
697
698 // Look up IP metadata
699 match sleuth.lookup_ip_metadata(&proxy.address).await {
700 Ok(metadata) => {
701 // Update proxy with IP metadata
702 proxy.update_with_ip_metadata(metadata);
703 self.last_update_time = Some(Utc::now());
704 debug!("Enriched proxy {proxy_id} with IP metadata");
705 }
706 Err(e) => {
707 warn!("Failed to enrich proxy {proxy_id} with IP metadata: {e}");
708 return Err(ManagerError::SleuthError(e));
709 }
710 }
711
712 Ok(())
713 }
714
715 /// Get the last update time of the manager state.
716 ///
717 /// # Returns
718 ///
719 /// An Option containing the `DateTime` of the last update, or None if never updated.
720 #[must_use]
721 pub fn get_last_update_time(&self) -> Option<DateTime<Utc>> {
722 self.last_update_time
723 }
724
725 /// Clear all proxies from the manager.
726 ///
727 /// This removes all proxies from the manager but keeps the sources.
728 pub fn clear_proxies(&mut self) {
729 if !self.proxies.is_empty() {
730 self.proxies.clear();
731 self.last_update_time = Some(Utc::now());
732 }
733 }
734
735 /// Clear all sources from the manager.
736 ///
737 /// This removes all sources from the manager but keeps the proxies.
738 pub fn clear_sources(&mut self) {
739 if !self.sources.is_empty() {
740 self.sources.clear();
741 self.last_update_time = Some(Utc::now());
742 }
743 }
744
745 /// Check all proxies in parallel.
746 ///
747 /// This method is useful for bulk verification of proxies, using
748 /// concurrent processing for efficiency.
749 ///
750 /// # Arguments
751 ///
752 /// * `proxies` - A mutable slice of proxies to verify
753 /// * `concurrency` - The maximum number of concurrent verification operations
754 ///
755 /// # Returns
756 ///
757 /// Ok(()) if the verification process completes.
758 ///
759 /// # Errors
760 ///
761 /// Returns an error if there's a critical failure in the verification process.
762 ///
763 /// # Panics
764 ///
765 /// Panics if `init_judge()` has been called successfully but the internal judge
766 /// is still `None`, which should never happen in practice.
767 pub async fn check_all_proxies(
768 &mut self,
769 proxies: &mut [Proxy],
770 concurrency: usize,
771 ) -> ManagerResult<()> {
772 // Ensure judge is initialized
773 if self.judge.is_none() {
774 self.init_judge()?;
775 }
776
777 let judge = self.judge.clone().unwrap();
778
779 if proxies.is_empty() {
780 return Ok(());
781 }
782
783 // Use the processes module to verify proxies with progress
784 processes::verify_proxies(proxies, &judge, concurrency).await?;
785
786 self.last_update_time = Some(Utc::now());
787 Ok(())
788 }
789
790 /// Enrich all proxies with IP metadata in parallel.
791 ///
792 /// This method is useful for bulk enrichment of proxies, using
793 /// concurrent processing for efficiency.
794 ///
795 /// # Arguments
796 ///
797 /// * `proxies` - A mutable slice of proxies to enrich
798 /// * `concurrency` - The maximum number of concurrent enrichment operations
799 ///
800 /// # Returns
801 ///
802 /// Ok(()) if the enrichment process completes.
803 ///
804 /// # Errors
805 ///
806 /// Returns an error if there's a critical failure in the enrichment process.
807 ///
808 /// # Panics
809 ///
810 /// Panics if `init_sleuth()` has been called successfully but the internal sleuth
811 /// is still `None`, which should never happen in practice.
812 pub async fn enrich_all_proxies(
813 &mut self,
814 proxies: &mut [Proxy],
815 concurrency: usize,
816 ) -> ManagerResult<()> {
817 // Only proceed if sleuth is initialized
818 if self.sleuth.is_none() {
819 self.init_sleuth()?;
820 }
821
822 let sleuth = self.sleuth.clone().unwrap();
823
824 if proxies.is_empty() {
825 return Ok(());
826 }
827
828 // Use the processes module to enrich proxies with progress
829 processes::enrich_proxies(proxies, &sleuth, concurrency).await?;
830
831 self.last_update_time = Some(Utc::now());
832 Ok(())
833 }
834
835 /// Fetch proxies from all active sources in parallel.
836 ///
837 /// This method scrapes proxies from all active sources concurrently,
838 /// handles errors gracefully, and filters out inactive or blacklisted sources.
839 ///
840 /// # Arguments
841 ///
842 /// * `concurrency` - The maximum number of concurrent fetch operations
843 ///
844 /// # Returns
845 ///
846 /// Ok(()) if the fetch process completes.
847 ///
848 /// # Errors
849 ///
850 /// Returns an error if there's a critical failure in the fetch process.
851 pub async fn fetch_from_all_sources(&mut self, concurrency: usize) -> ManagerResult<()> {
852 let active_sources: Vec<Source> = self
853 .sources
854 .values()
855 .filter(|s| s.last_failure_reason.is_none() || s.failure_count < s.use_count / 2)
856 .cloned()
857 .collect();
858
859 if active_sources.is_empty() {
860 info!("No active sources to fetch from");
861 return Ok(());
862 }
863
864 // Use the processes module to fetch from sources
865 let new_proxies =
866 processes::fetch_from_sources(&active_sources, &self.requestor, concurrency).await?;
867
868 // Add new proxies to the manager
869 let added = self.add_proxies(new_proxies)?;
870
871 // Update source metadata in the manager
872 for source in active_sources {
873 if let Some(s) = self.sources.get_mut(&source.url) {
874 s.last_used_at = source.last_used_at;
875 s.use_count = source.use_count;
876 s.proxies_found = source.proxies_found;
877 }
878 }
879
880 info!("Added {added} unique proxies from all sources");
881 self.last_update_time = Some(Utc::now());
882 Ok(())
883 }
884
885 /// Get the best proxies based on latency and success rate.
886 ///
887 /// This method selects the most reliable proxies based on their
888 /// success rate and latency. It's useful for getting a set of
889 /// high-quality proxies for critical tasks.
890 ///
891 /// # Arguments
892 ///
893 /// * `count` - The maximum number of proxies to return
894 ///
895 /// # Returns
896 ///
897 /// A vector containing references to the best proxies, ordered by quality.
898 ///
899 /// # Examples
900 ///
901 /// ```
902 /// // Get the 5 best proxies for an important task
903 /// let best_proxies = manager.get_best_proxies(5);
904 /// ```
905 #[must_use]
906 pub fn get_best_proxies(&self, count: usize) -> Vec<&Proxy> {
907 let mut proxies: Vec<&Proxy> = self
908 .proxies
909 .values()
910 .filter(|p| p.check_count > 0 && p.check_success_rate() > 50)
911 .collect();
912
913 // Sort by success rate and latency
914 proxies.sort_by(|a, b| {
915 let a_success = a.check_success_rate();
916 let b_success = b.check_success_rate();
917
918 // Compare success rates first (higher is better)
919 if a_success - b_success > 0 {
920 return b_success
921 .partial_cmp(&a_success)
922 .unwrap_or(std::cmp::Ordering::Equal);
923 }
924
925 // If success rates are similar, compare latency (lower is better)
926 match (a.latency_ms, b.latency_ms) {
927 (Some(a_lat), Some(b_lat)) => a_lat.cmp(&b_lat),
928 (Some(_), None) => std::cmp::Ordering::Less,
929 (None, Some(_)) => std::cmp::Ordering::Greater,
930 _ => std::cmp::Ordering::Equal,
931 }
932 });
933
934 // Take the requested number of proxies
935 proxies.truncate(count);
936 proxies
937 }
938}