use anyhow::{Context, Result};
use colored::Colorize;
use dialoguer::Select;
use std::path::PathBuf;
use firecloud_storage::ManifestStore;
use comfy_table::{Table, presets::UTF8_FULL};
pub async fn run_interactive(data_dir: PathBuf, file_id: Option<String>) -> Result<()> {
let manifest_store_path = data_dir.join("manifests");
let manifest_store = ManifestStore::open(&manifest_store_path)
.context("Failed to open manifest store")?;
if let Some(id) = file_id {
println!("\n{} Downloading file: {}", "📥".cyan(), id.yellow());
super::download::run(data_dir, None, Some(id), None).await?;
return Ok(());
}
println!("\n{}", "📥 Available Files".bold().cyan());
println!("{}", "─".repeat(50).dimmed());
let manifests = manifest_store.list()
.context("Failed to list manifests")?;
if manifests.is_empty() {
println!("\n{} No files available for download", "ℹ".blue());
println!(" Upload files using: {}\n", "firecloud upload <file>".cyan());
return Ok(());
}
let mut table = Table::new();
table.load_preset(UTF8_FULL);
table.set_header(vec!["#", "Filename", "Size", "Chunks", "Encrypted"]);
let mut items = Vec::new();
for (idx, summary) in manifests.iter().enumerate() {
let size = format_size(summary.size);
let encrypted = " ?";
table.add_row(vec![
(idx + 1).to_string(),
summary.name.clone(),
size.clone(),
summary.chunk_count.to_string(),
encrypted.to_string(),
]);
items.push(format!(
"{:2}. {:<30} {:>10}",
idx + 1,
truncate_filename(&summary.name, 30),
size
));
}
println!("{}\n", table);
let selection = Select::new()
.with_prompt("Select file to download")
.items(&items)
.default(0)
.interact_opt()?;
match selection {
Some(idx) => {
let summary = &manifests[idx];
println!("\n{} Downloading: {}",
"📥".cyan(),
summary.name.yellow());
super::download::run(
data_dir,
None,
Some(summary.file_id.to_string()),
None
).await?;
}
None => {
println!("\n{} Download cancelled", "✓".yellow());
}
}
Ok(())
}
fn truncate_filename(filename: &str, max_len: usize) -> String {
if filename.len() <= max_len {
filename.to_string()
} else {
format!("{}...", &filename[..max_len-3])
}
}
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)
}
}