1use crate::error::{IoError, Result};
28use std::collections::HashMap;
29use std::path::Path;
30use std::time::Duration;
31
32pub mod cloud;
34pub mod http;
36pub mod streaming;
38
39#[derive(Debug, Clone)]
41pub struct NetworkConfig {
42 pub connect_timeout: Duration,
44 pub read_timeout: Duration,
46 pub max_retries: u32,
48 pub user_agent: String,
50 pub headers: HashMap<String, String>,
52 pub compression: bool,
54 pub cache_dir: Option<String>,
56 pub max_cache_size: u64,
58}
59
60impl Default for NetworkConfig {
61 fn default() -> Self {
62 let mut headers = HashMap::new();
63 headers.insert("Accept".to_string(), "*/*".to_string());
64
65 Self {
66 connect_timeout: Duration::from_secs(30),
67 read_timeout: Duration::from_secs(300),
68 max_retries: 3,
69 user_agent: "scirs2-io/0.1.0".to_string(),
70 headers,
71 compression: true,
72 cache_dir: None,
73 max_cache_size: 1024, }
75 }
76}
77
78#[derive(Debug)]
80pub struct NetworkClient {
81 config: NetworkConfig,
82 #[cfg(feature = "reqwest")]
83 http_client: Option<reqwest::Client>,
84 cloud_provider: Option<cloud::CloudProvider>,
85}
86
87impl Default for NetworkClient {
88 fn default() -> Self {
89 Self::new()
90 }
91}
92
93impl NetworkClient {
94 pub fn new() -> Self {
96 Self {
97 config: NetworkConfig::default(),
98 #[cfg(feature = "reqwest")]
99 http_client: None,
100 cloud_provider: None,
101 }
102 }
103
104 pub fn with_config(config: NetworkConfig) -> Self {
106 Self {
107 config,
108 #[cfg(feature = "reqwest")]
109 http_client: None,
110 cloud_provider: None,
111 }
112 }
113
114 pub fn with_cloud_provider(mut self, provider: cloud::CloudProvider) -> Self {
116 self.cloud_provider = Some(provider);
117 self
118 }
119
120 pub fn with_cache_dir<P: AsRef<Path>>(mut self, cache_dir: P) -> Self {
122 self.config.cache_dir = Some(cache_dir.as_ref().to_string_lossy().to_string());
123 self
124 }
125
126 #[cfg(feature = "reqwest")]
128 pub async fn download<P: AsRef<Path>>(&self, url: &str, localpath: P) -> Result<()> {
129 if let Some(_client) = &self.http_client {
130 let mut http_client = http::HttpClient::new(self.config.clone());
132 http_client.init()?;
133 http_client.download(url, localpath).await
134 } else {
135 Err(IoError::ConfigError(
136 "HTTP client not configured".to_string(),
137 ))
138 }
139 }
140
141 #[cfg(feature = "reqwest")]
143 pub async fn upload<P: AsRef<Path>>(&self, localpath: P, url: &str) -> Result<()> {
144 if let Some(_client) = &self.http_client {
145 let mut http_client = http::HttpClient::new(self.config.clone());
147 http_client.init()?;
148 http_client.upload(localpath, url).await
149 } else {
150 Err(IoError::ConfigError(
151 "HTTP client not configured".to_string(),
152 ))
153 }
154 }
155
156 pub async fn upload_to_cloud<P: AsRef<Path>>(
158 &self,
159 localpath: P,
160 remote_path: &str,
161 ) -> Result<()> {
162 if let Some(ref provider) = self.cloud_provider {
163 provider.upload_file(localpath, remote_path).await
164 } else {
165 Err(IoError::ConfigError(
166 "No cloud provider configured".to_string(),
167 ))
168 }
169 }
170
171 pub async fn download_from_cloud<P: AsRef<Path>>(
173 &self,
174 remote_path: &str,
175 localpath: P,
176 ) -> Result<()> {
177 if let Some(ref provider) = self.cloud_provider {
178 provider.download_file(remote_path, localpath).await
179 } else {
180 Err(IoError::ConfigError(
181 "No cloud provider configured".to_string(),
182 ))
183 }
184 }
185
186 pub async fn list_cloud_files(&self, path: &str) -> Result<Vec<String>> {
188 if let Some(ref provider) = self.cloud_provider {
189 provider.list_files(path).await
190 } else {
191 Err(IoError::ConfigError(
192 "No cloud provider configured".to_string(),
193 ))
194 }
195 }
196
197 pub async fn cloud_file_exists(&self, path: &str) -> Result<bool> {
199 if let Some(ref provider) = self.cloud_provider {
200 provider.file_exists(path).await
201 } else {
202 Err(IoError::ConfigError(
203 "No cloud provider configured".to_string(),
204 ))
205 }
206 }
207
208 pub async fn get_cloud_file_metadata(&self, path: &str) -> Result<cloud::FileMetadata> {
210 if let Some(ref provider) = self.cloud_provider {
211 provider.get_metadata(path).await
212 } else {
213 Err(IoError::ConfigError(
214 "No cloud provider configured".to_string(),
215 ))
216 }
217 }
218
219 pub fn clear_cache(&self) -> Result<()> {
221 if let Some(ref cache_dir) = self.config.cache_dir {
222 let cache_path = Path::new(cache_dir);
223 if cache_path.exists() {
224 std::fs::remove_dir_all(cache_path)
225 .map_err(|e| IoError::FileError(format!("Failed to clear cache: {}", e)))?;
226 std::fs::create_dir_all(cache_path).map_err(|e| {
227 IoError::FileError(format!("Failed to recreate cache dir: {}", e))
228 })?;
229 }
230 }
231 Ok(())
232 }
233
234 pub fn get_cache_info(&self) -> Result<(u64, u64)> {
236 if let Some(ref cache_dir) = self.config.cache_dir {
237 let cache_path = Path::new(cache_dir);
238 if cache_path.exists() {
239 let mut total_size = 0u64;
240 let mut file_count = 0u64;
241
242 for entry in (std::fs::read_dir(cache_path)
243 .map_err(|e| IoError::FileError(format!("Failed to read cache dir: {}", e)))?)
244 .flatten()
245 {
246 if let Ok(metadata) = entry.metadata() {
247 if metadata.is_file() {
248 total_size += metadata.len();
249 file_count += 1;
250 }
251 }
252 }
253 return Ok((total_size, file_count));
254 }
255 }
256 Ok((0, 0))
257 }
258}
259
260#[cfg(feature = "reqwest")]
263pub async fn download_file<P: AsRef<Path>>(url: &str, localpath: P) -> Result<()> {
264 let client = NetworkClient::new();
265 client.download(url, localpath).await
266}
267
268#[cfg(feature = "reqwest")]
270pub async fn upload_file<P: AsRef<Path>>(localpath: P, url: &str) -> Result<()> {
271 let client = NetworkClient::new();
272 client.upload(localpath, url).await
273}
274
275#[cfg(feature = "reqwest")]
277pub async fn download_with_cache<P: AsRef<Path>>(
278 url: &str,
279 localpath: P,
280 cache_dir: Option<&str>,
281) -> Result<()> {
282 let mut client = NetworkClient::new();
283 if let Some(cache) = cache_dir {
284 client = client.with_cache_dir(cache);
285 }
286 client.download(url, localpath).await
287}
288
289#[allow(dead_code)]
291pub fn create_cloud_client(provider: cloud::CloudProvider) -> NetworkClient {
292 NetworkClient::new().with_cloud_provider(provider)
293}
294
295#[cfg(feature = "reqwest")]
297pub async fn batch_download(downloads: Vec<(&str, &str)>) -> Result<Vec<Result<()>>> {
298 let client = NetworkClient::new();
299 let mut results = Vec::new();
300
301 for (url, localpath) in downloads {
302 let result = client.download(url, localpath).await;
303 results.push(result);
304 }
305
306 Ok(results)
307}
308
309pub async fn batch_upload_to_cloud(
311 client: &NetworkClient,
312 uploads: Vec<(&str, &str)>,
313) -> Result<Vec<Result<()>>> {
314 let mut results = Vec::new();
315
316 for (localpath, remote_path) in uploads {
317 let result = client.upload_to_cloud(localpath, remote_path).await;
318 results.push(result);
319 }
320
321 Ok(results)
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327
328 #[test]
329 fn test_network_config_default() {
330 let config = NetworkConfig::default();
331 assert_eq!(config.connect_timeout, Duration::from_secs(30));
332 assert_eq!(config.read_timeout, Duration::from_secs(300));
333 assert_eq!(config.max_retries, 3);
334 assert!(config.compression);
335 assert_eq!(config.max_cache_size, 1024);
336 }
337
338 #[test]
339 fn test_network_client_creation() {
340 let client = NetworkClient::new();
341 assert!(client.cloud_provider.is_none());
342
343 let cache_dir = std::env::temp_dir().join("cache");
344 let cache_dir_str = cache_dir.to_string_lossy().into_owned();
345 let client_with_cache = NetworkClient::new().with_cache_dir(&cache_dir_str);
346 assert_eq!(client_with_cache.config.cache_dir, Some(cache_dir_str));
347 }
348
349 #[test]
350 fn test_network_config_custom() {
351 let mut headers = HashMap::new();
352 headers.insert("Authorization".to_string(), "Bearer token".to_string());
353
354 let config = NetworkConfig {
355 connect_timeout: Duration::from_secs(10),
356 read_timeout: Duration::from_secs(60),
357 max_retries: 5,
358 user_agent: "custom-agent/1.0".to_string(),
359 headers,
360 compression: false,
361 cache_dir: Some("/custom/cache".to_string()),
362 max_cache_size: 512,
363 };
364
365 let client = NetworkClient::with_config(config.clone());
366 assert_eq!(client.config.connect_timeout, Duration::from_secs(10));
367 assert_eq!(client.config.max_retries, 5);
368 assert!(!client.config.compression);
369 }
370
371 #[cfg(feature = "async")]
372 #[tokio::test]
373 async fn test_cache_operations() {
374 let temp_dir = tempfile::tempdir().expect("Operation failed");
375 let cache_path = temp_dir.path().to_str().expect("Operation failed");
376
377 let client = NetworkClient::new().with_cache_dir(cache_path);
378
379 let (size, count) = client.get_cache_info().expect("Operation failed");
381 assert_eq!(size, 0);
382 assert_eq!(count, 0);
383
384 client.clear_cache().expect("Operation failed");
386 }
387}