use crate::{EmbeddedDatabase, Result, Error};
use crate::storage::{DumpManager, DumpOptions, DumpMode, DumpCompressionType};
use std::path::PathBuf;
use indicatif::{ProgressBar, ProgressStyle};
use colored::Colorize;
pub struct DumpCommand {
pub output: PathBuf,
pub append: bool,
pub compression: String,
pub connection: Option<String>,
pub verbose: bool,
pub data_dir: Option<PathBuf>,
pub memory: bool,
}
impl DumpCommand {
pub fn execute(&self) -> Result<()> {
if self.verbose {
println!("{}", "HeliosDB Dump Utility".bold());
println!();
}
let compression = DumpCompressionType::from_str(&self.compression)?;
let data_dir = if self.memory {
return Err(Error::config(
"Cannot dump from in-memory database without data directory".to_string()
));
} else if let Some(ref data_dir) = self.data_dir {
if self.verbose {
println!("{} {}", "Database directory:".dimmed(), data_dir.display());
}
data_dir.clone()
} else if let Some(ref _conn) = self.connection {
return Err(Error::config(
"Server mode dump not yet implemented. Use --data-dir for embedded mode.".to_string()
));
} else {
return Err(Error::config(
"Either --data-dir or --connection must be specified".to_string()
));
};
let db = EmbeddedDatabase::new(&data_dir)?;
let dump_manager = DumpManager::new(data_dir, compression);
let mode = if self.append {
DumpMode::Incremental
} else {
DumpMode::Full
};
let options = DumpOptions {
output_path: self.output.clone(),
mode,
compression,
append: self.append,
tables: None, verbose: self.verbose,
connection: self.connection.clone(),
format: crate::storage::DumpOutputFormat::Binary, };
if self.verbose {
println!();
println!("{}", "Dump configuration:".bold());
println!(" Mode: {}", if self.append { "Incremental" } else { "Full" });
println!(" Compression: {:?}", compression);
println!(" Output: {}", self.output.display());
println!();
}
let progress = if !self.verbose {
let pb = ProgressBar::new_spinner();
pb.set_style(
ProgressStyle::default_spinner()
.template("{spinner:.green} {msg}")
.map_err(|e| Error::io(format!("Failed to set progress style: {}", e)))?
);
pb.set_message("Dumping database...");
pb.enable_steady_tick(std::time::Duration::from_millis(100));
Some(pb)
} else {
None
};
let report = dump_manager.dump(&options, &db)?;
if let Some(pb) = progress {
pb.finish_with_message("Dump completed");
}
println!();
println!("{}", "Dump completed successfully!".green().bold());
println!();
println!("{}", "Summary:".bold());
println!(" Dump ID: {}", report.dump_id);
println!(" Tables: {}", report.tables_dumped);
println!(" Rows: {}", format_number(report.rows_dumped));
println!(" Size (compressed): {}", format_bytes(report.bytes_written));
println!(" Size (uncompressed): {}", format_bytes(report.bytes_uncompressed));
println!(" Compression ratio: {:.1}%", report.compression_ratio * 100.0);
println!(" Duration: {}", format_duration(report.duration_ms));
println!();
println!("Dump file: {}", self.output.display().to_string().cyan());
Ok(())
}
}
fn format_number(n: u64) -> String {
let s = n.to_string();
let mut result = String::new();
for (i, c) in s.chars().rev().enumerate() {
if i > 0 && i % 3 == 0 {
result.push(',');
}
result.push(c);
}
result.chars().rev().collect()
}
fn format_bytes(bytes: u64) -> String {
const KB: u64 = 1024;
const MB: u64 = KB * 1024;
const GB: u64 = MB * 1024;
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!("{} bytes", bytes)
}
}
fn format_duration(ms: u64) -> String {
if ms >= 60000 {
format!("{:.1} min", ms as f64 / 60000.0)
} else if ms >= 1000 {
format!("{:.1} sec", ms as f64 / 1000.0)
} else {
format!("{} ms", ms)
}
}