use crate::callback::{c_callback, with_libstorage_lock, CallbackFuture};
use crate::download::session::download_init_sync;
use crate::download::types::{DownloadOptions, DownloadResult, DownloadStreamOptions};
use crate::error::{Result, StorageError};
use crate::ffi::{free_c_string, storage_download_stream, string_to_c_string};
use crate::node::lifecycle::StorageNode;
use libc::c_void;
use std::io::Write;
use std::sync::{Arc, Mutex};
pub async fn download_stream(
node: &StorageNode,
cid: &str,
options: DownloadStreamOptions,
) -> Result<DownloadResult> {
if cid.is_empty() {
return Err(StorageError::invalid_parameter(
"cid",
"CID cannot be empty",
));
}
options.validate()?;
let start_time = std::time::Instant::now();
let chunk_size = options.chunk_size.unwrap_or(1024 * 1024);
let total_bytes = Arc::new(Mutex::new(0usize));
let total_bytes_clone = total_bytes.clone();
let file_handle = if let Some(ref filepath) = options.filepath {
match std::fs::File::create(filepath) {
Ok(file) => Some(Arc::new(Mutex::new(Some(file)))),
Err(e) => {
return Err(StorageError::Io(e));
}
}
} else {
None
};
let future = CallbackFuture::new();
let context = future.context.clone();
let file_handle_clone = file_handle.clone();
let (tx, rx) = std::sync::mpsc::channel::<Vec<u8>>();
let tx_clone = tx.clone();
let writer_task = if options.writer.is_some() {
let mut writer = options.writer.unwrap();
Some(std::thread::spawn(move || {
while let Ok(chunk) = rx.recv() {
if let Err(e) = writer.write_all(&chunk) {
eprintln!("Failed to write to writer: {}", e);
break;
}
}
}))
} else {
None
};
context.set_progress_callback(move |_len, chunk| {
if let Some(chunk_bytes) = chunk {
let mut total = total_bytes_clone.lock().unwrap();
*total += chunk_bytes.len();
if let Some(ref file_handle) = file_handle_clone {
if let Some(ref mut file) = file_handle.lock().unwrap().as_mut() {
if let Err(e) = file.write_all(chunk_bytes) {
eprintln!("Failed to write to file: {}", e);
}
}
}
if tx_clone.send(chunk_bytes.to_vec()).is_err() {
eprintln!("Failed to send data to writer thread");
}
}
});
let download_options = DownloadOptions::new(cid)
.chunk_size(chunk_size)
.timeout(options.timeout.unwrap_or(300))
.verify(options.verify);
download_init_sync(node, cid, &download_options)?;
let context_ptr = future.context_ptr() as *mut c_void;
let filepath_str = options
.filepath
.as_ref()
.and_then(|p| p.to_str())
.unwrap_or("");
let result = with_libstorage_lock(|| unsafe {
node.with_ctx(|ctx| {
let c_cid = string_to_c_string(cid);
let c_filepath = string_to_c_string(filepath_str);
let result = storage_download_stream(
ctx as *mut _,
c_cid,
chunk_size,
options.local,
c_filepath,
Some(c_callback),
context_ptr,
);
free_c_string(c_cid);
if !c_filepath.is_null() {
free_c_string(c_filepath);
}
result
})
});
if result != 0 {
return Err(StorageError::download_error("Failed to download stream"));
}
future.await?;
drop(tx);
if let Some(handle) = writer_task {
if let Err(e) = handle.join() {
eprintln!("Writer thread failed: {:?}", e);
}
}
if let Some(file_handle) = file_handle {
if let Some(ref mut file) = file_handle.lock().unwrap().as_mut() {
if let Err(e) = file.flush() {
eprintln!("Failed to flush file: {}", e);
}
}
}
let duration = start_time.elapsed();
let bytes_downloaded = *total_bytes.lock().unwrap();
let mut result = DownloadResult::new(cid.to_string(), bytes_downloaded)
.duration_ms(duration.as_millis() as u64)
.verified(options.verify);
if let Some(filepath) = options.filepath {
result = result.filepath(filepath);
}
Ok(result)
}
pub async fn download_to_file(
node: &StorageNode,
cid: &str,
filepath: &std::path::Path,
) -> Result<DownloadResult> {
let options = DownloadStreamOptions::new(cid)
.filepath(filepath.to_path_buf())
.local(true);
download_stream(node, cid, options).await
}
pub async fn download_to_writer<W>(
node: &StorageNode,
cid: &str,
writer: W,
) -> Result<DownloadResult>
where
W: Write + Send + 'static,
{
let options = DownloadStreamOptions::new(cid).writer(writer);
download_stream(node, cid, options).await
}