use anyhow::Result;
use owo_colors::OwoColorize;
use crate::api::client::ApiClient;
use crate::output::{self, Format};
use crate::render::sanitise;
use crate::render::table::{Cell, Table};
use crate::render::theme;
use crate::render::time;
pub async fn list(
client: &ApiClient,
project_reference: &str,
service_reference: &str,
path: Option<&str>,
format: Format,
) -> Result<()> {
let (_, service) = client.locate(project_reference, service_reference).await?;
let answer = client.list_files(&service.id, path).await?;
if format.answered_in_json(&answer.raw) {
return Ok(());
}
let listing = &answer.body;
println!(
" {}",
sanitise::plain(&listing.path).style(theme::strong())
);
let mut table = Table::new(&["nombre", "tamaño", "modificado"]);
for entry in &listing.entries {
let directory = entry.kind == "directory";
table.push(vec![
Cell::styled(
if directory {
format!("{}/", sanitise::plain(&entry.name))
} else {
sanitise::plain(&entry.name)
},
if directory {
theme::accent()
} else {
theme::strong()
},
),
Cell::plain(if directory {
"—".to_owned()
} else {
size_word(entry.size_bytes)
}),
Cell::styled(
time::relative_time(&entry.modified_at).unwrap_or_default(),
theme::muted(),
),
]);
}
table.print_or("Esta carpeta está vacía.");
Ok(())
}
pub async fn cat(
client: &ApiClient,
project_reference: &str,
service_reference: &str,
path: &str,
format: Format,
) -> Result<()> {
let (_, service) = client.locate(project_reference, service_reference).await?;
let answer = client.preview_file(&service.id, path).await?;
if format.answered_in_json(&answer.raw) {
return Ok(());
}
let preview = &answer.body;
match &preview.text {
Some(text) => {
print!("{}", sanitise::with_colour(text));
if !text.ends_with('\n') {
println!();
}
if preview.truncated {
eprintln!(
" {}",
format!(
"Solo los primeros 256 KB de {}: `fundaia files get` lo descarga entero.",
size_word(preview.size_bytes)
)
.style(theme::warning())
);
}
}
None => eprintln!(
" {}",
format!(
"Es un archivo binario de {}: `fundaia files get` lo descarga.",
size_word(preview.size_bytes)
)
.style(theme::muted())
),
}
Ok(())
}
pub async fn get(
client: &ApiClient,
project_reference: &str,
service_reference: &str,
path: &str,
into: Option<&str>,
format: Format,
) -> Result<()> {
let (_, service) = client.locate(project_reference, service_reference).await?;
let name = path
.rsplit('/')
.next()
.filter(|name| !name.is_empty())
.unwrap_or("archivo");
let target = into.unwrap_or(name).to_owned();
let bytes = client
.download_file(&service.id, path, std::path::Path::new(&target))
.await?;
if format.is_json() {
output::emit(&serde_json::json!({ "path": path, "file": target, "bytes": bytes }));
return Ok(());
}
println!(
" {} {} {}",
theme::CHECK.style(theme::success()),
target.style(theme::strong()),
format!("({})", size_word(bytes)).style(theme::muted())
);
Ok(())
}
fn size_word(bytes: u64) -> String {
const KB: f64 = 1024.0;
let size = bytes as f64;
if size < KB {
format!("{bytes} B")
} else if size < KB * KB {
format!("{:.1} KB", size / KB)
} else if size < KB * KB * KB {
format!("{:.1} MB", size / KB / KB)
} else {
format!("{:.1} GB", size / KB / KB / KB)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_should_say_a_small_file_in_bytes() {
assert_eq!(size_word(512), "512 B");
}
#[test]
fn it_should_say_a_large_file_in_megabytes() {
assert_eq!(size_word(5 * 1024 * 1024), "5.0 MB");
}
}