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}