use std::collections::{BTreeMap, HashMap};
use std::io::{Write, self};
use std::os::unix::io::{AsRawFd, RawFd};
use std::sync::{Arc, Mutex};
use std::fs::File;
use std::time::{Duration, Instant};
#[derive(Debug)]
pub struct SendFile {
#[allow(dead_code)]
file: File,
fd: RawFd,
file_size: u64,
pub current_pos: u64,
stats: Arc<std::sync::Mutex<SendFileStats>>,
}
#[derive(Debug, Clone, Default)]
pub struct SendFileStats {
pub bytes_transferred: u64,
pub start_time: Option<Instant>,
pub end_time: Option<Instant>,
pub avg_speed: f64,
pub instant_speed: f64,
pub syscall_count: u64,
pub error_count: u64,
}
impl SendFile {
pub fn new(file: File) -> io::Result<Self> {
let file_size: _ = file.metadata()?.len();
let fd: _ = file.as_raw_fd();
Ok(Self {
file,
fd,
file_size,
current_pos: 0,
stats: Arc::new(Mutex::new(SendFileStats::default())),
})
}
pub fn from_path(path: &str) -> io::Result<Self> {
let file: _ = File::open(path)?;
Self::new(file)
}
pub fn send_to<W: Write + AsRawFd>(&mut self, output: &mut W, max_bytes: usize) -> io::Result<u64> {
let start: _ = Instant::now();
let mut bytes_sent = 0;
let chunk_size: _ = 64 * 1024; let out_fd: _ = output.as_raw_fd();
let bytes_to_send: _ = if max_bytes == 0 {
self.file_size - self.current_pos
} else {
std::cmp::min(max_bytes as u64, self.file_size - self.current_pos)
};
let mut remaining = bytes_to_send;
while remaining > 0 {
let chunk: _ = std::cmp::min(chunk_size as u64, remaining);
match self.sendfile_chunk(out_fd, chunk) {
Ok(sent) => {
bytes_sent += sent;
remaining -= sent;
self.current_pos += sent;
self.update_stats(sent, &start);
}
Err(e) => {
if e.kind() == io::ErrorKind::WouldBlock || e.kind() == io::ErrorKind::Interrupted {
std::thread::sleep(Duration::from_millis(1));
continue;
}
return Err(e);
}
}
if bytes_sent == bytes_to_send {
break;
}
}
{
let mut stats = self.stats.lock().unwrap();
stats.end_time = Some(Instant::now());
if let Some(end) = stats.end_time {
if let Some(start_time) = stats.start_time {
let duration: _ = end.duration_since(start_time);
if duration.as_secs() > 0 {
stats.avg_speed = bytes_sent as f64 / duration.as_secs_f64();
}
}
}
}
Ok(bytes_sent)
}
fn sendfile_chunk(&mut self, out_fd: RawFd, _count: u64) -> io::Result<u64> {
#[cfg(unix)]
{
let mut offset: off_t = self.current_pos as off_t;
let result: _ = unsafe {
libc::sendfile(out_fd, self.fd, 0, &mut offset, std::ptr::null_mut(), 0)
};
if result < 0 {
return Err(io::Error::last_os_error());
}
Ok(result as u64)
}
#[cfg(not(unix))]
{
let mut buffer = vec![0u8; count as usize];
self.file.seek(SeekFrom::Start(self.current_pos))?;
let _read_bytes: _ = self.file.read(&mut buffer)?;
self.file.seek(SeekFrom::Start(self.current_pos))?; Err(io::Error::new(
io::ErrorKind::Other,
"sendfile not supported on this platform",
))
}
}
fn update_stats(&self, bytes: u64, start: &Instant) {
let mut stats = self.stats.lock().unwrap();
if stats.start_time.is_none() {
stats.start_time = Some(*start);
}
stats.bytes_transferred += bytes;
stats.syscall_count += 1;
let elapsed: _ = start.elapsed().as_secs_f64();
if elapsed > 0.0 {
stats.instant_speed = bytes as f64 / elapsed;
}
}
pub fn current_position(&self) -> u64 {
self.current_pos
}
pub fn file_size(&self) -> u64 {
self.file_size
}
pub fn reset(&mut self) {
self.current_pos = 0;
self.stats.lock().unwrap().bytes_transferred = 0;
self.stats.lock().unwrap().syscall_count = 0;
self.stats.lock().unwrap().error_count = 0;
}
pub fn get_stats(&self) -> SendFileStats {
self.stats.lock().unwrap().clone()
}
pub fn progress(&self) -> f64 {
if self.file_size == 0 {
return 0.0;
}
(self.current_pos as f64 / self.file_size as f64) * 100.0
}
pub fn average_speed(&self) -> f64 {
self.stats.lock().unwrap().avg_speed
}
pub fn instant_speed(&self) -> f64 {
self.stats.lock().unwrap().instant_speed
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_sendfile_zero_copy_file_transfer() {
let test_data: _ = b"Hello, World! This is a test file for sendfile.";
let mut cursor = Cursor::new(Vec::new());
cursor.write_all(test_data).unwrap();
println!("SendFile test placeholder");
println!("This test validates the sendfile structure and basic functionality");
println!("Actual sendfile operation requires a real file and network socket");
}
}