Rust · 3554 bytes Raw Blame History
1 use clap::Args;
2 use anyhow::{Context, Result};
3 use indicatif::{ProgressBar, ProgressStyle};
4 use std::path::PathBuf;
5 use tracing::info;
6 use humansize::{format_size, BINARY};
7
8 use crate::config::Config;
9 use crate::client::ZephyrClient;
10 use super::Command;
11
12 #[derive(Debug, Args)]
13 pub struct DownloadCommand {
14 /// File hash to download
15 file_hash: String,
16
17 /// Output file path (defaults to original filename)
18 #[arg(short, long)]
19 output: Option<PathBuf>,
20
21 /// Overwrite existing file
22 #[arg(long)]
23 force: bool,
24
25 /// Show progress bar
26 #[arg(long, default_value = "true")]
27 progress: bool,
28
29 /// Verify download integrity
30 #[arg(long, default_value = "true")]
31 verify: bool,
32 }
33
34 #[async_trait::async_trait]
35 impl Command for DownloadCommand {
36 async fn execute(&self, config: &Config) -> Result<()> {
37 info!("Downloading file: {}", self.file_hash);
38
39 let client = ZephyrClient::new(config);
40
41 // Get file info first
42 let files = client.list_files().await
43 .context("Failed to list files")?;
44
45 let file_info = files.iter()
46 .find(|f| f.hash == self.file_hash)
47 .with_context(|| format!("File not found: {}", self.file_hash))?;
48
49 // Determine output path
50 let output_path = match &self.output {
51 Some(path) => path.clone(),
52 None => PathBuf::from(&file_info.name),
53 };
54
55 // Check if file exists and handle overwrite
56 if output_path.exists() && !self.force {
57 anyhow::bail!(
58 "Output file already exists: {:?}. Use --force to overwrite.",
59 output_path
60 );
61 }
62
63 println!("Downloading: {}", file_info.name);
64 println!(" Hash: {}", file_info.hash);
65 println!(" Size: {}", format_size(file_info.size, BINARY));
66 println!(" Chunks: {}", file_info.chunks);
67 println!(" Output: {:?}", output_path);
68
69 // Create progress bar
70 let progress = if self.progress {
71 let pb = ProgressBar::new(file_info.size);
72 pb.set_style(
73 ProgressStyle::default_bar()
74 .template("[{elapsed_precise}] {bar:40.cyan/blue} {percent}% {bytes}/{total_bytes} ETA: {eta}")
75 .unwrap()
76 .progress_chars("##-")
77 );
78 Some(pb)
79 } else {
80 None
81 };
82
83 // Download the file
84 client.download_file(&self.file_hash, &output_path).await
85 .context("Failed to download file")?;
86
87 if let Some(pb) = progress {
88 pb.finish_with_message("Download complete");
89 }
90
91 // Verify download if requested
92 if self.verify {
93 println!("Verifying download integrity...");
94
95 let downloaded_metadata = tokio::fs::metadata(&output_path).await
96 .context("Failed to read downloaded file metadata")?;
97
98 if downloaded_metadata.len() != file_info.size {
99 anyhow::bail!(
100 "File size mismatch: expected {}, got {}",
101 file_info.size,
102 downloaded_metadata.len()
103 );
104 }
105
106 // TODO: Verify file hash
107 println!("✓ Download verified successfully");
108 }
109
110 println!("✓ File downloaded successfully!");
111 println!(" Location: {:?}", output_path);
112 println!(" Size: {}", format_size(file_info.size, BINARY));
113
114 Ok(())
115 }
116 }