Files
sif/internal/output/progress.go
2026-01-03 06:01:00 -08:00

168 lines
3.8 KiB
Go

/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2025 vmfunc (Celeste Hickenlooper), xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package output
import (
"fmt"
"sync"
"sync/atomic"
)
// Progress bar configuration
const (
progressWidth = 30
progressFilled = "="
progressCurrent = ">"
progressEmpty = " "
)
// Progress displays a progress bar for operations with known counts
type Progress struct {
total int64
current int64
message string
lastItem string
mu sync.Mutex
paused bool
}
// NewProgress creates a new progress bar
func NewProgress(total int, message string) *Progress {
return &Progress{
total: int64(total),
message: message,
}
}
// Increment advances the progress by 1 and optionally updates the current item
func (p *Progress) Increment(item string) {
atomic.AddInt64(&p.current, 1)
p.mu.Lock()
p.lastItem = item
paused := p.paused
p.mu.Unlock()
if !paused {
p.render()
}
}
// Set sets the progress to a specific value
func (p *Progress) Set(current int, item string) {
atomic.StoreInt64(&p.current, int64(current))
p.mu.Lock()
p.lastItem = item
paused := p.paused
p.mu.Unlock()
if !paused {
p.render()
}
}
// Pause temporarily stops rendering (use before printing other output)
func (p *Progress) Pause() {
p.mu.Lock()
p.paused = true
p.mu.Unlock()
ClearLine()
}
// Resume resumes rendering after a pause
func (p *Progress) Resume() {
p.mu.Lock()
p.paused = false
p.mu.Unlock()
p.render()
}
// Done clears the progress bar line
func (p *Progress) Done() {
if apiMode || !IsTTY {
return
}
ClearLine()
}
func (p *Progress) render() {
if apiMode {
return
}
// In non-TTY mode, print progress at milestones only
if !IsTTY {
current := atomic.LoadInt64(&p.current)
total := p.total
percent := int(current * 100 / total)
// Print at 0%, 25%, 50%, 75%, 100%
if current == 1 || percent == 25 || percent == 50 || percent == 75 || current == total {
fmt.Printf(" [%d%%] %d/%d\n", percent, current, total)
}
return
}
current := atomic.LoadInt64(&p.current)
total := p.total
p.mu.Lock()
lastItem := p.lastItem
p.mu.Unlock()
// Calculate percentage
percent := 0
if total > 0 {
percent = int(current * 100 / total)
}
// Build progress bar
filled := 0
if total > 0 {
filled = int(progressWidth * current / total)
}
if filled > progressWidth {
filled = progressWidth
}
bar := ""
for i := 0; i < progressWidth; i++ {
if i < filled {
bar += progressFilled
} else if i == filled && current < total {
bar += progressCurrent
} else {
bar += progressEmpty
}
}
// Truncate item if too long
maxItemLen := 30
if len(lastItem) > maxItemLen {
lastItem = lastItem[:maxItemLen-3] + "..."
}
// Format: [========> ] 45% (4500/10000) /admin
line := fmt.Sprintf(" [%s] %3d%% (%d/%d) %s",
prefixInfo.Render(bar),
percent,
current,
total,
Muted.Render(lastItem),
)
ClearLine()
fmt.Print(line)
}