mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
228 lines
7.4 KiB
Python
228 lines
7.4 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# Copyright (c) 2025 Allaun
|
|
|
|
"""Progress tracker for file processing operations.
|
|
|
|
This module provides progress tracking functionality for file processing operations,
|
|
including time estimation, rate calculation, and progress reporting.
|
|
|
|
Key Features:
|
|
- Progress tracking with time estimation
|
|
- Rate calculation (files/second, bytes/second)
|
|
- Console and callback-based reporting
|
|
- Error handling
|
|
- Thread-safe operations
|
|
|
|
Dependencies:
|
|
- time (standard library)
|
|
- typing (standard library)
|
|
- threading (standard library)
|
|
"""
|
|
|
|
import time
|
|
from typing import Dict, Any, Optional, Callable
|
|
import threading
|
|
|
|
|
|
class ProgressTracker:
|
|
"""Progress tracker for file processing operations.
|
|
|
|
Responsibilities:
|
|
- Track progress of file processing operations
|
|
- Calculate time estimates and rates
|
|
- Report progress via callbacks
|
|
- Handle progress tracking errors
|
|
- Support thread-safe operations
|
|
"""
|
|
|
|
def __init__(self):
|
|
"""Initialize progress tracker."""
|
|
self._lock = threading.Lock()
|
|
self._start_time: float = 0.0
|
|
self._last_update_time: float = 0.0
|
|
self._total_items = 0
|
|
self._completed_items = 0
|
|
self._total_bytes = 0
|
|
self._processed_bytes = 0
|
|
self._status = "not_started"
|
|
self._error_count = 0
|
|
|
|
def start(self, total_items: int = 0, total_bytes: int = 0) -> None:
|
|
"""Start progress tracking.
|
|
|
|
Args:
|
|
total_items: Total number of items to process
|
|
total_bytes: Total number of bytes to process
|
|
"""
|
|
with self._lock:
|
|
self._start_time = time.monotonic()
|
|
self._last_update_time = self._start_time
|
|
self._total_items = total_items
|
|
self._completed_items = 0
|
|
self._total_bytes = total_bytes
|
|
self._processed_bytes = 0
|
|
self._status = "in_progress"
|
|
self._error_count = 0
|
|
|
|
def update(self, items_completed: int = 1, bytes_processed: int = 0) -> None:
|
|
"""Update progress with completed items and processed bytes.
|
|
|
|
Args:
|
|
items_completed: Number of items completed since last update
|
|
bytes_processed: Number of bytes processed since last update
|
|
"""
|
|
with self._lock:
|
|
self._completed_items += items_completed
|
|
self._processed_bytes += bytes_processed
|
|
self._last_update_time = time.monotonic()
|
|
|
|
def complete(self) -> None:
|
|
"""Mark progress as complete."""
|
|
with self._lock:
|
|
self._status = "completed"
|
|
self._last_update_time = time.monotonic()
|
|
|
|
def error(self) -> None:
|
|
"""Record an error."""
|
|
with self._lock:
|
|
self._error_count += 1
|
|
self._last_update_time = time.monotonic()
|
|
|
|
def get_progress(self) -> Dict[str, Any]:
|
|
"""Get current progress information.
|
|
|
|
Returns:
|
|
Dictionary containing progress information
|
|
"""
|
|
with self._lock:
|
|
elapsed = time.monotonic() - self._start_time if self._start_time > 0 else 0
|
|
|
|
# Calculate rates
|
|
items_per_second: float = self._completed_items / elapsed if elapsed > 0 else 0.0
|
|
bytes_per_second: float = self._processed_bytes / elapsed if elapsed > 0 else 0.0
|
|
|
|
# Calculate estimates
|
|
remaining_items = max(0, self._total_items - self._completed_items)
|
|
remaining_bytes = max(0, self._total_bytes - self._processed_bytes)
|
|
|
|
time_remaining: float = 0.0
|
|
if items_per_second > 0:
|
|
time_remaining = remaining_items / items_per_second
|
|
elif bytes_per_second > 0:
|
|
time_remaining = remaining_bytes / bytes_per_second
|
|
|
|
percent_complete: float = 0.0
|
|
if self._total_items > 0:
|
|
percent_complete = (self._completed_items / self._total_items) * 100
|
|
elif self._total_bytes > 0:
|
|
percent_complete = (self._processed_bytes / self._total_bytes) * 100
|
|
|
|
return {
|
|
'status': self._status,
|
|
'start_time': self._start_time,
|
|
'elapsed_time': elapsed,
|
|
'total_items': self._total_items,
|
|
'completed_items': self._completed_items,
|
|
'remaining_items': remaining_items,
|
|
'total_bytes': self._total_bytes,
|
|
'processed_bytes': self._processed_bytes,
|
|
'remaining_bytes': remaining_bytes,
|
|
'percent_complete': percent_complete,
|
|
'items_per_second': items_per_second,
|
|
'bytes_per_second': bytes_per_second,
|
|
'time_remaining': time_remaining,
|
|
'error_count': self._error_count
|
|
}
|
|
|
|
def report_progress(self, on_progress: Optional[Callable[[Dict[str, Any]], None]] = None) -> None:
|
|
"""Report progress via callback.
|
|
|
|
Args:
|
|
on_progress: Optional progress callback
|
|
"""
|
|
if on_progress:
|
|
progress = self.get_progress()
|
|
on_progress(progress)
|
|
|
|
def reset(self) -> None:
|
|
"""Reset progress tracker."""
|
|
with self._lock:
|
|
self._start_time = 0
|
|
self._last_update_time = 0
|
|
self._total_items = 0
|
|
self._completed_items = 0
|
|
self._total_bytes = 0
|
|
self._processed_bytes = 0
|
|
self._status = "not_started"
|
|
self._error_count = 0
|
|
|
|
def get_elapsed_time(self) -> float:
|
|
"""Get elapsed time since start.
|
|
|
|
Returns:
|
|
Elapsed time in seconds
|
|
"""
|
|
with self._lock:
|
|
return time.monotonic() - self._start_time if self._start_time > 0 else 0
|
|
|
|
def get_status(self) -> str:
|
|
"""Get current status.
|
|
|
|
Returns:
|
|
Current status string
|
|
"""
|
|
with self._lock:
|
|
return self._status
|
|
|
|
def is_complete(self) -> bool:
|
|
"""Check if progress is complete.
|
|
|
|
Returns:
|
|
True if complete, False otherwise
|
|
"""
|
|
with self._lock:
|
|
return self._status == "completed"
|
|
|
|
def get_error_count(self) -> int:
|
|
"""Get error count.
|
|
|
|
Returns:
|
|
Number of errors encountered
|
|
"""
|
|
with self._lock:
|
|
return self._error_count
|
|
|
|
def format_progress(self, progress: Optional[Dict[str, Any]] = None) -> str:
|
|
"""Format progress information as string.
|
|
|
|
Args:
|
|
progress: Optional progress dictionary
|
|
|
|
Returns:
|
|
Formatted progress string
|
|
"""
|
|
if progress is None:
|
|
progress = self.get_progress()
|
|
|
|
status = progress.get('status', 'unknown')
|
|
percent = progress.get('percent_complete', 0)
|
|
elapsed = progress.get('elapsed_time', 0)
|
|
remaining = progress.get('time_remaining', 0)
|
|
items = progress.get('completed_items', 0)
|
|
total = progress.get('total_items', 0)
|
|
errors = progress.get('error_count', 0)
|
|
|
|
return (f"Status: {status} | "
|
|
f"Progress: {percent:.1f}% | "
|
|
f"Items: {items}/{total} | "
|
|
f"Time: {elapsed:.1f}s (remaining: {remaining:.1f}s) | "
|
|
f"Errors: {errors}")
|
|
|
|
|
|
def create_progress_tracker() -> ProgressTracker:
|
|
"""Create and return a ProgressTracker instance.
|
|
|
|
Returns:
|
|
ProgressTracker instance
|
|
"""
|
|
return ProgressTracker()
|