10 releases (6 breaking)

Uses new Rust 2024

new 0.7.0 Mar 25, 2025
0.6.2 Mar 25, 2025
0.5.1 Mar 25, 2025
0.4.0 Mar 25, 2025
0.1.0 Mar 23, 2025

#127 in Concurrency

Download history 599/week @ 2025-03-23

599 downloads per month

MIT license

43KB
772 lines

progress-token

A Rust library for hierarchical progress tracking with support for cancellation, status updates, and nested operations.

Crates.io Documentation License: MIT

Features

  • 🌳 Hierarchical Progress: Create trees of operations with weighted progress tracking
  • 📢 Status Updates: Track and propagate status messages through the operation hierarchy
  • ⏸️ Cancellation: Built-in support for cancelling operations and their children
  • 📊 Progress States: Support for both determinate (0.0-1.0) and indeterminate progress
  • 🔄 Multiple Subscribers: Allow multiple parts of your application to monitor progress
  • 🧮 Automatic Aggregation: Progress automatically calculated from weighted child tasks

Installation

Add this to your Cargo.toml:

[dependencies]
progress-token = "0.1.0"

Quick Start

use progress_token::ProgressToken;

#[tokio::main]
async fn main() {
    // Create a root progress token
    let root = ProgressToken::new("Main task");
    
    // Create child tasks with weights
    let process = root.child(0.7, "Processing"); // 70% of total
    let cleanup = root.child(0.3, "Cleanup");    // 30% of total
    
    // Subscribe to progress updates
    let mut updates = root.subscribe();
    
    // Update progress as work is done
    process.update_progress(0.5);  // Root progress becomes 0.35 (0.5 * 0.7)
    process.update_status("Processing file 1/10");
    
    cleanup.update_progress(1.0);  // Root progress becomes 0.65 (0.35 + 1.0 * 0.3)
    cleanup.update_status("Cleanup complete");

    // Complete tokens when done - no further updates will be processed
    process.complete();
    cleanup.complete();
}

Usage Examples

Basic Progress Tracking

let token = ProgressToken::new("Processing files");

// Update progress
token.update_progress(0.25);
token.update_status("Processing file 1/4");

// Mark as complete when done - sets progress to 1.0 and prevents further updates
token.complete();

// These updates will be ignored since the token is completed
token.update_progress(0.5);
token.update_status("This won't be shown");

Automatic Completion with Guards

let token = ProgressToken::new("Processing files");

{
    let _guard = token.complete_guard(); // Token will be completed when guard is dropped
    token.update_progress(0.5);
    token.update_status("Working...");
} // Token is automatically completed here

// Or prevent completion by forgetting the guard
let token = ProgressToken::new("Another task");
{
    let guard = token.complete_guard();
    token.update_progress(0.25);
    guard.forget(); // Token won't be completed when guard is dropped
}

Status Updates

Status messages propagate through the hierarchy, with the most specific (deepest) status being reported:

let root = ProgressToken::new("Backup");
let compress = root.child(1.0, "Compressing files");

// Update status with more specific information
compress.update_status("Compressing images/photo1.jpg");

// Get full status hierarchy
let statuses = root.statuses();
assert_eq!(statuses, vec![
    "Backup",
    "Compressing files", 
    "Compressing images/photo1.jpg"
]);

Progress Subscription

let token = ProgressToken::new("Long operation");
let mut updates = token.subscribe();

tokio::spawn(async move {
    while let Some(update) = updates.next().await {
        println!(
            "Progress: {:?}, Current status: {}", 
            update.progress,
            update.status()
        );
    }
});

Cancellation

let token = ProgressToken::new("Cancellable operation");

// In one part of your code
if error_condition {
    token.cancel(); // Cancels this token and all children
}

// These updates will be ignored since the token is cancelled
token.update_progress(0.5);
token.update_status("This won't be shown");

// Check cancellation state
if token.is_cancelled() {
    return;
}

Convenient Cancellation Checking

The check() method and ? operator make it easy to handle cancellation in async functions:

use progress_token::{ProgressToken, ProgressError};

async fn process_files(token: &ProgressToken<String>) -> Result<(), ProgressError> {
    // check() returns Result<(), ProgressError>
    token.check()?; // early return if cancelled
    
    // process files...
    token.update_progress(0.5);
    token.update_status("Processing files...");
    
    // check again before more work
    token.check()?;
    
    // continue processing...
    token.update_progress(1.0);
    token.complete();
    
    Ok(())
}

#[tokio::main]
async fn main() {
    let token = ProgressToken::new("File processing");
    
    // spawn cancellation task
    let token_clone = token.clone();
    tokio::spawn(async move {
        tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
        token_clone.cancel();
    });
    
    // process files with automatic cancellation handling
    if let Err(ProgressError::Cancelled) = process_files(&token).await {
        println!("Operation was cancelled");
    }
}

Implementation Notes

  • Progress values are automatically clamped to the range 0.0-1.0
  • Child task weights should sum to 1.0 for accurate progress calculation
  • Status updates and progress changes are broadcast to all subscribers
  • Once a token is completed or cancelled, all further updates are ignored
  • The broadcast channel has a reasonable buffer size to prevent lagging
  • Completion and cancellation are permanent states - they cannot be undone
  • When a token is cancelled, all its children are also cancelled
  • When a token is completed, its progress is set to 1.0 and no further updates are allowed

License

This project is licensed under the MIT License - see the LICENSE.md file for details.

Dependencies

~3.5–9.5MB
~88K SLoC