sindri_cli/commands/
clone.rs

1use std::{fs, io::Cursor, path::Path};
2
3use flate2::read::GzDecoder;
4use regex::Regex;
5use tar::Archive;
6
7use sindri::client::SindriClient;
8
9use crate::handle_operation_error;
10
11pub fn clone(client: &SindriClient, circuit: String, directory: Option<String>) {
12    println!("{}", console::style("Cloning...").bold());
13
14    let circuit_regex =
15        Regex::new(r"^(?:([-a-zA-Z0-9_]+)\/)?([-a-zA-Z0-9_]+)(?::([-a-zA-Z0-9_.]+))?$").unwrap();
16    let circuit_name = if let Some(captures) = circuit_regex.captures(&circuit) {
17        captures
18            .get(2)
19            .map(|m| m.as_str().to_string())
20            .unwrap_or_else(|| handle_operation_error("Clone", "Invalid circuit identifier"))
21    } else {
22        handle_operation_error("Clone", "Invalid circuit identifier")
23    };
24    let output_directory = directory.unwrap_or(circuit_name.clone());
25    println!(
26        "{}",
27        console::style(format!("  ✓ Valid circuit identifier: {}", circuit)).cyan()
28    );
29
30    let download_path = {
31        let p = Path::new(&output_directory);
32        if p.is_dir() {
33            handle_operation_error("Clone", "Output directory already exists");
34        }
35        match fs::create_dir_all(p) {
36            Ok(_) => p.join("circuit.tar.gz"),
37            Err(e) => handle_operation_error("Clone", &e.to_string()),
38        }
39    };
40
41    match client.clone_circuit_blocking(&circuit, download_path.to_string_lossy().to_string()) {
42        Ok(_) => println!(
43            "{}",
44            console::style("  ✓ Successfully downloaded circuit").cyan()
45        ),
46        Err(e) => {
47            if e.to_string().contains("404") {
48                handle_operation_error(
49                    "Clone",
50                    "Circuit does not exist or you lack permission to access it.",
51                );
52            } else {
53                handle_operation_error("Clone", &e.to_string());
54            }
55        }
56    }
57
58    println!("{}", console::style("  ✓ Unpacking circuit...").cyan());
59    // Unpack the tarball
60    let downloaded = fs::read(&download_path).unwrap();
61    let cursor = Cursor::new(downloaded);
62    let gz_decoder = GzDecoder::new(cursor);
63    let mut archive = Archive::new(gz_decoder);
64
65    // Manually unpack the tarball, stripping the top-level directory
66    (|| -> Result<(), Box<dyn std::error::Error>> {
67        for entry in archive.entries()? {
68            let mut entry = entry?;
69            let path = entry.path()?;
70            if let Some(stripped) = path.iter().skip(1).collect::<std::path::PathBuf>().to_str() {
71                let output_path = Path::new(&output_directory).join(stripped);
72                if let Some(parent) = output_path.parent() {
73                    fs::create_dir_all(parent)?;
74                }
75                entry.unpack(&output_path)?;
76            }
77        }
78        Ok(())
79    })()
80    .unwrap_or_else(|e| {
81        handle_operation_error("Clone", &format!("Issue unpacking circuit: {}", e))
82    });
83
84    // Remove the download tarball
85    std::fs::remove_file(&download_path).unwrap();
86
87    println!(
88        "{}",
89        console::style("  ✓ Circuit cloned successfully!").cyan()
90    );
91    println!(
92        "\n{}",
93        console::style(format!("Circuit downloaded to: {}", output_directory)).bold()
94    );
95}