1#![allow(dead_code)]
9
10use std::path::Path;
11
12use indicatif::{ProgressBar, ProgressStyle};
13
14use crate::error::CliError;
15use crate::github::GithubClient;
16use crate::tarball;
17
18pub fn download_and_extract(
25 client: &GithubClient,
26 org: &str,
27 repo: &str,
28 tag: &str,
29 target: &Path,
30 expected_top_level: &str,
31) -> Result<(), CliError> {
32 let (content_length, body_reader) = client.release_asset_body(org, repo, tag)?;
33
34 let bar = ProgressBar::new(content_length);
35 bar.set_style(
36 ProgressStyle::default_bar()
37 .template("{msg} [{bar:30.cyan/dim}] {bytes}/{total_bytes} ({eta})")
38 .unwrap()
39 .progress_chars("█▓░"),
40 );
41 bar.set_message(format!("Downloading {org}/{repo} {tag}"));
42
43 let wrapped = bar.wrap_read(body_reader);
44 tarball::extract_safe(wrapped, target)?;
45 bar.finish_with_message("downloaded");
46
47 let expected_dir = target.join(expected_top_level);
49 if !expected_dir.is_dir() {
50 return Err(CliError::TarballLayoutMismatch {
51 expected: expected_top_level.to_string(),
52 found: list_top_level_entries(target),
53 });
54 }
55
56 Ok(())
57}
58
59fn list_top_level_entries(dir: &Path) -> Vec<String> {
61 std::fs::read_dir(dir)
62 .ok()
63 .map(|entries| {
64 entries
65 .filter_map(|e| e.ok())
66 .map(|e| e.file_name().to_string_lossy().into_owned())
67 .collect()
68 })
69 .unwrap_or_default()
70}