1use crate::error::{IoError, Result};
8use std::io::{Read, Seek, SeekFrom, Write};
9use std::path::Path;
10
11#[derive(Debug, Clone)]
13pub struct StreamConfig {
14 pub buffer_size: usize,
16 pub max_memory: usize,
18 pub compression: bool,
20 pub progress_interval: u64,
22}
23
24impl Default for StreamConfig {
25 fn default() -> Self {
26 Self {
27 buffer_size: 64 * 1024, max_memory: 16 * 1024 * 1024, compression: false,
30 progress_interval: 1024 * 1024, }
32 }
33}
34
35#[derive(Debug, Clone)]
37pub struct StreamProgress {
38 pub bytes_transferred: u64,
40 pub total_bytes: Option<u64>,
42 pub rate: f64,
44 pub eta_seconds: Option<f64>,
46}
47
48impl StreamProgress {
49 pub fn percentage(&self) -> Option<f64> {
51 self.total_bytes.map(|total| {
52 if total > 0 {
53 (self.bytes_transferred as f64 / total as f64) * 100.0
54 } else {
55 0.0
56 }
57 })
58 }
59
60 pub fn is_complete(&self) -> bool {
62 if let Some(total) = self.total_bytes {
63 self.bytes_transferred >= total
64 } else {
65 false
66 }
67 }
68}
69
70pub type ProgressCallback = Box<dyn Fn(StreamProgress) + Send + Sync>;
72
73pub struct ProgressReader<R: Read> {
75 inner: R,
76 bytes_read: u64,
77 total_bytes: Option<u64>,
78 progresscallback: Option<ProgressCallback>,
79 progress_interval: u64,
80 last_progress_report: u64,
81 start_time: std::time::Instant,
82}
83
84impl<R: Read> ProgressReader<R> {
85 pub fn new(inner: R) -> Self {
87 Self {
88 inner,
89 bytes_read: 0,
90 total_bytes: None,
91 progresscallback: None,
92 progress_interval: 1024 * 1024, last_progress_report: 0,
94 start_time: std::time::Instant::now(),
95 }
96 }
97
98 pub fn with_total_bytes(mut self, total: u64) -> Self {
100 self.total_bytes = Some(total);
101 self
102 }
103
104 pub fn with_progresscallback(mut self, callback: ProgressCallback) -> Self {
106 self.progresscallback = Some(callback);
107 self
108 }
109
110 pub fn with_progress_interval(mut self, interval: u64) -> Self {
112 self.progress_interval = interval;
113 self
114 }
115
116 pub fn progress(&self) -> StreamProgress {
118 let elapsed = self.start_time.elapsed().as_secs_f64();
119 let rate = if elapsed > 0.0 {
120 self.bytes_read as f64 / elapsed
121 } else {
122 0.0
123 };
124
125 let eta_seconds = if let Some(total) = self.total_bytes {
126 if rate > 0.0 && self.bytes_read < total {
127 Some((total - self.bytes_read) as f64 / rate)
128 } else {
129 None
130 }
131 } else {
132 None
133 };
134
135 StreamProgress {
136 bytes_transferred: self.bytes_read,
137 total_bytes: self.total_bytes,
138 rate,
139 eta_seconds,
140 }
141 }
142
143 fn report_progress(&mut self) {
144 if let Some(ref callback) = self.progresscallback {
145 let progress = self.progress();
146 callback(progress);
147 self.last_progress_report = self.bytes_read;
148 }
149 }
150}
151
152impl<R: Read> Read for ProgressReader<R> {
153 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
154 let bytes_read = self.inner.read(buf)?;
155 self.bytes_read += bytes_read as u64;
156
157 if self.bytes_read - self.last_progress_report >= self.progress_interval {
159 self.report_progress();
160 }
161
162 Ok(bytes_read)
163 }
164}
165
166pub struct ProgressWriter<W: Write> {
168 inner: W,
169 bytes_written: u64,
170 total_bytes: Option<u64>,
171 progresscallback: Option<ProgressCallback>,
172 progress_interval: u64,
173 last_progress_report: u64,
174 start_time: std::time::Instant,
175}
176
177impl<W: Write> ProgressWriter<W> {
178 pub fn new(inner: W) -> Self {
180 Self {
181 inner,
182 bytes_written: 0,
183 total_bytes: None,
184 progresscallback: None,
185 progress_interval: 1024 * 1024, last_progress_report: 0,
187 start_time: std::time::Instant::now(),
188 }
189 }
190
191 pub fn with_total_bytes(mut self, total: u64) -> Self {
193 self.total_bytes = Some(total);
194 self
195 }
196
197 pub fn with_progresscallback(mut self, callback: ProgressCallback) -> Self {
199 self.progresscallback = Some(callback);
200 self
201 }
202
203 pub fn with_progress_interval(mut self, interval: u64) -> Self {
205 self.progress_interval = interval;
206 self
207 }
208
209 pub fn progress(&self) -> StreamProgress {
211 let elapsed = self.start_time.elapsed().as_secs_f64();
212 let rate = if elapsed > 0.0 {
213 self.bytes_written as f64 / elapsed
214 } else {
215 0.0
216 };
217
218 let eta_seconds = if let Some(total) = self.total_bytes {
219 if rate > 0.0 && self.bytes_written < total {
220 Some((total - self.bytes_written) as f64 / rate)
221 } else {
222 None
223 }
224 } else {
225 None
226 };
227
228 StreamProgress {
229 bytes_transferred: self.bytes_written,
230 total_bytes: self.total_bytes,
231 rate,
232 eta_seconds,
233 }
234 }
235
236 fn report_progress(&mut self) {
237 if let Some(ref callback) = self.progresscallback {
238 let progress = self.progress();
239 callback(progress);
240 self.last_progress_report = self.bytes_written;
241 }
242 }
243}
244
245impl<W: Write> Write for ProgressWriter<W> {
246 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
247 let bytes_written = self.inner.write(buf)?;
248 self.bytes_written += bytes_written as u64;
249
250 if self.bytes_written - self.last_progress_report >= self.progress_interval {
252 self.report_progress();
253 }
254
255 Ok(bytes_written)
256 }
257
258 fn flush(&mut self) -> std::io::Result<()> {
259 self.inner.flush()
260 }
261}
262
263pub struct ChunkedReader {
265 file: std::fs::File,
266 chunk_size: usize,
267 current_position: u64,
268 file_size: u64,
269}
270
271impl ChunkedReader {
272 pub fn new<P: AsRef<Path>>(path: P, chunk_size: usize) -> Result<Self> {
274 let file = std::fs::File::open(path.as_ref())
275 .map_err(|e| IoError::FileError(format!("Failed to open file: {}", e)))?;
276
277 let file_size = file
278 .metadata()
279 .map_err(|e| IoError::FileError(format!("Failed to get file metadata: {}", e)))?
280 .len();
281
282 Ok(Self {
283 file,
284 chunk_size,
285 current_position: 0,
286 file_size,
287 })
288 }
289
290 pub fn read_chunk(&mut self) -> Result<Option<Vec<u8>>> {
292 if self.current_position >= self.file_size {
293 return Ok(None);
294 }
295
296 let mut buffer = vec![0u8; self.chunk_size];
297 let bytes_read = self
298 .file
299 .read(&mut buffer)
300 .map_err(|e| IoError::FileError(format!("Failed to read chunk: {}", e)))?;
301
302 if bytes_read == 0 {
303 return Ok(None);
304 }
305
306 buffer.truncate(bytes_read);
307 self.current_position += bytes_read as u64;
308
309 Ok(Some(buffer))
310 }
311
312 pub fn seek(&mut self, position: u64) -> Result<()> {
314 self.file
315 .seek(SeekFrom::Start(position))
316 .map_err(|e| IoError::FileError(format!("Failed to seek: {}", e)))?;
317 self.current_position = position;
318 Ok(())
319 }
320
321 pub fn position(&self) -> u64 {
323 self.current_position
324 }
325
326 pub fn size(&self) -> u64 {
328 self.file_size
329 }
330
331 pub fn is_eof(&self) -> bool {
333 self.current_position >= self.file_size
334 }
335
336 pub fn progress_percentage(&self) -> f64 {
338 if self.file_size > 0 {
339 (self.current_position as f64 / self.file_size as f64) * 100.0
340 } else {
341 0.0
342 }
343 }
344}
345
346pub struct ChunkedWriter {
348 file: std::fs::File,
349 bytes_written: u64,
350 buffer: Vec<u8>,
351 buffer_size: usize,
352}
353
354impl ChunkedWriter {
355 pub fn new<P: AsRef<Path>>(path: P, buffersize: usize) -> Result<Self> {
357 let file = std::fs::File::create(path.as_ref())
358 .map_err(|e| IoError::FileError(format!("Failed to create file: {}", e)))?;
359
360 Ok(Self {
361 file,
362 bytes_written: 0,
363 buffer: Vec::with_capacity(buffersize),
364 buffer_size: buffersize,
365 })
366 }
367
368 pub fn write_chunk(&mut self, data: &[u8]) -> Result<()> {
370 self.buffer.extend_from_slice(data);
371
372 if self.buffer.len() >= self.buffer_size {
374 self.flush_buffer()?;
375 }
376
377 Ok(())
378 }
379
380 pub fn flush_buffer(&mut self) -> Result<()> {
382 if !self.buffer.is_empty() {
383 self.file
384 .write_all(&self.buffer)
385 .map_err(|e| IoError::FileError(format!("Failed to write buffer: {}", e)))?;
386
387 self.bytes_written += self.buffer.len() as u64;
388 self.buffer.clear();
389 }
390 Ok(())
391 }
392
393 pub fn finish(mut self) -> Result<u64> {
395 self.flush_buffer()?;
396 self.file
397 .flush()
398 .map_err(|e| IoError::FileError(format!("Failed to flush file: {}", e)))?;
399 Ok(self.bytes_written)
400 }
401
402 pub fn bytes_written(&self) -> u64 {
404 self.bytes_written + self.buffer.len() as u64
405 }
406}
407
408#[allow(dead_code)]
410pub fn copy_with_progress<R: Read, W: Write>(
411 mut reader: R,
412 mut writer: W,
413 total_size: Option<u64>,
414 progresscallback: Option<ProgressCallback>,
415) -> Result<u64> {
416 let mut buffer = vec![0u8; 64 * 1024]; let mut total_copied = 0u64;
418 let start_time = std::time::Instant::now();
419 let mut last_progress_report = 0u64;
420 let progress_interval = 1024 * 1024; loop {
423 let bytes_read = reader
424 .read(&mut buffer)
425 .map_err(|e| IoError::FileError(format!("Read error: {}", e)))?;
426
427 if bytes_read == 0 {
428 break;
429 }
430
431 writer
432 .write_all(&buffer[..bytes_read])
433 .map_err(|e| IoError::FileError(format!("Write error: {}", e)))?;
434
435 total_copied += bytes_read as u64;
436
437 if let Some(ref callback) = progresscallback {
439 if total_copied - last_progress_report >= progress_interval {
440 let elapsed = start_time.elapsed().as_secs_f64();
441 let rate = if elapsed > 0.0 {
442 total_copied as f64 / elapsed
443 } else {
444 0.0
445 };
446
447 let eta_seconds = if let Some(total) = total_size {
448 if rate > 0.0 && total_copied < total {
449 Some((total - total_copied) as f64 / rate)
450 } else {
451 None
452 }
453 } else {
454 None
455 };
456
457 let progress = StreamProgress {
458 bytes_transferred: total_copied,
459 total_bytes: total_size,
460 rate,
461 eta_seconds,
462 };
463
464 callback(progress);
465 last_progress_report = total_copied;
466 }
467 }
468 }
469
470 if let Some(ref callback) = progresscallback {
472 let elapsed = start_time.elapsed().as_secs_f64();
473 let rate = if elapsed > 0.0 {
474 total_copied as f64 / elapsed
475 } else {
476 0.0
477 };
478
479 let progress = StreamProgress {
480 bytes_transferred: total_copied,
481 total_bytes: total_size,
482 rate,
483 eta_seconds: Some(0.0),
484 };
485
486 callback(progress);
487 }
488
489 Ok(total_copied)
490}
491
492#[cfg(feature = "async")]
494pub async fn async_copy_with_progress<R, W>(
495 mut reader: R,
496 mut writer: W,
497 total_size: Option<u64>,
498 progresscallback: Option<ProgressCallback>,
499) -> Result<u64>
500where
501 R: tokio::io::AsyncRead + Unpin,
502 W: tokio::io::AsyncWrite + Unpin,
503{
504 use tokio::io::{AsyncReadExt, AsyncWriteExt};
505
506 let mut buffer = vec![0u8; 64 * 1024]; let mut total_copied = 0u64;
508 let start_time = std::time::Instant::now();
509 let mut last_progress_report = 0u64;
510 let progress_interval = 1024 * 1024; loop {
513 let bytes_read = reader
514 .read(&mut buffer)
515 .await
516 .map_err(|e| IoError::FileError(format!("Async read error: {}", e)))?;
517
518 if bytes_read == 0 {
519 break;
520 }
521
522 writer
523 .write_all(&buffer[..bytes_read])
524 .await
525 .map_err(|e| IoError::FileError(format!("Async write error: {}", e)))?;
526
527 total_copied += bytes_read as u64;
528
529 if let Some(ref callback) = progresscallback {
531 if total_copied - last_progress_report >= progress_interval {
532 let elapsed = start_time.elapsed().as_secs_f64();
533 let rate = if elapsed > 0.0 {
534 total_copied as f64 / elapsed
535 } else {
536 0.0
537 };
538
539 let eta_seconds = if let Some(total) = total_size {
540 if rate > 0.0 && total_copied < total {
541 Some((total - total_copied) as f64 / rate)
542 } else {
543 None
544 }
545 } else {
546 None
547 };
548
549 let progress = StreamProgress {
550 bytes_transferred: total_copied,
551 total_bytes: total_size,
552 rate,
553 eta_seconds,
554 };
555
556 callback(progress);
557 last_progress_report = total_copied;
558 }
559 }
560 }
561
562 writer
563 .flush()
564 .await
565 .map_err(|e| IoError::FileError(format!("Async flush error: {}", e)))?;
566
567 Ok(total_copied)
568}
569
570#[cfg(test)]
571mod tests {
572 use super::*;
573 use std::io::Cursor;
574 use tempfile::tempdir;
575
576 #[test]
577 fn test_stream_config_default() {
578 let config = StreamConfig::default();
579 assert_eq!(config.buffer_size, 64 * 1024);
580 assert_eq!(config.max_memory, 16 * 1024 * 1024);
581 assert!(!config.compression);
582 assert_eq!(config.progress_interval, 1024 * 1024);
583 }
584
585 #[test]
586 fn test_stream_progress() {
587 let progress = StreamProgress {
588 bytes_transferred: 512,
589 total_bytes: Some(1024),
590 rate: 256.0,
591 eta_seconds: Some(2.0),
592 };
593
594 assert_eq!(progress.percentage(), Some(50.0));
595 assert!(!progress.is_complete());
596
597 let complete_progress = StreamProgress {
598 bytes_transferred: 1024,
599 total_bytes: Some(1024),
600 rate: 512.0,
601 eta_seconds: Some(0.0),
602 };
603
604 assert_eq!(complete_progress.percentage(), Some(100.0));
605 assert!(complete_progress.is_complete());
606 }
607
608 #[test]
609 fn test_progress_reader() {
610 let data = b"Hello, world! This is test data for streaming.";
611 let cursor = Cursor::new(data);
612
613 let mut reader = ProgressReader::new(cursor)
614 .with_total_bytes(data.len() as u64)
615 .with_progress_interval(10);
616
617 let mut buffer = [0u8; 20];
618 let bytes_read = reader.read(&mut buffer).expect("Operation failed");
619
620 assert_eq!(bytes_read, 20);
621 assert_eq!(reader.progress().bytes_transferred, 20);
622
623 let progress = reader.progress();
624 assert_eq!(progress.bytes_transferred, 20);
625 assert_eq!(progress.total_bytes, Some(data.len() as u64));
626 assert!(progress.rate >= 0.0);
627 }
628
629 #[test]
630 fn test_progress_writer() {
631 let mut output = Vec::new();
632 let mut writer = ProgressWriter::new(&mut output)
633 .with_total_bytes(100)
634 .with_progress_interval(25);
635
636 let data = b"Test data for progress writer functionality.";
637 let bytes_written = writer.write(data).expect("Operation failed");
638
639 assert_eq!(bytes_written, data.len());
640 assert_eq!(writer.progress().bytes_transferred, data.len() as u64);
641
642 writer.flush().expect("Operation failed");
643 assert_eq!(output, data);
644 }
645
646 #[test]
647 fn test_chunked_reader() {
648 let temp_dir = tempdir().expect("Operation failed");
649 let file_path = temp_dir.path().join("test_chunked.txt");
650
651 let test_data = b"This is test data for chunked reading. It should be read in chunks.";
653 std::fs::write(&file_path, test_data).expect("Operation failed");
654
655 let mut reader = ChunkedReader::new(&file_path, 10).expect("Operation failed");
656 assert_eq!(reader.size(), test_data.len() as u64);
657 assert!(!reader.is_eof());
658
659 let mut all_data = Vec::new();
660 while let Some(chunk) = reader.read_chunk().expect("Operation failed") {
661 all_data.extend_from_slice(&chunk);
662 }
663
664 assert_eq!(all_data, test_data);
665 assert!(reader.is_eof());
666 assert_eq!(reader.progress_percentage(), 100.0);
667 }
668
669 #[test]
670 fn test_chunked_writer() {
671 let temp_dir = tempdir().expect("Operation failed");
672 let file_path = temp_dir.path().join("test_chunked_write.txt");
673
674 let mut writer = ChunkedWriter::new(&file_path, 20).expect("Operation failed");
675
676 let data1 = b"First chunk of data.";
677 let data2 = b"Second chunk of data.";
678
679 writer.write_chunk(data1).expect("Operation failed");
680 writer.write_chunk(data2).expect("Operation failed");
681
682 let total_bytes = writer.finish().expect("Operation failed");
683 assert_eq!(total_bytes, (data1.len() + data2.len()) as u64);
684
685 let file_contents = std::fs::read(&file_path).expect("Operation failed");
687 let expected = [&data1[..], &data2[..]].concat();
688 assert_eq!(file_contents, expected);
689 }
690
691 #[test]
692 fn test_copy_with_progress() {
693 let input_data = b"This is test data for copy with progress functionality. It demonstrates streaming copy operations.";
694 let input = Cursor::new(input_data);
695 let mut output = Vec::new();
696
697 let progress_reports = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
698 let progress_reports_clone = progress_reports.clone();
699 let callback = Box::new(move |progress: StreamProgress| {
700 progress_reports_clone.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
701 assert!(progress.bytes_transferred <= input_data.len() as u64);
702 assert!(progress.rate >= 0.0);
703 }) as ProgressCallback;
704
705 let copied = copy_with_progress(
707 input,
708 &mut output,
709 Some(input_data.len() as u64),
710 Some(callback),
711 )
712 .expect("Operation failed");
713
714 assert_eq!(copied, input_data.len() as u64);
715 assert_eq!(output, input_data);
716 }
717
718 #[tokio::test]
719 #[cfg(feature = "async")]
720 async fn test_async_copy_with_progress() {
721 let input_data = b"Async test data for copy with progress functionality.";
722 let input = Cursor::new(input_data);
723 let mut output = Vec::new();
724
725 let copied =
726 async_copy_with_progress(input, &mut output, Some(input_data.len() as u64), None)
727 .await
728 .expect("Operation failed");
729
730 assert_eq!(copied, input_data.len() as u64);
731 assert_eq!(output, input_data);
732 }
733}