use std::path::{Path, PathBuf};
use std::sync::Arc;
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
use color_eyre::eyre::{self, Context, Result};
use color_eyre::{Help, SectionExt};
use futures::StreamExt;
use sdk::Bundle;
use tokio::fs;
use crate::cmd::util::resolve_bundle_paths;
pub(crate) fn command_definition() -> Command {
Command::new("list")
.about("List the contents of one or multiple bundles.")
.arg(
Arg::new("json")
.long("json")
.action(ArgAction::SetTrue)
.help("Print machine-readable JSON"),
)
.arg(
Arg::new("bundle")
.required(true)
.action(ArgAction::Append)
.value_parser(value_parser!(PathBuf))
.help(
"Path to the bundle(s) to read. If this points to a directory instead \
of a file, all files in that directory will be checked.",
),
)
}
#[derive(Copy, Clone, Debug)]
enum OutputFormat {
Text,
}
fn format_byte_size(size: usize) -> String {
if size < 1024 {
format!("{size} Bytes")
} else if size < 1024 * 1024 {
format!("{} kB", size / 1024)
} else if size < 1024 * 1024 * 1024 {
format!("{} MB", size / (1024 * 1024))
} else {
format!("{} GB", size / (1024 * 1024 * 1024))
}
}
#[tracing::instrument(skip(ctx))]
async fn print_bundle_contents
(ctx: &sdk::Context, path: P, fmt: OutputFormat) -> Result<()>
where
P: AsRef + std::fmt::Debug,
{
let p = path.as_ref();
let bundle = {
let binary = fs::read(p).await?;
let name = Bundle::get_name_from_path(ctx, p);
Bundle::from_binary(ctx, name, binary)?
};
match fmt {
OutputFormat::Text => {
println!(
"Bundle: {} ({:016x})",
bundle.name().display(),
bundle.name()
);
for f in bundle.files().iter() {
if f.variants().len() != 1 {
let err = eyre::eyre!("Expected exactly one version for this file.")
.with_section(|| f.variants().len().to_string().header("Bundle:"))
.with_section(|| bundle.name().display().header("Bundle:"));
tracing::error!("{:#}", err);
}
let v = &f.variants()[0];
println!(
"\t{}.{}: {} ({})",
f.base_name().display(),
f.file_type().ext_name(),
format_byte_size(v.size()),
v.size()
);
}
}
}
Ok(())
}
#[tracing::instrument(skip_all)]
pub(crate) async fn run(ctx: sdk::Context, matches: &ArgMatches) -> Result<()> {
let bundles = matches
.get_many::("bundle")
.unwrap_or_default()
.cloned();
let paths = resolve_bundle_paths(bundles);
let fmt = if matches.get_flag("json") {
unimplemented!("JSON output is not implemented yet");
} else {
OutputFormat::Text
};
let ctx = Arc::new(ctx);
paths
.for_each_concurrent(10, |p| async {
let ctx = ctx.clone();
async move {
if let Err(err) = print_bundle_contents(&ctx, &p, fmt)
.await
.wrap_err_with(|| format!("Failed to list contents of bundle {}", p.display()))
{
tracing::error!("{err:?}");
}
}
.await;
})
.await;
Ok(())
}