mirror of
https://github.com/lunchcat/sif.git
synced 2026-01-13 05:16:44 -08:00
168 lines
3.8 KiB
Go
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)
|
|
}
|