raps_cli/commands/object/
mod.rs1mod copy;
9mod download;
10mod upload;
11
12use anyhow::Result;
13use clap::Subcommand;
14use std::path::PathBuf;
15
16use crate::output::OutputFormat;
17use raps_kernel::prompts;
18use raps_oss::OssClient;
19
20use copy::{batch_copy_objects, batch_rename_objects, copy_object, rename_object};
21use download::{delete_object, download_object, get_signed_url, list_objects, object_info};
22use upload::{upload_batch, upload_object};
23
24#[derive(Debug, Subcommand)]
25pub enum ObjectCommands {
26 Upload {
28 bucket: Option<String>,
30
31 file: PathBuf,
33
34 #[arg(short, long)]
36 key: Option<String>,
37
38 #[arg(short, long)]
40 resume: bool,
41 },
42
43 #[command(name = "upload-batch")]
45 UploadBatch {
46 bucket: Option<String>,
48
49 files: Vec<PathBuf>,
51
52 #[arg(short, long, default_value = "4")]
54 parallel: usize,
55
56 #[arg(long)]
58 resume: bool,
59 },
60
61 Download {
63 bucket: Option<String>,
65
66 object: Option<String>,
68
69 #[arg(long = "out-file")]
71 out_file: Option<PathBuf>,
72 },
73
74 List {
76 bucket: Option<String>,
78 },
79
80 Delete {
82 bucket: Option<String>,
84
85 object: Option<String>,
87
88 #[arg(short = 'y', long)]
90 yes: bool,
91 },
92
93 SignedUrl {
95 bucket: String,
97
98 object: String,
100
101 #[arg(short, long)]
103 minutes: Option<u32>,
104 },
105
106 Info {
108 bucket: String,
110 object: String,
112 },
113
114 Copy {
116 #[arg(long)]
118 source_bucket: String,
119 #[arg(long)]
121 source_object: String,
122 #[arg(long)]
124 dest_bucket: String,
125 #[arg(long)]
127 dest_object: Option<String>,
128 },
129
130 Rename {
132 bucket: String,
134 object: String,
136 #[arg(long)]
138 new_key: String,
139 },
140
141 #[command(name = "batch-copy")]
143 BatchCopy {
144 source_bucket: String,
146 dest_bucket: String,
148 #[arg(long)]
150 prefix: Option<String>,
151 #[arg(long)]
153 keys: Option<String>,
154 },
155
156 #[command(name = "batch-rename")]
158 BatchRename {
159 bucket: String,
161 #[arg(long)]
163 from: String,
164 #[arg(long)]
166 to: String,
167 },
168}
169
170impl ObjectCommands {
171 pub async fn execute(self, client: &OssClient, output_format: OutputFormat) -> Result<()> {
172 match self {
173 ObjectCommands::Upload {
174 bucket,
175 file,
176 key,
177 resume,
178 } => upload_object(client, bucket, file, key, resume, output_format).await,
179 ObjectCommands::UploadBatch {
180 bucket,
181 files,
182 parallel,
183 resume,
184 } => upload_batch(client, bucket, files, parallel, resume, output_format).await,
185 ObjectCommands::Download {
186 bucket,
187 object,
188 out_file,
189 } => download_object(client, bucket, object, out_file, output_format).await,
190 ObjectCommands::List { bucket } => list_objects(client, bucket, output_format).await,
191 ObjectCommands::Delete {
192 bucket,
193 object,
194 yes,
195 } => delete_object(client, bucket, object, yes, output_format).await,
196 ObjectCommands::SignedUrl {
197 bucket,
198 object,
199 minutes,
200 } => get_signed_url(client, &bucket, &object, minutes, output_format).await,
201 ObjectCommands::Info { bucket, object } => {
202 object_info(client, &bucket, &object, output_format).await
203 }
204 ObjectCommands::Copy {
205 source_bucket,
206 source_object,
207 dest_bucket,
208 dest_object,
209 } => {
210 copy_object(
211 client,
212 &source_bucket,
213 &source_object,
214 &dest_bucket,
215 dest_object.as_deref(),
216 output_format,
217 )
218 .await
219 }
220 ObjectCommands::Rename {
221 bucket,
222 object,
223 new_key,
224 } => rename_object(client, &bucket, &object, &new_key, output_format).await,
225 ObjectCommands::BatchCopy {
226 source_bucket,
227 dest_bucket,
228 prefix,
229 keys,
230 } => {
231 batch_copy_objects(
232 client,
233 &source_bucket,
234 &dest_bucket,
235 prefix,
236 keys,
237 output_format,
238 )
239 .await
240 }
241 ObjectCommands::BatchRename { bucket, from, to } => {
242 batch_rename_objects(client, &bucket, &from, &to, output_format).await
243 }
244 }
245 }
246}
247
248pub(super) async fn select_bucket(client: &OssClient, provided: Option<String>) -> Result<String> {
249 match provided {
250 Some(b) => Ok(b),
251 None => {
252 let buckets = client.list_buckets().await?;
253 if buckets.is_empty() {
254 anyhow::bail!("No buckets found. Create a bucket first using 'raps bucket create'");
255 }
256
257 let bucket_keys: Vec<String> = buckets.iter().map(|b| b.bucket_key.clone()).collect();
258
259 let selection = prompts::select("Select bucket", &bucket_keys)?;
260 Ok(bucket_keys[selection].clone())
261 }
262 }
263}
264
265pub(super) fn format_size(bytes: u64) -> String {
267 const KB: u64 = 1024;
268 const MB: u64 = KB * 1024;
269 const GB: u64 = MB * 1024;
270
271 if bytes >= GB {
272 format!("{:.2} GB", bytes as f64 / GB as f64)
273 } else if bytes >= MB {
274 format!("{:.2} MB", bytes as f64 / MB as f64)
275 } else if bytes >= KB {
276 format!("{:.2} KB", bytes as f64 / KB as f64)
277 } else {
278 format!("{} B", bytes)
279 }
280}
281
282pub(super) fn truncate_str(s: &str, max_len: usize) -> String {
284 if s.len() <= max_len {
285 s.to_string()
286 } else {
287 format!("{}...", &s[..max_len - 3])
288 }
289}