pub mod bits;
pub mod cookies;
pub mod error;
pub mod http_client;
pub mod websocket;
pub use bits::BitsDownloadManager;
pub use cookies::WindowsCookieStorage;
pub use error::*;
pub use websocket::{WindowsWebSocket, WindowsWebSocketBuilder};
use crate::backend::types::{BackendRequest, BackendResponse};
use crate::{Error, Result};
use std::time::Duration;
use url::Url;
#[derive(Clone)]
pub struct WindowsBackend {
user_agent: String,
cookie_jar: Option<crate::CookieJar>,
cookie_storage: Option<cookies::WindowsCookieStorage>,
default_headers: Option<http::HeaderMap>,
timeout: Option<Duration>,
bits_manager: Option<std::sync::Arc<BitsDownloadManager>>,
}
impl WindowsBackend {
pub fn new() -> Result<Self> {
let bits_manager = BitsDownloadManager::new().ok().map(std::sync::Arc::new);
Ok(Self {
user_agent: "frakt/1.0".to_string(),
cookie_jar: None,
cookie_storage: None,
default_headers: None,
timeout: None,
bits_manager,
})
}
pub fn with_config(config: crate::backend::BackendConfig) -> Result<Self> {
let bits_manager = BitsDownloadManager::new().ok().map(std::sync::Arc::new);
let user_agent = config.user_agent.unwrap_or_else(|| "frakt/1.0".to_string());
let cookie_storage = if config.use_cookies.unwrap_or(false) {
cookies::WindowsCookieStorage::new().ok()
} else {
None
};
Ok(Self {
user_agent,
cookie_jar: config.cookie_jar,
cookie_storage,
default_headers: config.default_headers,
timeout: config.timeout,
bits_manager,
})
}
pub async fn execute(&self, request: BackendRequest) -> Result<BackendResponse> {
http_client::execute_winhttp_request(
request,
&self.user_agent,
&self.default_headers,
&self.timeout,
&self.cookie_storage,
)
.await
}
pub async fn execute_background_download(
&self,
url: Url,
file_path: std::path::PathBuf,
session_identifier: Option<String>,
progress_callback: Option<Box<dyn Fn(u64, Option<u64>) + Send + Sync + 'static>>,
) -> Result<crate::client::download::DownloadResponse> {
if let Some(ref bits_manager) = self.bits_manager {
match bits_manager
.start_background_download(url.clone(), file_path.clone(), session_identifier, None)
.await
{
Ok(response) => return Ok(response),
Err(e) => {
eprintln!(
"BITS download failed, falling back to regular download: {}",
e
);
}
}
}
let request = BackendRequest {
method: http::Method::GET,
url,
headers: http::HeaderMap::new(),
body: None,
progress_callback: progress_callback.map(|cb| {
std::sync::Arc::new(cb)
as std::sync::Arc<dyn Fn(u64, Option<u64>) + Send + Sync + 'static>
}),
};
let response = self.execute(request).await?;
let mut file = tokio::fs::File::create(&file_path)
.await
.map_err(|e| Error::Internal(format!("Failed to create file: {}", e)))?;
let mut receiver = response.body_receiver;
let mut bytes_downloaded = 0u64;
while let Some(chunk_result) = receiver.recv().await {
let chunk = chunk_result?;
bytes_downloaded += chunk.len() as u64;
tokio::io::AsyncWriteExt::write_all(&mut file, &chunk)
.await
.map_err(|e| Error::Internal(format!("Failed to write to file: {}", e)))?;
}
Ok(crate::client::download::DownloadResponse {
file_path,
bytes_downloaded,
})
}
pub fn cookie_jar(&self) -> Option<&crate::CookieJar> {
self.cookie_jar.as_ref()
}
}