mod copy;
mod download;
mod upload;
use anyhow::Result;
use clap::Subcommand;
use std::path::PathBuf;
use crate::output::OutputFormat;
use raps_kernel::prompts;
use raps_oss::OssClient;
use copy::{batch_copy_objects, batch_rename_objects, copy_object, rename_object};
use download::{delete_object, download_object, get_signed_url, list_objects, object_info};
use upload::{upload_batch, upload_object};
#[derive(Debug, Subcommand)]
pub enum ObjectCommands {
Upload {
bucket: Option<String>,
file: PathBuf,
#[arg(short, long)]
key: Option<String>,
#[arg(short, long)]
resume: bool,
},
#[command(name = "upload-batch")]
UploadBatch {
bucket: Option<String>,
files: Vec<PathBuf>,
#[arg(short, long, default_value = "4")]
parallel: usize,
#[arg(long)]
resume: bool,
},
Download {
bucket: Option<String>,
object: Option<String>,
#[arg(long = "out-file")]
out_file: Option<PathBuf>,
},
List {
bucket: Option<String>,
},
Delete {
bucket: Option<String>,
object: Option<String>,
#[arg(short = 'y', long)]
yes: bool,
},
SignedUrl {
bucket: String,
object: String,
#[arg(short, long)]
minutes: Option<u32>,
},
Info {
bucket: String,
object: String,
},
Copy {
#[arg(long)]
source_bucket: String,
#[arg(long)]
source_object: String,
#[arg(long)]
dest_bucket: String,
#[arg(long)]
dest_object: Option<String>,
},
Rename {
bucket: String,
object: String,
#[arg(long)]
new_key: String,
},
#[command(name = "batch-copy")]
BatchCopy {
source_bucket: String,
dest_bucket: String,
#[arg(long)]
prefix: Option<String>,
#[arg(long)]
keys: Option<String>,
},
#[command(name = "batch-rename")]
BatchRename {
bucket: String,
#[arg(long)]
from: String,
#[arg(long)]
to: String,
},
}
impl ObjectCommands {
pub async fn execute(self, client: &OssClient, output_format: OutputFormat) -> Result<()> {
match self {
ObjectCommands::Upload {
bucket,
file,
key,
resume,
} => upload_object(client, bucket, file, key, resume, output_format).await,
ObjectCommands::UploadBatch {
bucket,
files,
parallel,
resume,
} => upload_batch(client, bucket, files, parallel, resume, output_format).await,
ObjectCommands::Download {
bucket,
object,
out_file,
} => download_object(client, bucket, object, out_file, output_format).await,
ObjectCommands::List { bucket } => list_objects(client, bucket, output_format).await,
ObjectCommands::Delete {
bucket,
object,
yes,
} => delete_object(client, bucket, object, yes, output_format).await,
ObjectCommands::SignedUrl {
bucket,
object,
minutes,
} => get_signed_url(client, &bucket, &object, minutes, output_format).await,
ObjectCommands::Info { bucket, object } => {
object_info(client, &bucket, &object, output_format).await
}
ObjectCommands::Copy {
source_bucket,
source_object,
dest_bucket,
dest_object,
} => {
copy_object(
client,
&source_bucket,
&source_object,
&dest_bucket,
dest_object.as_deref(),
output_format,
)
.await
}
ObjectCommands::Rename {
bucket,
object,
new_key,
} => rename_object(client, &bucket, &object, &new_key, output_format).await,
ObjectCommands::BatchCopy {
source_bucket,
dest_bucket,
prefix,
keys,
} => {
batch_copy_objects(
client,
&source_bucket,
&dest_bucket,
prefix,
keys,
output_format,
)
.await
}
ObjectCommands::BatchRename { bucket, from, to } => {
batch_rename_objects(client, &bucket, &from, &to, output_format).await
}
}
}
}
pub(super) async fn select_bucket(client: &OssClient, provided: Option<String>) -> Result<String> {
match provided {
Some(b) => Ok(b),
None => {
let buckets = client.list_buckets().await?;
if buckets.is_empty() {
anyhow::bail!("No buckets found. Create a bucket first using 'raps bucket create'");
}
let bucket_keys: Vec<String> = buckets.iter().map(|b| b.bucket_key.clone()).collect();
let selection = prompts::select("Select bucket", &bucket_keys)?;
Ok(bucket_keys[selection].clone())
}
}
}
pub(super) fn format_size(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!("{} B", bytes)
}
}
pub(super) fn truncate_str(s: &str, max_len: usize) -> String {
if s.len() <= max_len {
s.to_string()
} else {
format!("{}...", &s[..max_len - 3])
}
}