use anyhow::{Context, Result};
use colored::Colorize;
use std::path::PathBuf;
use firecloud_net::{FireCloudNode, NodeConfig, NodeEvent};
use comfy_table::{Table, presets::UTF8_FULL};
use std::time::Duration;
pub async fn run(data_dir: PathBuf, live: bool) -> Result<()> {
loop {
if live {
print!("\x1B[2J\x1B[1;1H"); }
println!("\n{}", "🌐 FireCloud Network Topology".bold().cyan());
println!("{}", "─".repeat(50).dimmed());
let config = NodeConfig {
port: 0,
enable_mdns: true,
bootstrap_peers: Vec::new(),
bootstrap_relays: vec![],
};
let mut node = FireCloudNode::new(config)
.await
.context("Failed to initialize node")?;
println!("\n{} Your Peer ID: {}", "ℹ".blue(), node.local_peer_id().to_string().yellow());
println!("\n{} Discovering network...", "🔍".cyan());
let mut all_peers = Vec::new();
let discovery_timeout = tokio::time::Instant::now() + Duration::from_secs(5);
while tokio::time::Instant::now() < discovery_timeout {
tokio::select! {
_ = tokio::time::sleep(Duration::from_millis(100)) => {}
event = node.poll_event() => {
if let Some(event) = event {
match event {
NodeEvent::PeerDiscovered(peer_id) => {
if peer_id != node.local_peer_id() {
all_peers.push(peer_id);
}
}
NodeEvent::Listening(addr) => {
println!("{} Listening on {}", "✓".green(), addr.to_string().dimmed());
}
_ => {}
}
}
}
}
}
if all_peers.is_empty() {
println!("\n{} No peers connected", "⚠".yellow());
println!(" Start the network node: {}", "firecloud node".cyan());
if !live {
return Ok(());
} else {
println!("\n{} Retrying in 5 seconds... Press Ctrl+C to exit.", "ℹ".blue());
tokio::time::sleep(Duration::from_secs(5)).await;
continue;
}
}
println!("\n{} Connected Peers: {}", "✓".green(), all_peers.len());
println!("{} Querying for storage providers...", "🔍".cyan());
let providers = match node.find_storage_providers(10).await {
Ok(p) => p,
Err(e) => {
eprintln!("{} Warning: Could not query storage providers: {}", "⚠".yellow(), e);
vec![]
}
};
println!("\n{}", "Network Topology:".bold());
println!("┌─ {} (you)", "Local Node".cyan());
for (idx, peer_id) in all_peers.iter().enumerate() {
let is_provider = providers.iter().any(|p| p.peer_id == *peer_id);
let symbol = if idx == all_peers.len() - 1 { "└" } else { "├" };
let role = if is_provider { "📦 Provider" } else { "👤 User" };
println!("{}─ {} {}",
symbol,
truncate_peer_id(&peer_id.to_string()).dimmed(),
role.yellow());
}
if !providers.is_empty() {
println!("\n{}", "Storage Providers:".bold());
let mut table = Table::new();
table.load_preset(UTF8_FULL);
table.set_header(vec!["Peer ID", "Available Space", "Status"]);
for provider in &providers {
let peer_id_str = truncate_peer_id(&provider.peer_id.to_string());
let space = format_size(provider.available_space);
let status = "🟢 Online";
table.add_row(vec![peer_id_str, space, status.to_string()]);
}
println!("{}\n", table);
}
println!("{}", "Network Statistics:".bold());
println!(" Total Nodes: {}", all_peers.len() + 1);
println!(" Storage Providers: {}", providers.len());
let total_storage: u64 = providers.iter()
.map(|p| p.available_space)
.sum();
if total_storage > 0 {
println!(" Total Available Storage: {}", format_size(total_storage).green());
}
if live {
println!("\n{} Live mode enabled. Press Ctrl+C to exit. Refreshing in 5s...", "ℹ".blue());
tokio::time::sleep(Duration::from_secs(5)).await;
} else {
println!();
break;
}
}
Ok(())
}
fn truncate_peer_id(peer_id: &str) -> String {
if peer_id.len() > 20 {
format!("{}...{}", &peer_id[..8], &peer_id[peer_id.len()-6..])
} else {
peer_id.to_string()
}
}
fn format_size(bytes: u64) -> String {
const KB: u64 = 1_024;
const MB: u64 = 1_024 * 1_024;
const GB: u64 = 1_024 * 1_024 * 1_024;
const TB: u64 = 1_024 * 1_024 * 1_024 * 1_024;
if bytes >= TB {
format!("{:.2} TB", bytes as f64 / TB as f64)
} else if bytes >= GB {
format!("{:.2} GB", bytes as f64 / GB as f64)
} else if bytes >= MB {
format!("{:.2} MB", bytes as f64 / MB as f64)
} else if bytes >= KB {
format!("{:.2} KB", bytes as f64 / KB as f64)
} else {
format!("{} B", bytes)
}
}