1use crate::error::{Error, Result};
4use flate2::read::GzDecoder;
5use std::fs::File;
6use std::path::{Path, PathBuf};
7use tar::Archive;
8use tracing::{debug, info};
9
10pub fn extract_bottle(
15 bottle_path: impl AsRef<Path>,
16 cellar: impl AsRef<Path>,
17) -> Result<PathBuf> {
18 let bottle_path = bottle_path.as_ref();
19 let cellar = cellar.as_ref();
20
21 debug!("Extracting {} to {}", bottle_path.display(), cellar.display());
22
23 let file = File::open(bottle_path)?;
24 let decoder = GzDecoder::new(file);
25 let mut archive = Archive::new(decoder);
26
27 std::fs::create_dir_all(cellar)?;
29
30 let mut install_path: Option<PathBuf> = None;
32
33 for entry in archive.entries()? {
34 let mut entry = entry?;
35 let path = entry.path()?;
36
37 if install_path.is_none() {
39 if let Some(component) = path.components().next() {
40 let pkg_name = component.as_os_str().to_string_lossy();
41 if let Some(second) = path.components().nth(1) {
44 let version = second.as_os_str().to_string_lossy();
45 install_path = Some(cellar.join(&*pkg_name).join(&*version));
46 }
47 }
48 }
49
50 let dest = cellar.join(&path);
52
53 if let Some(parent) = dest.parent() {
55 std::fs::create_dir_all(parent)?;
56 }
57
58 entry.unpack(&dest)?;
60 }
61
62 let install_path = install_path.ok_or_else(|| {
63 Error::InvalidBottle("Could not determine install path from bottle".to_string())
64 })?;
65
66 info!("Extracted to {}", install_path.display());
67 Ok(install_path)
68}
69
70pub fn remove_package(cellar: impl AsRef<Path>, name: &str, version: &str) -> Result<()> {
72 let package_path = cellar.as_ref().join(name).join(version);
73
74 if !package_path.exists() {
75 return Err(Error::PackageNotFound(format!("{}/{}", name, version)));
76 }
77
78 debug!("Removing {}", package_path.display());
79 std::fs::remove_dir_all(&package_path)?;
80
81 let parent = cellar.as_ref().join(name);
83 if parent.read_dir()?.next().is_none() {
84 std::fs::remove_dir(&parent)?;
85 }
86
87 info!("Removed {}-{}", name, version);
88 Ok(())
89}