1#[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#[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#[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#[cfg(not(any(feature = "rustls-ring", feature = "rustls-aws-lc")))]
62pub fn init_rustls_provider() {
63 }
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
91pub 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
106use std::sync::Arc;
108use std::time::Instant;
109use tokio::sync::RwLock;
110use tracing::{info, warn};
111
112#[derive(Default)]
114pub struct DownloadOptions {
115 pub progress_callback: Option<ProgressCallback>,
117 pub max_concurrent_chunks: Option<usize>,
119 pub chunk_size: Option<u64>,
121 pub enable_resume: bool,
123 pub custom_headers: Option<std::collections::HashMap<String, String>>,
125 pub timeout_override: Option<std::time::Duration>,
127 pub verify_integrity: bool,
129 pub expected_size: Option<u64>,
131}
132
133impl DownloadOptions {
134 pub fn new() -> Self {
136 Self::default()
137 }
138
139 pub fn with_progress_callback(mut self, callback: ProgressCallback) -> Self {
141 self.progress_callback = Some(callback);
142 self
143 }
144
145 pub fn with_max_concurrent_chunks(mut self, chunks: usize) -> Self {
147 self.max_concurrent_chunks = Some(chunks);
148 self
149 }
150
151 pub fn with_chunk_size(mut self, size: u64) -> Self {
153 self.chunk_size = Some(size);
154 self
155 }
156
157 pub fn with_resume(mut self, enable: bool) -> Self {
159 self.enable_resume = enable;
160 self
161 }
162
163 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 pub fn with_timeout(mut self, timeout: std::time::Duration) -> Self {
177 self.timeout_override = Some(timeout);
178 self
179 }
180
181 pub fn with_integrity_verification(mut self, enable: bool) -> Self {
183 self.verify_integrity = enable;
184 self
185 }
186
187 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, 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#[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 pub fn builder() -> TurboCdnBuilder {
256 TurboCdnBuilder::new()
257 }
258
259 pub async fn new() -> Result<Self> {
261 let config = TurboCdnConfig::load().unwrap_or_default();
262 Self::with_config(config).await
263 }
264
265 pub async fn with_config(config: TurboCdnConfig) -> Result<Self> {
267 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 pub async fn download_from_url(&self, url: &str) -> Result<DownloadResult> {
324 let urls = self.url_mapper.read().await.map_url(url)?;
326
327 let filename = self.extract_filename_from_url(url)?;
329 let output_path = std::env::temp_dir().join(&filename);
330
331 let result = self.downloader.download(&urls, &output_path, None).await?;
333
334 self.update_stats(&result).await;
336
337 Ok(result)
338 }
339
340 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 pub async fn download_direct_from_url(&self, url: &str) -> Result<DownloadResult> {
354 let urls = vec![url.to_string()];
356
357 let filename = self.extract_filename_from_url(url)?;
359 let output_path = std::env::temp_dir().join(&filename);
360
361 let result = self.downloader.download(&urls, &output_path, None).await?;
363 self.update_stats(&result).await;
364 Ok(result)
365 }
366
367 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 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 pub async fn download_smart(&self, url: &str) -> Result<DownloadResult> {
382 self.download_smart_with_verbose(url, false).await
383 }
384
385 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 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 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 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 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 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 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 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 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 pub fn get_performance_summary(&self) -> PerformanceSummary {
477 self.downloader.get_server_stats()
478 }
479
480 pub fn get_server_stats(&self, url: &str) -> Option<ServerStats> {
482 self.downloader.get_server_detail(url)
483 }
484
485 pub async fn reset_stats(&self) {
487 let mut stats = self.stats.write().await;
488 *stats = TurboCdnStats::default();
489 }
490
491 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 let alpha = 0.3; stats.average_speed = alpha * result.speed + (1.0 - alpha) * stats.average_speed;
501 } else {
502 stats.failed_downloads += 1;
503 }
504 }
505
506 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#[derive(Debug, Clone, Default)]
524pub struct TurboCdnStats {
525 pub total_downloads: u64,
527
528 pub successful_downloads: u64,
530
531 pub failed_downloads: u64,
533
534 pub total_bytes: u64,
536
537 pub cache_hit_rate: f64,
539
540 pub average_speed: f64,
542
543 pub uptime: std::time::Duration,
545}
546
547impl TurboCdnStats {
548 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 pub fn average_speed_mbps(&self) -> f64 {
559 self.average_speed / 1024.0 / 1024.0
560 }
561
562 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#[derive(Debug, Clone)]
581pub struct TurboCdnBuilder {
582 config: TurboCdnConfig,
583}
584
585impl TurboCdnBuilder {
586 pub fn new() -> Self {
588 Self {
589 config: TurboCdnConfig::load().unwrap_or_default(),
590 }
591 }
592
593 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 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 pub fn with_max_concurrent_downloads(mut self, count: usize) -> Self {
608 self.config.performance.max_concurrent_downloads = count;
609 self
610 }
611
612 pub fn with_chunk_size(mut self, size: u64) -> Self {
614 self.config.performance.chunk_size = size;
615 self
616 }
617
618 pub fn with_timeout(mut self, timeout_secs: u64) -> Self {
620 self.config.performance.timeout = timeout_secs;
621 self
622 }
623
624 pub fn with_adaptive_chunking(mut self, enable: bool) -> Self {
626 self.config.performance.adaptive_chunking = enable;
627 self
628 }
629
630 pub fn with_retry_attempts(mut self, attempts: usize) -> Self {
632 self.config.performance.retry_attempts = attempts;
633 self
634 }
635
636 pub fn with_debug(mut self, enable: bool) -> Self {
638 self.config.general.debug = enable;
639 self
640 }
641
642 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 pub fn with_config(mut self, config: TurboCdnConfig) -> Self {
650 self.config = config;
651 self
652 }
653
654 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
666pub mod sync_api {
668 use super::*;
669 use std::path::Path;
670
671 #[derive(Debug)]
676 pub struct SyncTurboCdn {
677 runtime: tokio::runtime::Runtime,
678 inner: TurboCdn,
679 }
680
681 impl SyncTurboCdn {
682 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 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 pub fn download_from_url(&self, url: &str) -> Result<DownloadResult> {
704 self.runtime.block_on(self.inner.download_from_url(url))
705 }
706
707 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 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 pub mod quick {
725 use super::*;
726
727 pub fn download_url(url: &str) -> Result<DownloadResult> {
729 let client = SyncTurboCdn::new()?;
730 client.download_from_url(url)
731 }
732
733 pub fn optimize_url(url: &str) -> Result<String> {
735 let client = SyncTurboCdn::new()?;
736 client.get_optimal_url(url)
737 }
738
739 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
750pub mod async_api {
752 use super::*;
753 use std::sync::Arc;
754 use tokio::sync::Mutex;
755
756 #[derive(Debug, Clone)]
758 pub struct AsyncTurboCdn {
759 inner: Arc<Mutex<TurboCdn>>,
760 }
761
762 impl AsyncTurboCdn {
763 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 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 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 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 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 pub mod quick {
804 use super::*;
805
806 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 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 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 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 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 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#[deprecated(note = "Use logging::init_api_logging() instead")]
864pub fn init_tracing() {
865 let _ = logging::init_api_logging();
866}
867
868#[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}