Skip to main content

zoi_cli/cmd/
download.rs

1//! Implementation of the `download` command for downloading package archives.
2
3use std::path::PathBuf;
4
5use anyhow::{Result, anyhow};
6use colored::Colorize;
7use zoi_core::cache;
8use zoi_install::resolver::resolve_dependency_graph;
9use zoi_install::util;
10
11/// Type of download to perform.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum DownloadType {
14    /// Download a pre-built archive (.zpa).
15    Archive,
16    /// Download a source bundle (.zsa).
17    Source
18}
19
20/// Runs the `download` command to download a package archive or source bundle.
21///
22/// # Errors
23///
24/// Returns an error if:
25/// - The package cannot be resolved.
26/// - The requested download type (Archive or Source) is not available for the
27///   package.
28/// - The download fails or file system operations fail.
29pub fn run(
30    package_source: &str,
31    download_type: DownloadType,
32    output_dir: Option<PathBuf>
33) -> Result<()> {
34    println!(
35        "{} Resolving package '{}' for download...",
36        "::".bold().blue(),
37        package_source.cyan()
38    );
39
40    let (graph, _) = resolve_dependency_graph(
41        &[package_source.to_string()],
42        None,
43        false,
44        true,
45        false,
46        None,
47        true,
48        None
49    )?;
50
51    if graph.nodes.is_empty() {
52        return Err(anyhow!("Could not resolve package '{package_source}'"));
53    }
54
55    // Find the direct package node
56    let node = graph
57        .nodes
58        .values()
59        .find(|n| matches!(n.reason, zoi_core::types::InstallReason::Direct))
60        .ok_or_else(|| {
61            anyhow!("Could not find target package in resolution graph")
62        })?;
63
64    println!(
65        "{} Resolved to {} v{}",
66        "::".bold().green(),
67        node.pkg.name.cyan(),
68        node.version.yellow()
69    );
70
71    let info = if download_type == DownloadType::Source {
72        util::find_source_bundle_info(node)?.ok_or_else(|| {
73            anyhow!(
74                "No source bundle (.zsa) information found for this package \
75                 in the registry."
76            )
77        })?
78    } else {
79        util::find_prebuilt_info(node)?.ok_or_else(|| {
80            anyhow!(
81                "No pre-built archive (.zpa) information found for this \
82                 package in the registry."
83            )
84        })?
85    };
86
87    let filename = info
88        .final_url
89        .split('/')
90        .next_back()
91        .unwrap_or("package.archive");
92
93    let dest_path = if let Some(dir) = output_dir {
94        std::fs::create_dir_all(&dir)?;
95        dir.join(filename)
96    } else {
97        let cache_root = cache::get_archive_cache_root()?;
98        std::fs::create_dir_all(&cache_root)?;
99        cache_root.join(filename)
100    };
101
102    println!(
103        "{} Downloading to: {}",
104        "::".bold().blue(),
105        dest_path.display()
106    );
107
108    let (down_size, _) = util::get_package_sizes(
109        &node.pkg,
110        &node.registry_handle,
111        &node.version
112    );
113
114    util::download_file_with_progress(
115        &info.final_url,
116        &dest_path,
117        None,
118        Some(down_size)
119    )?;
120
121    println!(
122        "{} Successfully downloaded: {}",
123        "::".bold().green(),
124        dest_path.display()
125    );
126
127    Ok(())
128}