Skip to main content

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}