- Add progress.zig for sync progress display - Add rsync placeholder and release binaries to assets/rsync/
151 lines
4.5 KiB
Zig
151 lines
4.5 KiB
Zig
const std = @import("std");
|
|
const colors = @import("../utils/colors.zig");
|
|
|
|
/// ProgressBar provides visual feedback for long-running operations.
|
|
/// It displays progress as a percentage, item count, and throughput rate.
|
|
pub const ProgressBar = struct {
|
|
total: usize,
|
|
current: usize,
|
|
label: []const u8,
|
|
start_time: i64,
|
|
width: usize,
|
|
|
|
/// Initialize a new progress bar
|
|
pub fn init(total: usize, label: []const u8) ProgressBar {
|
|
return .{
|
|
.total = total,
|
|
.current = 0,
|
|
.label = label,
|
|
.start_time = std.time.milliTimestamp(),
|
|
.width = 40, // Default bar width
|
|
};
|
|
}
|
|
|
|
/// Update the progress bar with current progress
|
|
pub fn update(self: *ProgressBar, current: usize) void {
|
|
self.current = current;
|
|
self.render();
|
|
}
|
|
|
|
/// Increment progress by one step
|
|
pub fn increment(self: *ProgressBar) void {
|
|
self.current += 1;
|
|
self.render();
|
|
}
|
|
|
|
/// Render the progress bar to stderr
|
|
fn render(self: ProgressBar) void {
|
|
const percent = if (self.total > 0)
|
|
@divFloor(self.current * 100, self.total)
|
|
else
|
|
0;
|
|
|
|
const elapsed_ms = std.time.milliTimestamp() - self.start_time;
|
|
const rate = if (elapsed_ms > 0 and self.current > 0)
|
|
@as(f64, @floatFromInt(self.current)) / (@as(f64, @floatFromInt(elapsed_ms)) / 1000.0)
|
|
else
|
|
0.0;
|
|
|
|
// Build progress bar
|
|
const filled = if (self.total > 0)
|
|
@divFloor(self.current * self.width, self.total)
|
|
else
|
|
0;
|
|
const empty = self.width - filled;
|
|
|
|
var bar_buf: [64]u8 = undefined;
|
|
var bar_stream = std.io.fixedBufferStream(&bar_buf);
|
|
const bar_writer = bar_stream.writer();
|
|
|
|
// Write filled portion
|
|
var i: usize = 0;
|
|
while (i < filled) : (i += 1) {
|
|
_ = bar_writer.write("=") catch {};
|
|
}
|
|
// Write empty portion
|
|
i = 0;
|
|
while (i < empty) : (i += 1) {
|
|
_ = bar_writer.write("-") catch {};
|
|
}
|
|
|
|
const bar = bar_stream.getWritten();
|
|
|
|
// Clear line and print progress
|
|
const stderr = std.io.getStdErr().writer();
|
|
stderr.print("\r{s} [{s}] {d}/{d} {d}% ({d:.1} items/s)", .{
|
|
self.label,
|
|
bar,
|
|
self.current,
|
|
self.total,
|
|
percent,
|
|
rate,
|
|
}) catch {};
|
|
}
|
|
|
|
/// Finish the progress bar and print a newline
|
|
pub fn finish(self: ProgressBar) void {
|
|
self.render();
|
|
const stderr = std.io.getStdErr().writer();
|
|
stderr.print("\n", .{}) catch {};
|
|
}
|
|
|
|
/// Complete with a success message
|
|
pub fn success(self: ProgressBar, msg: []const u8) void {
|
|
self.current = self.total;
|
|
self.render();
|
|
colors.printSuccess("\n{s}\n", .{msg});
|
|
}
|
|
|
|
/// Get elapsed time in milliseconds
|
|
pub fn elapsedMs(self: ProgressBar) i64 {
|
|
return std.time.milliTimestamp() - self.start_time;
|
|
}
|
|
|
|
/// Get current throughput (items per second)
|
|
pub fn throughput(self: ProgressBar) f64 {
|
|
const elapsed_ms = self.elapsedMs();
|
|
if (elapsed_ms > 0 and self.current > 0) {
|
|
return @as(f64, @floatFromInt(self.current)) / (@as(f64, @floatFromInt(elapsed_ms)) / 1000.0);
|
|
}
|
|
return 0.0;
|
|
}
|
|
};
|
|
|
|
/// Spinner provides visual feedback for indeterminate operations
|
|
pub const Spinner = struct {
|
|
label: []const u8,
|
|
start_time: i64,
|
|
frames: []const u8,
|
|
frame_idx: usize,
|
|
|
|
const DEFAULT_FRAMES = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏";
|
|
|
|
pub fn init(label: []const u8) Spinner {
|
|
return .{
|
|
.label = label,
|
|
.start_time = std.time.milliTimestamp(),
|
|
.frames = DEFAULT_FRAMES,
|
|
.frame_idx = 0,
|
|
};
|
|
}
|
|
|
|
/// Render one frame of the spinner
|
|
pub fn tick(self: *Spinner) void {
|
|
const frame = self.frames[self.frame_idx % self.frames.len];
|
|
const stderr = std.io.getStdErr().writer();
|
|
stderr.print("\r{s} {c} ", .{ self.label, frame }) catch {};
|
|
self.frame_idx += 1;
|
|
}
|
|
|
|
/// Stop the spinner and print a newline
|
|
pub fn stop(self: Spinner) void {
|
|
_ = self; // Intentionally unused - for API consistency
|
|
const stderr = std.io.getStdErr().writer();
|
|
stderr.print("\n", .{}) catch {};
|
|
}
|
|
|
|
/// Get elapsed time in seconds
|
|
pub fn elapsedSec(self: Spinner) i64 {
|
|
return @divFloor(std.time.milliTimestamp() - self.start_time, 1000);
|
|
}
|
|
};
|