files_sdk/
progress.rs

1//! Progress tracking for file operations
2//!
3//! This module provides traits and types for tracking progress during
4//! file uploads and downloads with the streaming API.
5//!
6//! # Overview
7//!
8//! The progress tracking system consists of:
9//! - [`Progress`] - Immutable snapshot of current progress
10//! - [`ProgressCallback`] - Trait for receiving progress updates
11//! - [`PrintProgressCallback`] - Built-in stdout progress logger
12//!
13//! # Usage
14//!
15//! Progress callbacks are optional and can be passed to streaming methods
16//! like [`FileHandler::upload_stream()`](crate::files::FileHandler::upload_stream)
17//! and [`FileHandler::download_stream()`](crate::files::FileHandler::download_stream).
18//!
19//! ## Basic Example
20//!
21//! ```rust
22//! use files_sdk::progress::{Progress, ProgressCallback};
23//! use std::sync::Arc;
24//!
25//! // Simple progress tracker
26//! struct SimpleTracker;
27//!
28//! impl ProgressCallback for SimpleTracker {
29//!     fn on_progress(&self, progress: &Progress) {
30//!         if let Some(pct) = progress.percentage() {
31//!             println!("Upload: {:.1}%", pct);
32//!         }
33//!     }
34//! }
35//!
36//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
37//! # use files_sdk::{FilesClient, files::FileHandler};
38//! # let client = FilesClient::builder().api_key("key").build()?;
39//! let handler = FileHandler::new(client);
40//! let callback = Arc::new(SimpleTracker);
41//!
42//! // Use with streaming upload
43//! let file = tokio::fs::File::open("large-file.bin").await?;
44//! let size = file.metadata().await?.len() as i64;
45//! handler.upload_stream("/remote/path.bin", file, Some(size), Some(callback)).await?;
46//! # Ok(())
47//! # }
48//! ```
49//!
50//! ## Advanced Example with State
51//!
52//! ```rust
53//! use files_sdk::progress::{Progress, ProgressCallback};
54//! use std::sync::{Arc, Mutex};
55//! use std::time::Instant;
56//!
57//! // Progress tracker with transfer rate calculation
58//! struct RateTracker {
59//!     start: Instant,
60//!     last_bytes: Arc<Mutex<u64>>,
61//! }
62//!
63//! impl RateTracker {
64//!     fn new() -> Self {
65//!         Self {
66//!             start: Instant::now(),
67//!             last_bytes: Arc::new(Mutex::new(0)),
68//!         }
69//!     }
70//! }
71//!
72//! impl ProgressCallback for RateTracker {
73//!     fn on_progress(&self, progress: &Progress) {
74//!         let elapsed = self.start.elapsed().as_secs_f64();
75//!         let rate = progress.bytes_transferred as f64 / elapsed / 1024.0 / 1024.0;
76//!
77//!         if let Some(pct) = progress.percentage() {
78//!             println!("Progress: {:.1}% @ {:.2} MB/s", pct, rate);
79//!         }
80//!     }
81//! }
82//! ```
83
84/// Progress information for a file operation
85#[derive(Debug, Clone)]
86pub struct Progress {
87    /// Bytes transferred so far
88    pub bytes_transferred: u64,
89    /// Total bytes to transfer (if known)
90    pub total_bytes: Option<u64>,
91}
92
93impl Progress {
94    /// Create a new Progress instance
95    pub fn new(bytes_transferred: u64, total_bytes: Option<u64>) -> Self {
96        Self {
97            bytes_transferred,
98            total_bytes,
99        }
100    }
101
102    /// Calculate progress percentage (0-100)
103    ///
104    /// Returns None if total_bytes is unknown
105    pub fn percentage(&self) -> Option<f64> {
106        self.total_bytes.map(|total| {
107            if total == 0 {
108                100.0
109            } else {
110                (self.bytes_transferred as f64 / total as f64) * 100.0
111            }
112        })
113    }
114}
115
116/// Trait for receiving progress updates during file operations
117///
118/// Implement this trait to receive callbacks as data is transferred.
119///
120/// # Examples
121///
122/// ```rust
123/// use files_sdk::progress::{Progress, ProgressCallback};
124///
125/// struct MyProgressTracker;
126///
127/// impl ProgressCallback for MyProgressTracker {
128///     fn on_progress(&self, progress: &Progress) {
129///         if let Some(pct) = progress.percentage() {
130///             println!("Progress: {:.1}%", pct);
131///         } else {
132///             println!("Transferred: {} bytes", progress.bytes_transferred);
133///         }
134///     }
135/// }
136/// ```
137pub trait ProgressCallback: Send + Sync {
138    /// Called when progress is made during a file operation
139    ///
140    /// # Arguments
141    ///
142    /// * `progress` - Current progress information
143    fn on_progress(&self, progress: &Progress);
144}
145
146/// A simple progress callback that prints to stdout
147#[derive(Debug)]
148pub struct PrintProgressCallback;
149
150impl ProgressCallback for PrintProgressCallback {
151    fn on_progress(&self, progress: &Progress) {
152        if let Some(pct) = progress.percentage() {
153            println!(
154                "Progress: {:.1}% ({} / {} bytes)",
155                pct,
156                progress.bytes_transferred,
157                progress.total_bytes.unwrap_or(0)
158            );
159        } else {
160            println!("Transferred: {} bytes", progress.bytes_transferred);
161        }
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168
169    #[test]
170    fn test_progress_percentage() {
171        let progress = Progress::new(50, Some(100));
172        assert_eq!(progress.percentage(), Some(50.0));
173
174        let progress = Progress::new(0, Some(100));
175        assert_eq!(progress.percentage(), Some(0.0));
176
177        let progress = Progress::new(100, Some(100));
178        assert_eq!(progress.percentage(), Some(100.0));
179
180        let progress = Progress::new(50, None);
181        assert_eq!(progress.percentage(), None);
182    }
183
184    #[test]
185    fn test_progress_percentage_zero_total() {
186        let progress = Progress::new(0, Some(0));
187        assert_eq!(progress.percentage(), Some(100.0));
188    }
189
190    #[test]
191    fn test_print_progress_callback() {
192        let callback = PrintProgressCallback;
193        let progress = Progress::new(50, Some(100));
194        callback.on_progress(&progress);
195
196        let progress = Progress::new(50, None);
197        callback.on_progress(&progress);
198    }
199}