Rust · 4768 bytes Raw Blame History
1 use clap::Args;
2 use anyhow::{Context, Result};
3 use chrono::{DateTime, Local};
4 use humansize::{format_size, BINARY};
5 use tracing::info;
6
7 use crate::config::Config;
8 use crate::client::ZephyrClient;
9 use super::Command;
10
11 #[derive(Debug, Args)]
12 pub struct ListCommand {
13 /// Show detailed information
14 #[arg(short, long)]
15 long: bool,
16
17 /// Sort by column (name, size, date, chunks)
18 #[arg(short, long, default_value = "name")]
19 sort: String,
20
21 /// Reverse sort order
22 #[arg(short, long)]
23 reverse: bool,
24
25 /// Filter by name pattern
26 #[arg(short, long)]
27 filter: Option<String>,
28
29 /// Output format (table, json, csv)
30 #[arg(long, default_value = "table")]
31 format: String,
32 }
33
34 #[async_trait::async_trait]
35 impl Command for ListCommand {
36 async fn execute(&self, config: &Config) -> Result<()> {
37 info!("Listing files in ZephyrFS network");
38
39 let client = ZephyrClient::new(config);
40 let mut files = client.list_files().await
41 .context("Failed to list files")?;
42
43 // Apply filter if specified
44 if let Some(filter) = &self.filter {
45 files.retain(|f| f.name.contains(filter));
46 }
47
48 // Sort files
49 match self.sort.as_str() {
50 "name" => files.sort_by(|a, b| a.name.cmp(&b.name)),
51 "size" => files.sort_by(|a, b| a.size.cmp(&b.size)),
52 "date" => files.sort_by(|a, b| a.uploaded_at.cmp(&b.uploaded_at)),
53 "chunks" => files.sort_by(|a, b| a.chunks.cmp(&b.chunks)),
54 _ => anyhow::bail!("Invalid sort column: {}. Valid options: name, size, date, chunks", self.sort),
55 }
56
57 if self.reverse {
58 files.reverse();
59 }
60
61 if files.is_empty() {
62 println!("No files found in the network.");
63 return Ok(());
64 }
65
66 match self.format.as_str() {
67 "table" => self.print_table(&files),
68 "json" => self.print_json(&files)?,
69 "csv" => self.print_csv(&files),
70 _ => anyhow::bail!("Invalid format: {}. Valid options: table, json, csv", self.format),
71 }
72
73 println!("\nTotal files: {}", files.len());
74 let total_size: u64 = files.iter().map(|f| f.size).sum();
75 println!("Total size: {}", format_size(total_size, BINARY));
76
77 Ok(())
78 }
79 }
80
81 impl ListCommand {
82 fn print_table(&self, files: &[crate::client::FileInfo]) {
83 if self.long {
84 // Detailed view
85 println!("{:<12} {:>10} {:>8} {:>8} {:<20} {}",
86 "HASH", "SIZE", "CHUNKS", "REPLICAS", "UPLOADED", "NAME");
87 println!("{}", "-".repeat(80));
88
89 for file in files {
90 let local_time: DateTime<Local> = file.uploaded_at.into();
91 let hash_short = if file.hash.len() > 12 {
92 &file.hash[..12]
93 } else {
94 &file.hash
95 };
96
97 println!("{:<12} {:>10} {:>8} {:>8} {:<20} {}",
98 hash_short,
99 format_size(file.size, BINARY),
100 file.chunks,
101 file.replicas,
102 local_time.format("%Y-%m-%d %H:%M"),
103 file.name);
104 }
105 } else {
106 // Simple view
107 println!("{:<40} {:>10} {}", "NAME", "SIZE", "HASH");
108 println!("{}", "-".repeat(60));
109
110 for file in files {
111 let hash_short = if file.hash.len() > 8 {
112 &file.hash[..8]
113 } else {
114 &file.hash
115 };
116
117 println!("{:<40} {:>10} {}",
118 if file.name.len() > 40 {
119 format!("{}...", &file.name[..37])
120 } else {
121 file.name.clone()
122 },
123 format_size(file.size, BINARY),
124 hash_short);
125 }
126 }
127 }
128
129 fn print_json(&self, files: &[crate::client::FileInfo]) -> Result<()> {
130 let json = serde_json::to_string_pretty(files)
131 .context("Failed to serialize files to JSON")?;
132 println!("{}", json);
133 Ok(())
134 }
135
136 fn print_csv(&self, files: &[crate::client::FileInfo]) {
137 println!("name,size,hash,chunks,replicas,uploaded_at");
138 for file in files {
139 println!("{},{},{},{},{},{}",
140 file.name,
141 file.size,
142 file.hash,
143 file.chunks,
144 file.replicas,
145 file.uploaded_at.to_rfc3339());
146 }
147 }
148 }