use crate::config::BoxChars;
use crate::render::table::ReportBuilder;
use crate::VERSION;
use indicatif::{ProgressBar, ProgressStyle};
use owo_colors::OwoColorize;
use super::{format_bytes, format_mbps, SpeedTestResult};
pub struct SpeedQXDisplay {
use_ascii: bool,
use_colors: bool,
json_mode: bool,
}
impl SpeedQXDisplay {
pub fn new(use_ascii: bool, use_colors: bool, json_mode: bool) -> Self {
Self {
use_ascii,
use_colors,
json_mode,
}
}
pub fn print_header(&self) {
if self.json_mode {
return;
}
println!();
if self.use_colors {
println!(
" {} - Internet Speed Test",
format!("SpeedQX v{}", VERSION).cyan().bold()
);
println!(" {}", "QubeTX Developer Tools".dimmed());
} else {
println!(" SpeedQX v{} - Internet Speed Test", VERSION);
println!(" QubeTX Developer Tools");
}
println!();
}
pub fn create_spinner(&self, step: u32, total: u32, msg: &str) -> ProgressBar {
if self.json_mode {
return ProgressBar::hidden();
}
let pb = ProgressBar::new_spinner();
let template = format!(" [{{spinner}}] [{}/{}] {{msg}}", step, total);
pb.set_style(
ProgressStyle::default_spinner()
.template(&template)
.unwrap_or_else(|_| ProgressStyle::default_spinner()),
);
pb.set_message(msg.to_string());
pb.enable_steady_tick(std::time::Duration::from_millis(80));
pb
}
pub fn create_progress_bar(&self, step: u32, total: u32, msg: &str) -> ProgressBar {
if self.json_mode {
return ProgressBar::hidden();
}
let pb = ProgressBar::new(100);
let template = format!(" [{{bar:20.cyan/dim}}] [{}/{}] {{msg}}", step, total);
pb.set_style(
ProgressStyle::default_bar()
.template(&template)
.unwrap_or_else(|_| ProgressStyle::default_bar())
.progress_chars("##-"),
);
pb.set_message(msg.to_string());
pb
}
pub fn finish_step(&self, step: u32, total: u32, msg: &str) {
if self.json_mode {
return;
}
let check = if self.use_ascii { "[OK]" } else { "\u{2713}" };
if self.use_colors {
println!(" [{}/{}] {} {}", step, total, check.green(), msg,);
} else {
println!(" [{}/{}] {} {}", step, total, check, msg);
}
}
}
pub fn render_results(result: &SpeedTestResult, use_ascii: bool, use_colors: bool) -> String {
let chars = if use_ascii {
BoxChars::ascii()
} else {
BoxChars::unicode()
};
let label_width = 14;
let data_width = 27;
let single_provider = result.providers.len() == 1;
if single_provider {
return render_single_provider(result, chars, label_width, data_width, use_colors);
}
render_multi_provider(result, chars, label_width, data_width, use_colors)
}
fn render_multi_provider(
result: &SpeedTestResult,
chars: BoxChars,
label_width: usize,
data_width: usize,
_use_colors: bool,
) -> String {
let mut builder = ReportBuilder::new(label_width, data_width, chars);
builder = builder.full_top_border().span_row(&format!(
" {:^width$}",
"SPEEDQX RESULTS",
width = label_width + data_width + 3
));
builder = builder.section_header("Averaged Results");
if let Some(ping) = result.ping_ms {
builder = builder.row("Ping", &format!("{:.1} ms", ping));
}
if let Some(jitter) = result.jitter_ms {
builder = builder.row("Jitter", &format!("{:.1} ms", jitter));
}
builder = builder.row(
"Download",
&format!("{} (avg)", format_mbps(result.download_mbps)),
);
builder = builder.row(
"Upload",
&format!("{} (avg)", format_mbps(result.upload_mbps)),
);
if let Some(loss) = result.packet_loss_pct {
builder = builder.row("Packet Loss", &format!("{}%", loss));
}
builder = builder.row("Duration", &format!("{:.1}s", result.duration_s));
if let Some(ref stability) = result.stability {
let dl_label = if stability.download_stable {
"Stable"
} else {
"Variable"
};
let ul_label = if stability.upload_stable {
"Stable"
} else {
"Variable"
};
builder = builder.row(
"Stability",
&format!(
"DL: {} (CV {:.0}%) / UL: {} (CV {:.0}%)",
dl_label,
stability.download_cv * 100.0,
ul_label,
stability.upload_cv * 100.0,
),
);
}
if let Some(ref div) = result.provider_divergence {
if div.significant {
builder = builder.row(
"Divergence",
&format!(
"DL {:.0}% / UL {:.0}% (significant)",
div.download * 100.0,
div.upload * 100.0,
),
);
}
}
for provider in &result.providers {
builder = render_provider_section(builder, provider);
}
let mut output = builder.finish();
output.push('\n');
output
}
fn render_single_provider(
result: &SpeedTestResult,
chars: BoxChars,
label_width: usize,
data_width: usize,
_use_colors: bool,
) -> String {
let mut builder = ReportBuilder::new(label_width, data_width, chars);
builder = builder.full_top_border().span_row(&format!(
" {:^width$}",
"SPEEDQX RESULTS",
width = label_width + data_width + 3
));
if let Some(provider) = result.providers.first() {
builder = builder.section_header(&provider.provider);
builder = builder.row("Server", &provider.server);
if let Some(ref loc) = provider.location {
builder = builder.row("Location", loc);
}
if let Some(ping) = provider.ping_ms {
builder = builder.row("Ping", &format!("{:.1} ms", ping));
}
if let Some(jitter) = provider.jitter_ms {
builder = builder.row("Jitter", &format!("{:.1} ms", jitter));
}
if let Some(dl) = provider.download_mbps {
builder = builder.row("Download", &format_mbps(dl));
}
if let Some(ul) = provider.upload_mbps {
builder = builder.row("Upload", &format_mbps(ul));
}
builder = builder.row("DL Data", &format_bytes(provider.download_bytes));
builder = builder.row("UL Data", &format_bytes(provider.upload_bytes));
if let Some(loss) = provider.packet_loss_pct {
builder = builder.row("Packet Loss", &format!("{}%", loss));
}
builder = builder.row("Duration", &format!("{:.1}s", result.duration_s));
}
let mut output = builder.finish();
output.push('\n');
output
}
fn render_provider_section(
builder: ReportBuilder,
provider: &super::ProviderResult,
) -> ReportBuilder {
let mut b = builder.section_header(&provider.provider);
if let Some(ref err) = provider.error {
b = b.row("Error", err);
return b;
}
b = b.row("Server", &provider.server);
if let Some(ref loc) = provider.location {
b = b.row("Location", loc);
}
if let Some(ping) = provider.ping_ms {
b = b.row("Ping", &format!("{:.1} ms", ping));
}
if let Some(jitter) = provider.jitter_ms {
b = b.row("Jitter", &format!("{:.1} ms", jitter));
}
if let Some(dl) = provider.download_mbps {
b = b.row("Download", &format_mbps(dl));
}
if let Some(ul) = provider.upload_mbps {
b = b.row("Upload", &format_mbps(ul));
}
b = b.row("DL Data", &format_bytes(provider.download_bytes));
b = b.row("UL Data", &format_bytes(provider.upload_bytes));
b
}