use crate::eg::{Result, EgError};
use flate2::read::GzDecoder;
use std::fs;
use std::io::Read;
use std::path::{Path, PathBuf};
use tar::Archive;
pub struct CrateExtractor;
impl CrateExtractor {
pub fn new() -> Self {
Self
}
pub async fn extract_crate_to_cache(
&self,
crate_path: &Path,
extraction_path: &PathBuf,
) -> Result<PathBuf> {
let file = fs::File::open(crate_path)?;
self.extract_from_reader(file, extraction_path).await?;
Ok(extraction_path.clone())
}
pub async fn download_and_extract_crate(
&self,
crate_name: &str,
version: &str,
extraction_path: &PathBuf,
) -> Result<PathBuf> {
let download_url = format!(
"https://static.crates.io/crates/{}/{}-{}.crate",
crate_name, crate_name, version
);
let response = reqwest::get(&download_url).await?;
if !response.status().is_success() {
return Err(EgError::Other(format!(
"Failed to download crate: HTTP {}",
response.status()
)));
}
let bytes = response.bytes().await?;
self.extract_from_reader(std::io::Cursor::new(bytes), extraction_path).await?;
Ok(extraction_path.clone())
}
async fn extract_from_reader<R: Read>(
&self,
reader: R,
extraction_path: &PathBuf,
) -> Result<()> {
fs::create_dir_all(extraction_path)?;
let gz_decoder = GzDecoder::new(reader);
let mut archive = Archive::new(gz_decoder);
archive.unpack(extraction_path)
.map_err(|e| EgError::ExtractionError(format!("Failed to extract archive: {}", e)))?;
self.flatten_extraction(extraction_path)?;
Ok(())
}
fn flatten_extraction(&self, extraction_path: &PathBuf) -> Result<()> {
let entries: Vec<_> = fs::read_dir(extraction_path)?
.collect::<std::result::Result<Vec<_>, _>>()?;
if entries.len() == 1 {
let entry = &entries[0];
if entry.file_type()?.is_dir() {
let inner_dir = entry.path();
for inner_entry in fs::read_dir(&inner_dir)? {
let inner_entry = inner_entry?;
let src = inner_entry.path();
let dst = extraction_path.join(inner_entry.file_name());
if src.is_dir() {
self.move_dir(&src, &dst)?;
} else {
fs::rename(&src, &dst)?;
}
}
fs::remove_dir(&inner_dir)?;
}
}
Ok(())
}
fn move_dir(&self, src: &Path, dst: &Path) -> Result<()> {
fs::create_dir_all(dst)?;
for entry in fs::read_dir(src)? {
let entry = entry?;
let src_path = entry.path();
let dst_path = dst.join(entry.file_name());
if src_path.is_dir() {
self.move_dir(&src_path, &dst_path)?;
} else {
fs::rename(&src_path, &dst_path)?;
}
}
fs::remove_dir(src)?;
Ok(())
}
}