Skip to main content

turbo_cdn/
lib.rs

1// Licensed under the MIT License
2// Copyright (c) 2025 Hal <hal.long@outlook.com>
3
4//! # Turbo CDN
5//!
6//! Intelligent download accelerator with automatic CDN optimization and concurrent chunked downloads.
7//!
8//! ## Features
9//!
10//! - **Automatic CDN Optimization**: GitHub, jsDelivr, Fastly, Cloudflare mirrors
11//! - **Concurrent Downloads**: Multi-threaded chunked downloads with range requests
12//! - **Smart URL Mapping**: Regex-based URL pattern matching for CDN selection
13//! - **Resume Support**: Automatic resume for interrupted downloads
14//! - **Geographic Awareness**: Location-based CDN selection for optimal speed
15//! - **Progress Tracking**: Real-time download progress and speed monitoring
16//!
17//! ## Quick Start
18//!
19//! ```rust,no_run
20//! use turbo_cdn::*;
21//!
22//! #[tokio::main]
23//! async fn main() -> turbo_cdn::Result<()> {
24//!     // Simple download with automatic CDN optimization
25//!     let downloader = TurboCdn::new().await?;
26//!     
27//!     let result = downloader.download_from_url(
28//!         "https://github.com/BurntSushi/ripgrep/releases/download/14.1.1/ripgrep-14.1.1-x86_64-pc-windows-msvc.zip"
29//!     ).await?;
30//!     
31//!     println!("Downloaded {} bytes to: {}", result.size, result.path.display());
32//!     println!("Speed: {:.2} MB/s", result.speed / 1024.0 / 1024.0);
33//!     Ok(())
34//! }
35//! ```
36
37// Initialize rustls crypto provider once (required when using rustls-no-provider feature)
38#[cfg(any(feature = "rustls-ring", feature = "rustls-aws-lc"))]
39use std::sync::Once;
40#[cfg(any(feature = "rustls-ring", feature = "rustls-aws-lc"))]
41static INIT_RUSTLS: Once = Once::new();
42
43/// Initialize rustls crypto provider (ring backend)
44/// This must be called before creating any reqwest Client when using rustls-no-provider
45#[cfg(feature = "rustls-ring")]
46pub fn init_rustls_provider() {
47    INIT_RUSTLS.call_once(|| {
48        let _ = rustls::crypto::ring::default_provider().install_default();
49    });
50}
51
52/// Initialize rustls crypto provider (aws-lc-rs backend)
53#[cfg(all(feature = "rustls-aws-lc", not(feature = "rustls-ring")))]
54pub fn init_rustls_provider() {
55    INIT_RUSTLS.call_once(|| {
56        let _ = rustls_aws_lc::crypto::aws_lc_rs::default_provider().install_default();
57    });
58}
59
60/// No-op when rustls feature is disabled
61#[cfg(not(any(feature = "rustls-ring", feature = "rustls-aws-lc")))]
62pub fn init_rustls_provider() {
63    // No-op: rustls is not enabled, using native-tls or other TLS backend
64}
65
66pub mod adaptive_concurrency;
67pub mod adaptive_speed_controller;
68pub mod cdn_quality;
69pub mod cli_progress;
70pub mod concurrent_downloader;
71pub mod config;
72pub mod constants;
73pub mod dns_cache;
74pub mod error;
75pub mod geo_detection;
76pub mod github_releases;
77pub mod http_client;
78pub mod http_client_manager;
79pub mod load_balancer;
80pub mod logging;
81pub mod memory_tracker;
82pub mod mmap_writer;
83pub mod progress;
84pub mod server_quality_scorer;
85pub mod server_tracker;
86pub mod smart_chunking;
87pub mod smart_downloader;
88pub mod string_interner;
89pub mod url_mapper;
90
91// Note: Imports will be added as needed
92
93// Re-export commonly used types
94pub use concurrent_downloader::{ConcurrentDownloader, DownloadResult};
95pub use config::{Region, TurboCdnConfig};
96pub use constants::*;
97pub use error::{Result, TurboCdnError};
98pub use github_releases::{
99    AssetInfo, DataSource, FetchOptions, GitHubReleasesFetcher, ReleaseInfo, ReleasesResult,
100    VersionsResult,
101};
102pub use progress::{ConsoleProgressReporter, ProgressCallback, ProgressInfo, ProgressTracker};
103pub use server_tracker::{PerformanceSummary, ServerStats};
104pub use url_mapper::UrlMapper;
105
106// Internal imports
107use std::sync::Arc;
108use std::time::Instant;
109use tokio::sync::RwLock;
110use tracing::{info, warn};
111
112/// Download options for customizing download behavior
113#[derive(Default)]
114pub struct DownloadOptions {
115    /// Progress callback function
116    pub progress_callback: Option<ProgressCallback>,
117    /// Maximum number of concurrent chunks
118    pub max_concurrent_chunks: Option<usize>,
119    /// Chunk size for downloads
120    pub chunk_size: Option<u64>,
121    /// Enable resume for interrupted downloads
122    pub enable_resume: bool,
123    /// Custom headers to include in requests
124    pub custom_headers: Option<std::collections::HashMap<String, String>>,
125    /// Override timeout for this specific download
126    pub timeout_override: Option<std::time::Duration>,
127    /// Verify file integrity after download
128    pub verify_integrity: bool,
129    /// Expected file size (for progress calculation)
130    pub expected_size: Option<u64>,
131}
132
133impl DownloadOptions {
134    /// Create new download options with defaults
135    pub fn new() -> Self {
136        Self::default()
137    }
138
139    /// Set progress callback
140    pub fn with_progress_callback(mut self, callback: ProgressCallback) -> Self {
141        self.progress_callback = Some(callback);
142        self
143    }
144
145    /// Set maximum concurrent chunks
146    pub fn with_max_concurrent_chunks(mut self, chunks: usize) -> Self {
147        self.max_concurrent_chunks = Some(chunks);
148        self
149    }
150
151    /// Set chunk size
152    pub fn with_chunk_size(mut self, size: u64) -> Self {
153        self.chunk_size = Some(size);
154        self
155    }
156
157    /// Enable resume support
158    pub fn with_resume(mut self, enable: bool) -> Self {
159        self.enable_resume = enable;
160        self
161    }
162
163    /// Add custom header
164    pub fn with_header<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
165        if self.custom_headers.is_none() {
166            self.custom_headers = Some(std::collections::HashMap::new());
167        }
168        self.custom_headers
169            .as_mut()
170            .unwrap()
171            .insert(key.into(), value.into());
172        self
173    }
174
175    /// Set timeout override
176    pub fn with_timeout(mut self, timeout: std::time::Duration) -> Self {
177        self.timeout_override = Some(timeout);
178        self
179    }
180
181    /// Enable integrity verification
182    pub fn with_integrity_verification(mut self, enable: bool) -> Self {
183        self.verify_integrity = enable;
184        self
185    }
186
187    /// Set expected file size
188    pub fn with_expected_size(mut self, size: u64) -> Self {
189        self.expected_size = Some(size);
190        self
191    }
192}
193
194impl std::fmt::Debug for DownloadOptions {
195    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196        f.debug_struct("DownloadOptions")
197            .field(
198                "progress_callback",
199                &self.progress_callback.as_ref().map(|_| "<callback>"),
200            )
201            .field("max_concurrent_chunks", &self.max_concurrent_chunks)
202            .field("chunk_size", &self.chunk_size)
203            .field("enable_resume", &self.enable_resume)
204            .finish()
205    }
206}
207
208impl Clone for DownloadOptions {
209    fn clone(&self) -> Self {
210        Self {
211            progress_callback: None, // Cannot clone function pointers
212            max_concurrent_chunks: self.max_concurrent_chunks,
213            chunk_size: self.chunk_size,
214            enable_resume: self.enable_resume,
215            custom_headers: self.custom_headers.clone(),
216            timeout_override: self.timeout_override,
217            verify_integrity: self.verify_integrity,
218            expected_size: self.expected_size,
219        }
220    }
221}
222
223/// Main TurboCdn client - simplified download accelerator
224///
225/// # Example
226/// ```rust,no_run
227/// use turbo_cdn::*;
228///
229/// #[tokio::main]
230/// async fn main() -> turbo_cdn::Result<()> {
231///     // Using builder pattern
232///     let downloader = TurboCdn::builder()
233///         .with_region(Region::China)
234///         .with_max_concurrent_downloads(16)
235///         .build()
236///         .await?;
237///     
238///     let result = downloader.download_from_url("https://example.com/file.zip").await?;
239///     println!("Downloaded: {}", result.path.display());
240///     Ok(())
241/// }
242/// ```
243#[derive(Debug)]
244pub struct TurboCdn {
245    url_mapper: Arc<RwLock<UrlMapper>>,
246    downloader: ConcurrentDownloader,
247    #[allow(dead_code)]
248    progress_tracker: Option<Arc<ProgressTracker>>,
249    stats: Arc<RwLock<TurboCdnStats>>,
250    created_at: Instant,
251}
252
253impl TurboCdn {
254    /// Create a new builder for TurboCdn
255    pub fn builder() -> TurboCdnBuilder {
256        TurboCdnBuilder::new()
257    }
258
259    /// Create a TurboCdn client with default configuration
260    pub async fn new() -> Result<Self> {
261        let config = TurboCdnConfig::load().unwrap_or_default();
262        Self::with_config(config).await
263    }
264
265    /// Create a TurboCdn client with custom configuration
266    pub async fn with_config(config: TurboCdnConfig) -> Result<Self> {
267        // Auto-detect region if enabled
268        let region = if config.geo_detection.auto_detect_region {
269            let mut geo_detector = crate::geo_detection::GeoDetector::new(config.clone());
270            match geo_detector.detect_region().await {
271                Ok(detected_region) => {
272                    info!("Auto-detected region: {:?}", detected_region);
273                    detected_region
274                }
275                Err(e) => {
276                    warn!("Failed to auto-detect region: {}, using default", e);
277                    config.general.default_region.clone()
278                }
279            }
280        } else {
281            config.general.default_region.clone()
282        };
283
284        let url_mapper = UrlMapper::new(&config, region)?;
285        let downloader = ConcurrentDownloader::with_config(&config)?;
286
287        Ok(Self {
288            url_mapper: Arc::new(RwLock::new(url_mapper)),
289            downloader,
290            progress_tracker: None,
291            stats: Arc::new(RwLock::new(TurboCdnStats::default())),
292            created_at: Instant::now(),
293        })
294    }
295
296    /// Download from any supported URL with automatic CDN optimization
297    ///
298    /// This is the main download method that provides automatic CDN optimization
299    /// and concurrent chunked downloads for maximum speed.
300    ///
301    /// # Arguments
302    /// * `url` - The source URL to download from
303    ///
304    /// # Returns
305    /// * `DownloadResult` containing download information
306    ///
307    /// # Example
308    /// ```rust,no_run
309    /// use turbo_cdn::*;
310    ///
311    /// #[tokio::main]
312    /// async fn main() -> turbo_cdn::Result<()> {
313    ///     let downloader = TurboCdn::new().await?;
314    ///     
315    ///     let result = downloader.download_from_url(
316    ///         "https://github.com/BurntSushi/ripgrep/releases/download/14.1.1/ripgrep-14.1.1-x86_64-pc-windows-msvc.zip"
317    ///     ).await?;
318    ///     
319    ///     println!("Downloaded {} bytes to: {}", result.size, result.path.display());
320    ///     Ok(())
321    /// }
322    /// ```
323    pub async fn download_from_url(&self, url: &str) -> Result<DownloadResult> {
324        // Map URL to optimal CDN alternatives
325        let urls = self.url_mapper.read().await.map_url(url)?;
326
327        // Generate output filename from URL
328        let filename = self.extract_filename_from_url(url)?;
329        let output_path = std::env::temp_dir().join(&filename);
330
331        // Download with concurrent downloader
332        let result = self.downloader.download(&urls, &output_path, None).await?;
333
334        // Update stats
335        self.update_stats(&result).await;
336
337        Ok(result)
338    }
339
340    /// Download from URL to specific path
341    pub async fn download_to_path<P: AsRef<std::path::Path>>(
342        &self,
343        url: &str,
344        output_path: P,
345    ) -> Result<DownloadResult> {
346        let urls = self.url_mapper.read().await.map_url(url)?;
347        let result = self.downloader.download(&urls, output_path, None).await?;
348        self.update_stats(&result).await;
349        Ok(result)
350    }
351
352    /// Download directly from original URL without CDN optimization
353    pub async fn download_direct_from_url(&self, url: &str) -> Result<DownloadResult> {
354        // Use only the original URL, no CDN mapping
355        let urls = vec![url.to_string()];
356
357        // Generate output filename from URL
358        let filename = self.extract_filename_from_url(url)?;
359        let output_path = std::env::temp_dir().join(&filename);
360
361        // Download with concurrent downloader
362        let result = self.downloader.download(&urls, &output_path, None).await?;
363        self.update_stats(&result).await;
364        Ok(result)
365    }
366
367    /// Download directly from URL to specific path without CDN optimization
368    pub async fn download_direct_to_path<P: AsRef<std::path::Path>>(
369        &self,
370        url: &str,
371        output_path: P,
372    ) -> Result<DownloadResult> {
373        // Use only the original URL, no CDN mapping
374        let urls = vec![url.to_string()];
375        let result = self.downloader.download(&urls, output_path, None).await?;
376        self.update_stats(&result).await;
377        Ok(result)
378    }
379
380    /// Smart download that automatically selects the fastest method
381    pub async fn download_smart(&self, url: &str) -> Result<DownloadResult> {
382        self.download_smart_with_verbose(url, false).await
383    }
384
385    /// Smart download with verbose control
386    pub async fn download_smart_with_verbose(
387        &self,
388        url: &str,
389        verbose: bool,
390    ) -> Result<DownloadResult> {
391        use crate::smart_downloader::SmartDownloader;
392
393        let smart_downloader = SmartDownloader::new_with_verbose(verbose).await?;
394        smart_downloader.download_smart(url).await
395    }
396
397    /// Smart download to specific path with automatic method selection
398    pub async fn download_smart_to_path<P: AsRef<std::path::Path>>(
399        &self,
400        url: &str,
401        output_path: P,
402    ) -> Result<DownloadResult> {
403        self.download_smart_to_path_with_verbose(url, output_path, false)
404            .await
405    }
406
407    /// Smart download to specific path with verbose control
408    pub async fn download_smart_to_path_with_verbose<P: AsRef<std::path::Path>>(
409        &self,
410        url: &str,
411        output_path: P,
412        verbose: bool,
413    ) -> Result<DownloadResult> {
414        use crate::smart_downloader::SmartDownloader;
415
416        let smart_downloader = SmartDownloader::new_with_verbose(verbose).await?;
417        smart_downloader
418            .download_smart_to_path(url, output_path)
419            .await
420    }
421
422    /// Download with custom options
423    pub async fn download_with_options<P: AsRef<std::path::Path>>(
424        &self,
425        url: &str,
426        output_path: P,
427        options: DownloadOptions,
428    ) -> Result<DownloadResult> {
429        let urls = self.url_mapper.read().await.map_url(url)?;
430
431        // Create progress tracker if callback is provided
432        let progress_tracker = if options.progress_callback.is_some() {
433            let expected_size = options.expected_size.unwrap_or(0);
434            Some(Arc::new(ProgressTracker::new(expected_size)))
435        } else {
436            None
437        };
438
439        let result = self
440            .downloader
441            .download(&urls, output_path, progress_tracker)
442            .await?;
443        self.update_stats(&result).await;
444        Ok(result)
445    }
446
447    /// Get optimal CDN URL without downloading
448    pub async fn get_optimal_url(&self, url: &str) -> Result<String> {
449        let urls = self.url_mapper.read().await.map_url(url)?;
450        Ok(urls.into_iter().next().unwrap_or_else(|| url.to_string()))
451    }
452
453    /// Get all available CDN URLs for a given URL
454    pub async fn get_all_cdn_urls(&self, url: &str) -> Result<Vec<String>> {
455        self.url_mapper.read().await.map_url(url)
456    }
457
458    /// Check if a URL can be optimized
459    pub async fn can_optimize_url(&self, url: &str) -> bool {
460        self.url_mapper
461            .read()
462            .await
463            .map_url(url)
464            .map(|urls| urls.len() > 1)
465            .unwrap_or(false)
466    }
467
468    /// Get download statistics
469    pub async fn get_stats(&self) -> TurboCdnStats {
470        let mut stats = self.stats.read().await.clone();
471        stats.uptime = self.created_at.elapsed();
472        stats
473    }
474
475    /// Get server performance summary
476    pub fn get_performance_summary(&self) -> PerformanceSummary {
477        self.downloader.get_server_stats()
478    }
479
480    /// Get detailed server statistics for a specific URL
481    pub fn get_server_stats(&self, url: &str) -> Option<ServerStats> {
482        self.downloader.get_server_detail(url)
483    }
484
485    /// Reset statistics
486    pub async fn reset_stats(&self) {
487        let mut stats = self.stats.write().await;
488        *stats = TurboCdnStats::default();
489    }
490
491    /// Update internal statistics after a download
492    async fn update_stats(&self, result: &DownloadResult) {
493        let mut stats = self.stats.write().await;
494        stats.total_downloads += 1;
495        if result.size > 0 {
496            stats.successful_downloads += 1;
497            stats.total_bytes += result.size;
498            // Update moving average speed
499            let alpha = 0.3; // Smoothing factor
500            stats.average_speed = alpha * result.speed + (1.0 - alpha) * stats.average_speed;
501        } else {
502            stats.failed_downloads += 1;
503        }
504    }
505
506    /// Extract filename from URL
507    fn extract_filename_from_url(&self, url: &str) -> Result<String> {
508        let url_obj =
509            url::Url::parse(url).map_err(|e| TurboCdnError::config(format!("Invalid URL: {e}")))?;
510
511        let path = url_obj.path();
512        let filename = path.split('/').next_back().unwrap_or("download");
513
514        if filename.is_empty() || filename == "/" {
515            Ok("download".to_string())
516        } else {
517            Ok(filename.to_string())
518        }
519    }
520}
521
522/// Statistics for TurboCdn
523#[derive(Debug, Clone, Default)]
524pub struct TurboCdnStats {
525    /// Total downloads
526    pub total_downloads: u64,
527
528    /// Successful downloads
529    pub successful_downloads: u64,
530
531    /// Failed downloads
532    pub failed_downloads: u64,
533
534    /// Total bytes downloaded
535    pub total_bytes: u64,
536
537    /// Cache hit rate
538    pub cache_hit_rate: f64,
539
540    /// Average download speed in bytes per second
541    pub average_speed: f64,
542
543    /// Uptime since creation
544    pub uptime: std::time::Duration,
545}
546
547impl TurboCdnStats {
548    /// Get success rate as a percentage
549    pub fn success_rate(&self) -> f64 {
550        if self.total_downloads == 0 {
551            0.0
552        } else {
553            (self.successful_downloads as f64 / self.total_downloads as f64) * 100.0
554        }
555    }
556
557    /// Get average speed in MB/s
558    pub fn average_speed_mbps(&self) -> f64 {
559        self.average_speed / 1024.0 / 1024.0
560    }
561
562    /// Get total bytes in human-readable format
563    pub fn total_bytes_human(&self) -> String {
564        if self.total_bytes >= 1024 * 1024 * 1024 {
565            format!(
566                "{:.2} GB",
567                self.total_bytes as f64 / 1024.0 / 1024.0 / 1024.0
568            )
569        } else if self.total_bytes >= 1024 * 1024 {
570            format!("{:.2} MB", self.total_bytes as f64 / 1024.0 / 1024.0)
571        } else if self.total_bytes >= 1024 {
572            format!("{:.2} KB", self.total_bytes as f64 / 1024.0)
573        } else {
574            format!("{} B", self.total_bytes)
575        }
576    }
577}
578
579/// Builder for TurboCdn with fluent API
580#[derive(Debug, Clone)]
581pub struct TurboCdnBuilder {
582    config: TurboCdnConfig,
583}
584
585impl TurboCdnBuilder {
586    /// Create a new builder with default configuration
587    pub fn new() -> Self {
588        Self {
589            config: TurboCdnConfig::load().unwrap_or_default(),
590        }
591    }
592
593    /// Set the region for CDN optimization
594    pub fn with_region(mut self, region: Region) -> Self {
595        self.config.general.default_region = region;
596        self.config.geo_detection.auto_detect_region = false;
597        self
598    }
599
600    /// Enable or disable auto region detection
601    pub fn with_auto_detect_region(mut self, enable: bool) -> Self {
602        self.config.geo_detection.auto_detect_region = enable;
603        self
604    }
605
606    /// Set maximum concurrent downloads
607    pub fn with_max_concurrent_downloads(mut self, count: usize) -> Self {
608        self.config.performance.max_concurrent_downloads = count;
609        self
610    }
611
612    /// Set chunk size for downloads
613    pub fn with_chunk_size(mut self, size: u64) -> Self {
614        self.config.performance.chunk_size = size;
615        self
616    }
617
618    /// Set connection timeout
619    pub fn with_timeout(mut self, timeout_secs: u64) -> Self {
620        self.config.performance.timeout = timeout_secs;
621        self
622    }
623
624    /// Enable or disable adaptive chunking
625    pub fn with_adaptive_chunking(mut self, enable: bool) -> Self {
626        self.config.performance.adaptive_chunking = enable;
627        self
628    }
629
630    /// Set retry attempts
631    pub fn with_retry_attempts(mut self, attempts: usize) -> Self {
632        self.config.performance.retry_attempts = attempts;
633        self
634    }
635
636    /// Enable debug mode
637    pub fn with_debug(mut self, enable: bool) -> Self {
638        self.config.general.debug = enable;
639        self
640    }
641
642    /// Set custom user agent
643    pub fn with_user_agent<S: Into<String>>(mut self, user_agent: S) -> Self {
644        self.config.general.user_agent = user_agent.into();
645        self
646    }
647
648    /// Use a custom configuration
649    pub fn with_config(mut self, config: TurboCdnConfig) -> Self {
650        self.config = config;
651        self
652    }
653
654    /// Build the TurboCdn client
655    pub async fn build(self) -> Result<TurboCdn> {
656        TurboCdn::with_config(self.config).await
657    }
658}
659
660impl Default for TurboCdnBuilder {
661    fn default() -> Self {
662        Self::new()
663    }
664}
665
666/// Synchronous API for blocking operations
667pub mod sync_api {
668    use super::*;
669    use std::path::Path;
670
671    /// Synchronous wrapper for TurboCdn
672    ///
673    /// Provides blocking APIs for environments that don't use async/await.
674    /// Internally uses a Tokio runtime to execute async operations.
675    #[derive(Debug)]
676    pub struct SyncTurboCdn {
677        runtime: tokio::runtime::Runtime,
678        inner: TurboCdn,
679    }
680
681    impl SyncTurboCdn {
682        /// Create a new synchronous TurboCdn client
683        pub fn new() -> Result<Self> {
684            let runtime = tokio::runtime::Runtime::new()
685                .map_err(|e| TurboCdnError::internal(format!("Failed to create runtime: {e}")))?;
686
687            let inner = runtime.block_on(TurboCdn::new())?;
688
689            Ok(Self { runtime, inner })
690        }
691
692        /// Create a synchronous TurboCdn client with custom configuration
693        pub fn with_config(config: TurboCdnConfig) -> Result<Self> {
694            let runtime = tokio::runtime::Runtime::new()
695                .map_err(|e| TurboCdnError::internal(format!("Failed to create runtime: {e}")))?;
696
697            let inner = runtime.block_on(TurboCdn::with_config(config))?;
698
699            Ok(Self { runtime, inner })
700        }
701
702        /// Download from URL (blocking)
703        pub fn download_from_url(&self, url: &str) -> Result<DownloadResult> {
704            self.runtime.block_on(self.inner.download_from_url(url))
705        }
706
707        /// Download to specific path (blocking)
708        pub fn download_to_path<P: AsRef<Path>>(
709            &self,
710            url: &str,
711            output_path: P,
712        ) -> Result<DownloadResult> {
713            self.runtime
714                .block_on(self.inner.download_to_path(url, output_path))
715        }
716
717        /// Get optimal CDN URL (blocking)
718        pub fn get_optimal_url(&self, url: &str) -> Result<String> {
719            self.runtime.block_on(self.inner.get_optimal_url(url))
720        }
721    }
722
723    /// Quick synchronous functions for simple use cases
724    pub mod quick {
725        use super::*;
726
727        /// Quick download from URL with default settings (blocking)
728        pub fn download_url(url: &str) -> Result<DownloadResult> {
729            let client = SyncTurboCdn::new()?;
730            client.download_from_url(url)
731        }
732
733        /// Quick URL optimization (blocking)
734        pub fn optimize_url(url: &str) -> Result<String> {
735            let client = SyncTurboCdn::new()?;
736            client.get_optimal_url(url)
737        }
738
739        /// Quick download to specific path (blocking)
740        pub fn download_to_path<P: AsRef<Path>>(
741            url: &str,
742            output_path: P,
743        ) -> Result<DownloadResult> {
744            let client = SyncTurboCdn::new()?;
745            client.download_to_path(url, output_path)
746        }
747    }
748}
749
750/// Async API module for external integrations (like vx)
751pub mod async_api {
752    use super::*;
753    use std::sync::Arc;
754    use tokio::sync::Mutex;
755
756    /// Async wrapper for TurboCdn that provides thread-safe access
757    #[derive(Debug, Clone)]
758    pub struct AsyncTurboCdn {
759        inner: Arc<Mutex<TurboCdn>>,
760    }
761
762    impl AsyncTurboCdn {
763        /// Create a new AsyncTurboCdn instance
764        pub async fn new() -> Result<Self> {
765            let turbo_cdn = TurboCdn::new().await?;
766            Ok(Self {
767                inner: Arc::new(Mutex::new(turbo_cdn)),
768            })
769        }
770
771        /// Create a new AsyncTurboCdn instance with custom configuration
772        pub async fn with_config(config: TurboCdnConfig) -> Result<Self> {
773            let turbo_cdn = TurboCdn::with_config(config).await?;
774            Ok(Self {
775                inner: Arc::new(Mutex::new(turbo_cdn)),
776            })
777        }
778
779        /// Download from any supported URL with automatic optimization (async version)
780        pub async fn download_from_url_async(&self, url: &str) -> Result<DownloadResult> {
781            let client = self.inner.lock().await;
782            client.download_from_url(url).await
783        }
784
785        /// Get optimal CDN URL without downloading (async version)
786        pub async fn get_optimal_url_async(&self, url: &str) -> Result<String> {
787            let client = self.inner.lock().await;
788            client.get_optimal_url(url).await
789        }
790
791        /// Download to specific path (async version)
792        pub async fn download_to_path_async<P: AsRef<std::path::Path>>(
793            &self,
794            url: &str,
795            output_path: P,
796        ) -> Result<DownloadResult> {
797            let client = self.inner.lock().await;
798            client.download_to_path(url, output_path).await
799        }
800    }
801
802    /// Convenience functions for quick async operations
803    pub mod quick {
804        use super::*;
805
806        /// Quick download from URL with default settings
807        pub async fn download_url(url: &str) -> Result<DownloadResult> {
808            let client = AsyncTurboCdn::new().await?;
809            client.download_from_url_async(url).await
810        }
811
812        /// Quick URL optimization
813        pub async fn optimize_url(url: &str) -> Result<String> {
814            let client = AsyncTurboCdn::new().await?;
815            client.get_optimal_url_async(url).await
816        }
817
818        /// Quick download to specific path
819        pub async fn download_url_to_path<P: AsRef<std::path::Path>>(
820            url: &str,
821            output_path: P,
822        ) -> Result<DownloadResult> {
823            let client = AsyncTurboCdn::new().await?;
824            client.download_to_path_async(url, output_path).await
825        }
826
827        /// Fetch GitHub releases version list with automatic CDN fallback
828        ///
829        /// When GitHub API is rate-limited or unavailable, automatically falls back
830        /// to jsDelivr data API.
831        ///
832        /// # Example
833        /// ```rust,no_run
834        /// use turbo_cdn::async_api::quick;
835        ///
836        /// #[tokio::main]
837        /// async fn main() -> turbo_cdn::Result<()> {
838        ///     let versions = quick::fetch_github_versions("BurntSushi", "ripgrep").await?;
839        ///     println!("Latest: {}", versions[0]);
840        ///     Ok(())
841        /// }
842        /// ```
843        pub async fn fetch_github_versions(owner: &str, repo: &str) -> Result<Vec<String>> {
844            crate::github_releases::fetch_versions(owner, repo).await
845        }
846
847        /// List detailed GitHub release information with automatic CDN fallback
848        pub async fn list_github_releases(
849            owner: &str,
850            repo: &str,
851        ) -> Result<Vec<crate::github_releases::ReleaseInfo>> {
852            crate::github_releases::list_releases(owner, repo).await
853        }
854
855        /// Fetch the latest GitHub release version
856        pub async fn fetch_latest_github_version(owner: &str, repo: &str) -> Result<String> {
857            crate::github_releases::fetch_latest_version(owner, repo).await
858        }
859    }
860}
861
862/// Initialize tracing for the library (deprecated - use logging module)
863#[deprecated(note = "Use logging::init_api_logging() instead")]
864pub fn init_tracing() {
865    let _ = logging::init_api_logging();
866}
867
868/// Initialize tracing with specific level (deprecated - use logging module)
869#[deprecated(note = "Use logging module functions instead")]
870pub fn init_tracing_with_level(level: &str) {
871    let config = logging::LoggingConfig {
872        level: level.to_string(),
873        ..logging::LoggingConfig::api()
874    };
875    let _ = logging::init_logging(config);
876}
877
878#[cfg(test)]
879mod tests {
880    use super::*;
881
882    #[tokio::test]
883    async fn test_turbo_cdn_new() {
884        let result = TurboCdn::new().await;
885        assert!(result.is_ok());
886    }
887
888    #[test]
889    fn test_turbo_cdn_stats_creation() {
890        let stats = TurboCdnStats {
891            total_downloads: 100,
892            successful_downloads: 95,
893            failed_downloads: 5,
894            total_bytes: 1024 * 1024,
895            cache_hit_rate: 0.8,
896            average_speed: 1000.0,
897            uptime: std::time::Duration::from_secs(3600),
898        };
899
900        assert_eq!(stats.total_downloads, 100);
901        assert_eq!(stats.successful_downloads, 95);
902        assert_eq!(stats.failed_downloads, 5);
903        assert_eq!(stats.total_bytes, 1024 * 1024);
904        assert_eq!(stats.cache_hit_rate, 0.8);
905        assert_eq!(stats.average_speed, 1000.0);
906    }
907
908    #[test]
909    fn test_download_result_creation() {
910        use std::path::PathBuf;
911        use std::time::Duration;
912
913        let result = DownloadResult {
914            path: PathBuf::from("/tmp/file.zip"),
915            size: 1024,
916            duration: Duration::from_secs(1),
917            speed: 1024.0,
918            url: "https://github.com/owner/repo/releases/download/v1.0.0/file.zip".to_string(),
919            resumed: false,
920        };
921
922        assert_eq!(result.path, PathBuf::from("/tmp/file.zip"));
923        assert_eq!(result.size, 1024);
924        assert_eq!(result.duration, Duration::from_secs(1));
925        assert_eq!(result.speed, 1024.0);
926        assert!(!result.resumed);
927    }
928}