omne-cli 0.2.1

CLI for managing omne volumes: init, upgrade, and validate kernel and distro releases
Documentation
//! Download orchestration: GitHub release → progress bar → safe extraction.
//!
//! This is a thin glue layer (~30 lines of logic) that wires
//! `github::release_asset_body` → `indicatif::ProgressBar::wrap_read` →
//! `tarball::extract_safe`. It has no test file of its own — its behavior
//! is exercised by the integration tests in Units 8b and 9.

#![allow(dead_code)]

use std::path::Path;

use indicatif::{ProgressBar, ProgressStyle};

use crate::error::CliError;
use crate::github::GithubClient;
use crate::tarball;

/// Download a release tarball and extract it safely into `target`.
///
/// After extraction, verifies that `target/expected_top_level` exists as
/// a directory. If not, returns `CliError::TarballLayoutMismatch` —
/// an early-warning signal that the upstream release workflow changed
/// its tarball structure.
pub fn download_and_extract(
    client: &GithubClient,
    org: &str,
    repo: &str,
    tag: &str,
    target: &Path,
    expected_top_level: &str,
) -> Result<(), CliError> {
    let (content_length, body_reader) = client.release_asset_body(org, repo, tag)?;

    let bar = ProgressBar::new(content_length);
    bar.set_style(
        ProgressStyle::default_bar()
            .template("{msg} [{bar:30.cyan/dim}] {bytes}/{total_bytes} ({eta})")
            .unwrap()
            .progress_chars("█▓░"),
    );
    bar.set_message(format!("Downloading {org}/{repo} {tag}"));

    let wrapped = bar.wrap_read(body_reader);
    tarball::extract_safe(wrapped, target)?;
    bar.finish_with_message("downloaded");

    // Verify the tarball produced the expected top-level directory.
    let expected_dir = target.join(expected_top_level);
    if !expected_dir.is_dir() {
        return Err(CliError::TarballLayoutMismatch {
            expected: expected_top_level.to_string(),
            found: list_top_level_entries(target),
        });
    }

    Ok(())
}

/// List top-level directory entries for the layout mismatch error message.
fn list_top_level_entries(dir: &Path) -> Vec<String> {
    std::fs::read_dir(dir)
        .ok()
        .map(|entries| {
            entries
                .filter_map(|e| e.ok())
                .map(|e| e.file_name().to_string_lossy().into_owned())
                .collect()
        })
        .unwrap_or_default()
}