use crate::error::{IoError, Result};
use std::collections::HashMap;
use std::path::Path;
use std::time::Duration;
pub mod cloud;
pub mod http;
pub mod streaming;
#[derive(Debug, Clone)]
pub struct NetworkConfig {
pub connect_timeout: Duration,
pub read_timeout: Duration,
pub max_retries: u32,
pub user_agent: String,
pub headers: HashMap<String, String>,
pub compression: bool,
pub cache_dir: Option<String>,
pub max_cache_size: u64,
}
impl Default for NetworkConfig {
fn default() -> Self {
let mut headers = HashMap::new();
headers.insert("Accept".to_string(), "*/*".to_string());
Self {
connect_timeout: Duration::from_secs(30),
read_timeout: Duration::from_secs(300),
max_retries: 3,
user_agent: "scirs2-io/0.1.0".to_string(),
headers,
compression: true,
cache_dir: None,
max_cache_size: 1024, }
}
}
#[derive(Debug)]
pub struct NetworkClient {
config: NetworkConfig,
#[cfg(feature = "reqwest")]
http_client: Option<reqwest::Client>,
cloud_provider: Option<cloud::CloudProvider>,
}
impl Default for NetworkClient {
fn default() -> Self {
Self::new()
}
}
impl NetworkClient {
pub fn new() -> Self {
Self {
config: NetworkConfig::default(),
#[cfg(feature = "reqwest")]
http_client: None,
cloud_provider: None,
}
}
pub fn with_config(config: NetworkConfig) -> Self {
Self {
config,
#[cfg(feature = "reqwest")]
http_client: None,
cloud_provider: None,
}
}
pub fn with_cloud_provider(mut self, provider: cloud::CloudProvider) -> Self {
self.cloud_provider = Some(provider);
self
}
pub fn with_cache_dir<P: AsRef<Path>>(mut self, cache_dir: P) -> Self {
self.config.cache_dir = Some(cache_dir.as_ref().to_string_lossy().to_string());
self
}
#[cfg(feature = "reqwest")]
pub async fn download<P: AsRef<Path>>(&self, url: &str, localpath: P) -> Result<()> {
if let Some(_client) = &self.http_client {
let mut http_client = http::HttpClient::new(self.config.clone());
http_client.init()?;
http_client.download(url, localpath).await
} else {
Err(IoError::ConfigError(
"HTTP client not configured".to_string(),
))
}
}
#[cfg(feature = "reqwest")]
pub async fn upload<P: AsRef<Path>>(&self, localpath: P, url: &str) -> Result<()> {
if let Some(_client) = &self.http_client {
let mut http_client = http::HttpClient::new(self.config.clone());
http_client.init()?;
http_client.upload(localpath, url).await
} else {
Err(IoError::ConfigError(
"HTTP client not configured".to_string(),
))
}
}
pub async fn upload_to_cloud<P: AsRef<Path>>(
&self,
localpath: P,
remote_path: &str,
) -> Result<()> {
if let Some(ref provider) = self.cloud_provider {
provider.upload_file(localpath, remote_path).await
} else {
Err(IoError::ConfigError(
"No cloud provider configured".to_string(),
))
}
}
pub async fn download_from_cloud<P: AsRef<Path>>(
&self,
remote_path: &str,
localpath: P,
) -> Result<()> {
if let Some(ref provider) = self.cloud_provider {
provider.download_file(remote_path, localpath).await
} else {
Err(IoError::ConfigError(
"No cloud provider configured".to_string(),
))
}
}
pub async fn list_cloud_files(&self, path: &str) -> Result<Vec<String>> {
if let Some(ref provider) = self.cloud_provider {
provider.list_files(path).await
} else {
Err(IoError::ConfigError(
"No cloud provider configured".to_string(),
))
}
}
pub async fn cloud_file_exists(&self, path: &str) -> Result<bool> {
if let Some(ref provider) = self.cloud_provider {
provider.file_exists(path).await
} else {
Err(IoError::ConfigError(
"No cloud provider configured".to_string(),
))
}
}
pub async fn get_cloud_file_metadata(&self, path: &str) -> Result<cloud::FileMetadata> {
if let Some(ref provider) = self.cloud_provider {
provider.get_metadata(path).await
} else {
Err(IoError::ConfigError(
"No cloud provider configured".to_string(),
))
}
}
pub fn clear_cache(&self) -> Result<()> {
if let Some(ref cache_dir) = self.config.cache_dir {
let cache_path = Path::new(cache_dir);
if cache_path.exists() {
std::fs::remove_dir_all(cache_path)
.map_err(|e| IoError::FileError(format!("Failed to clear cache: {}", e)))?;
std::fs::create_dir_all(cache_path).map_err(|e| {
IoError::FileError(format!("Failed to recreate cache dir: {}", e))
})?;
}
}
Ok(())
}
pub fn get_cache_info(&self) -> Result<(u64, u64)> {
if let Some(ref cache_dir) = self.config.cache_dir {
let cache_path = Path::new(cache_dir);
if cache_path.exists() {
let mut total_size = 0u64;
let mut file_count = 0u64;
for entry in (std::fs::read_dir(cache_path)
.map_err(|e| IoError::FileError(format!("Failed to read cache dir: {}", e)))?)
.flatten()
{
if let Ok(metadata) = entry.metadata() {
if metadata.is_file() {
total_size += metadata.len();
file_count += 1;
}
}
}
return Ok((total_size, file_count));
}
}
Ok((0, 0))
}
}
#[cfg(feature = "reqwest")]
pub async fn download_file<P: AsRef<Path>>(url: &str, localpath: P) -> Result<()> {
let client = NetworkClient::new();
client.download(url, localpath).await
}
#[cfg(feature = "reqwest")]
pub async fn upload_file<P: AsRef<Path>>(localpath: P, url: &str) -> Result<()> {
let client = NetworkClient::new();
client.upload(localpath, url).await
}
#[cfg(feature = "reqwest")]
pub async fn download_with_cache<P: AsRef<Path>>(
url: &str,
localpath: P,
cache_dir: Option<&str>,
) -> Result<()> {
let mut client = NetworkClient::new();
if let Some(cache) = cache_dir {
client = client.with_cache_dir(cache);
}
client.download(url, localpath).await
}
#[allow(dead_code)]
pub fn create_cloud_client(provider: cloud::CloudProvider) -> NetworkClient {
NetworkClient::new().with_cloud_provider(provider)
}
#[cfg(feature = "reqwest")]
pub async fn batch_download(downloads: Vec<(&str, &str)>) -> Result<Vec<Result<()>>> {
let client = NetworkClient::new();
let mut results = Vec::new();
for (url, localpath) in downloads {
let result = client.download(url, localpath).await;
results.push(result);
}
Ok(results)
}
pub async fn batch_upload_to_cloud(
client: &NetworkClient,
uploads: Vec<(&str, &str)>,
) -> Result<Vec<Result<()>>> {
let mut results = Vec::new();
for (localpath, remote_path) in uploads {
let result = client.upload_to_cloud(localpath, remote_path).await;
results.push(result);
}
Ok(results)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_network_config_default() {
let config = NetworkConfig::default();
assert_eq!(config.connect_timeout, Duration::from_secs(30));
assert_eq!(config.read_timeout, Duration::from_secs(300));
assert_eq!(config.max_retries, 3);
assert!(config.compression);
assert_eq!(config.max_cache_size, 1024);
}
#[test]
fn test_network_client_creation() {
let client = NetworkClient::new();
assert!(client.cloud_provider.is_none());
let cache_dir = std::env::temp_dir().join("cache");
let cache_dir_str = cache_dir.to_string_lossy().into_owned();
let client_with_cache = NetworkClient::new().with_cache_dir(&cache_dir_str);
assert_eq!(client_with_cache.config.cache_dir, Some(cache_dir_str));
}
#[test]
fn test_network_config_custom() {
let mut headers = HashMap::new();
headers.insert("Authorization".to_string(), "Bearer token".to_string());
let config = NetworkConfig {
connect_timeout: Duration::from_secs(10),
read_timeout: Duration::from_secs(60),
max_retries: 5,
user_agent: "custom-agent/1.0".to_string(),
headers,
compression: false,
cache_dir: Some("/custom/cache".to_string()),
max_cache_size: 512,
};
let client = NetworkClient::with_config(config.clone());
assert_eq!(client.config.connect_timeout, Duration::from_secs(10));
assert_eq!(client.config.max_retries, 5);
assert!(!client.config.compression);
}
#[cfg(feature = "async")]
#[tokio::test]
async fn test_cache_operations() {
let temp_dir = tempfile::tempdir().expect("Operation failed");
let cache_path = temp_dir.path().to_str().expect("Operation failed");
let client = NetworkClient::new().with_cache_dir(cache_path);
let (size, count) = client.get_cache_info().expect("Operation failed");
assert_eq!(size, 0);
assert_eq!(count, 0);
client.clear_cache().expect("Operation failed");
}
}