265 lines
8.8 KiB
Rust
265 lines
8.8 KiB
Rust
use std::io::{self, IsTerminal, Write};
|
|
use std::sync::Mutex;
|
|
use std::time::Duration;
|
|
use terminal_size::{terminal_size, Width};
|
|
|
|
pub const BANNER: [&str; 9] = [
|
|
" ░██████ ░██████ ",
|
|
" ░██ ░██ ░██ ░██ ",
|
|
"░██ ░██████ ░█████████████░██ ░███████ ░██████ ░████████ ░████████ ░███████ ░██░████ ",
|
|
"░██ ░██░██ ░██ ░██░████████░██ ░██ ░██ ░██ ░██░██ ░██░██ ░██░███ ",
|
|
"░██ ░███████░██ ░██ ░██ ░██░██ ░███████ ░██ ░██░██ ░██░█████████░██ ",
|
|
" ░██ ░██░██ ░██░██ ░██ ░██░██ ░██░██ ░██░██ ░██░██ ░██░██ ░██░██ ░██ ",
|
|
" ░██████ ░█████░█░██ ░██ ░██ ░██████ ░███████ ░█████░██░██ ░██░██ ░██ ░███████ ░██ ",
|
|
" ",
|
|
" ",
|
|
];
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct ScreenState {
|
|
pub current_ip: String,
|
|
pub tested: usize,
|
|
pub total: usize,
|
|
}
|
|
|
|
pub struct TerminalUi {
|
|
enabled: bool,
|
|
lock: Mutex<()>,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct RedisWorkerScreenState {
|
|
pub worker_name: String,
|
|
pub queue_name: String,
|
|
pub current_ip: String,
|
|
pub current_path: String,
|
|
pub current_status: String,
|
|
pub processed: usize,
|
|
pub captured: usize,
|
|
pub global_done: usize,
|
|
pub global_total: usize,
|
|
pub queue_remaining: Option<usize>,
|
|
pub block_done: usize,
|
|
pub block_total: usize,
|
|
pub elapsed: Duration,
|
|
}
|
|
|
|
impl TerminalUi {
|
|
pub fn new(enabled: bool) -> Self {
|
|
Self {
|
|
enabled,
|
|
lock: Mutex::new(()),
|
|
}
|
|
}
|
|
|
|
pub fn is_enabled(&self) -> bool {
|
|
self.enabled
|
|
}
|
|
|
|
pub fn print_banner(&self) {
|
|
if !self.enabled {
|
|
return;
|
|
}
|
|
|
|
let _guard = self.lock.lock().expect("terminal mutex poisoned");
|
|
let mut stdout = io::stdout();
|
|
let _ = write!(stdout, "\x1b[2J\x1b[H\x1b[?25l");
|
|
for line in BANNER {
|
|
let _ = writeln!(stdout, "{line}");
|
|
}
|
|
let _ = writeln!(stdout, "IP: aguardando...");
|
|
let _ = writeln!(
|
|
stdout,
|
|
"Progress: [░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░] | 0.0%"
|
|
);
|
|
let _ = stdout.flush();
|
|
}
|
|
|
|
pub fn render_redis_worker(&self, state: &RedisWorkerScreenState) {
|
|
if !self.enabled {
|
|
return;
|
|
}
|
|
|
|
let _guard = self.lock.lock().expect("terminal mutex poisoned");
|
|
let mut stdout = io::stdout();
|
|
let width = terminal_width();
|
|
let content_width = width.saturating_sub(2).max(32);
|
|
let global_percent = percent(state.global_done, state.global_total);
|
|
let block_percent = percent(state.block_done, state.block_total);
|
|
let global_bar = progress_bar(global_percent, content_width.saturating_sub(24));
|
|
let block_bar = progress_bar(block_percent, content_width.saturating_sub(24));
|
|
let queue_remaining = state
|
|
.queue_remaining
|
|
.map(|value| value.to_string())
|
|
.unwrap_or_else(|| "?".to_string());
|
|
|
|
let _ = write!(stdout, "\x1b[2J\x1b[H\x1b[?25l");
|
|
let _ = writeln!(
|
|
stdout,
|
|
"{}",
|
|
fit_line(
|
|
&format!(
|
|
"CamFinder Redis Worker {} pid:{}",
|
|
state.worker_name,
|
|
std::process::id()
|
|
),
|
|
content_width
|
|
)
|
|
);
|
|
let _ = writeln!(stdout, "{}", "─".repeat(content_width));
|
|
let _ = writeln!(
|
|
stdout,
|
|
"{}",
|
|
fit_line(&format!("Fila: {}", state.queue_name), content_width)
|
|
);
|
|
let _ = writeln!(
|
|
stdout,
|
|
"{}",
|
|
fit_line(
|
|
&format!(
|
|
"Global: [{}] {:>5.1}% {}/{} IPs redis:{}",
|
|
global_bar,
|
|
global_percent,
|
|
state.global_done,
|
|
state.global_total,
|
|
queue_remaining
|
|
),
|
|
content_width
|
|
)
|
|
);
|
|
let _ = writeln!(
|
|
stdout,
|
|
"{}",
|
|
fit_line(
|
|
&format!(
|
|
"Bloco : [{}] {:>5.1}% {}/{} tentativas",
|
|
block_bar, block_percent, state.block_done, state.block_total
|
|
),
|
|
content_width
|
|
)
|
|
);
|
|
let _ = writeln!(stdout, "{}", "─".repeat(content_width));
|
|
let _ = writeln!(
|
|
stdout,
|
|
"{}",
|
|
fit_line(&format!("IP atual : {}", state.current_ip), content_width)
|
|
);
|
|
let _ = writeln!(
|
|
stdout,
|
|
"{}",
|
|
fit_line(&format!("Path : {}", state.current_path), content_width)
|
|
);
|
|
let _ = writeln!(
|
|
stdout,
|
|
"{}",
|
|
fit_line(
|
|
&format!("Status : {}", state.current_status),
|
|
content_width
|
|
)
|
|
);
|
|
let _ = writeln!(
|
|
stdout,
|
|
"{}",
|
|
fit_line(
|
|
&format!(
|
|
"Tentativas: {} Capturas: {} Tempo: {}",
|
|
state.processed,
|
|
state.captured,
|
|
format_duration(state.elapsed)
|
|
),
|
|
content_width
|
|
)
|
|
);
|
|
let _ = stdout.flush();
|
|
}
|
|
|
|
pub fn render(&self, state: &ScreenState) {
|
|
if !self.enabled {
|
|
return;
|
|
}
|
|
|
|
let _guard = self.lock.lock().expect("terminal mutex poisoned");
|
|
let mut stdout = io::stdout();
|
|
let percent = if state.total == 0 {
|
|
100.0
|
|
} else {
|
|
((state.tested as f64 / state.total as f64) * 100.0).min(100.0)
|
|
};
|
|
let bar_width = 36usize;
|
|
let filled = ((percent / 100.0) * bar_width as f64).round() as usize;
|
|
let empty = bar_width.saturating_sub(filled);
|
|
let bar = format!("{}{}", "█".repeat(filled), "░".repeat(empty));
|
|
let ip = format!("{:<15}", state.current_ip);
|
|
let percent = format!("{:>5.1}%", percent);
|
|
|
|
let _ = write!(
|
|
stdout,
|
|
"\x1b[2F\x1b[2KIP: {ip}\n\x1b[2KProgress: [{bar}] | {percent}\n"
|
|
);
|
|
let _ = stdout.flush();
|
|
}
|
|
|
|
pub fn finish(&self) {
|
|
if !self.enabled {
|
|
return;
|
|
}
|
|
|
|
let _guard = self.lock.lock().expect("terminal mutex poisoned");
|
|
let mut stdout = io::stdout();
|
|
let _ = write!(stdout, "\x1b[?25h");
|
|
let _ = stdout.flush();
|
|
}
|
|
}
|
|
|
|
pub fn stdout_is_terminal() -> bool {
|
|
io::stdout().is_terminal()
|
|
}
|
|
|
|
fn terminal_width() -> usize {
|
|
terminal_size()
|
|
.map(|(Width(width), _)| usize::from(width))
|
|
.unwrap_or(80)
|
|
}
|
|
|
|
fn percent(done: usize, total: usize) -> f64 {
|
|
if total == 0 {
|
|
0.0
|
|
} else {
|
|
((done as f64 / total as f64) * 100.0).clamp(0.0, 100.0)
|
|
}
|
|
}
|
|
|
|
fn progress_bar(percent: f64, width: usize) -> String {
|
|
let width = width.clamp(8, 80);
|
|
let filled = ((percent / 100.0) * width as f64).round() as usize;
|
|
let empty = width.saturating_sub(filled);
|
|
format!("{}{}", "█".repeat(filled), "░".repeat(empty))
|
|
}
|
|
|
|
fn fit_line(value: &str, width: usize) -> String {
|
|
let chars = value.chars().collect::<Vec<_>>();
|
|
if chars.len() <= width {
|
|
return value.to_string();
|
|
}
|
|
if width <= 1 {
|
|
return "…".to_string();
|
|
}
|
|
chars
|
|
.into_iter()
|
|
.take(width - 1)
|
|
.chain(std::iter::once('…'))
|
|
.collect()
|
|
}
|
|
|
|
fn format_duration(duration: Duration) -> String {
|
|
let total = duration.as_secs();
|
|
let hours = total / 3600;
|
|
let minutes = (total % 3600) / 60;
|
|
let seconds = total % 60;
|
|
if hours > 0 {
|
|
format!("{hours:02}:{minutes:02}:{seconds:02}")
|
|
} else {
|
|
format!("{minutes:02}:{seconds:02}")
|
|
}
|
|
}
|