diff --git a/Cargo.toml b/Cargo.toml index 67a15a0..e827691 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,8 @@ indicatif = "0.17" indicatif-log-bridge = "0.2.3" env_logger = "0.11.8" futures-util = { version = "0.3.31", features = ["tokio-io"] } +tar = "0.4" +xz2 = "0.1" [dev-dependencies] tempfile = "3.10.1" diff --git a/src/get.rs b/src/get.rs index 41a239d..74318e4 100644 --- a/src/get.rs +++ b/src/get.rs @@ -62,6 +62,28 @@ use std::fs::File; use std::io::Write; use futures_util::StreamExt; +use flate2::read::GzDecoder; +use xz2::read::XzDecoder; +use tar::Archive; + +fn extract_archive(path: &Path, dest: &Path) -> Result<(), Box> { + let file = File::open(path)?; + let filename = path.file_name().unwrap().to_string_lossy(); + + if filename.ends_with(".tar.gz") || filename.ends_with(".tgz") { + let tar = GzDecoder::new(file); + let mut archive = Archive::new(tar); + archive.unpack(dest)?; + } else if filename.ends_with(".tar.xz") || filename.ends_with(".txz") { + let tar = XzDecoder::new(file); + let mut archive = Archive::new(tar); + archive.unpack(dest)?; + } else { + return Err(format!("Unsupported archive format: {}", filename).into()); + } + + Ok(()) +} fn checkout_pristine_tar(package_dir: &Path, filename: &str) -> Result<(), Box> { let output = Command::new("pristine-tar") @@ -181,6 +203,42 @@ async fn fetch_orig_tarball(info: &PackageInfo, cwd: Option<&Path>, progress: Pr Ok(()) } +async fn fetch_archive_sources(info: &PackageInfo, cwd: Option<&Path>, progress: ProgressCallback<'_>) -> Result<(), Box> { + let package_dir = if let Some(path) = cwd { + path.join(&info.stanza.package) + } else { + Path::new(&info.stanza.package).to_path_buf() + }; + + std::fs::create_dir_all(&package_dir)?; + + for file in &info.stanza.files { + let url = format!("{}/{}", info.archive_url, file.name); + download_file_checksum(&url, &file.sha256, &package_dir, progress).await?; + } + + // Extract the debian tarball or diff + let debian_file = info.stanza.files.iter().find(|f| { + f.name.contains(".debian.tar.") || f.name.contains(".diff.gz") + }); + + if let Some(file) = debian_file { + let path = package_dir.join(&file.name); + let extract_dir = package_dir.join(&info.stanza.package); + + if file.name.ends_with(".tar.xz") || file.name.ends_with(".tar.gz") { + if let Err(e) = extract_archive(&path, &extract_dir) { + return Err(format!("Failed to extract {}: {}", file.name, e).into()); + } + } + + // Remove archive after extraction + std::fs::remove_file(&path)?; + } + + Ok(()) +} + pub async fn get(package: &str, _version: &str, series: &str, pocket: &str, _ppa: &str, cwd: Option<&Path>, progress: ProgressCallback<'_>) -> Result<(), Box> { if let Some(cb) = progress { cb(&format!("Resolving package info for {}...", package), "", 0, 0); @@ -205,7 +263,10 @@ pub async fn get(package: &str, _version: &str, series: &str, pocket: &str, _ppa } fetch_orig_tarball(&info, Some(&package_dir), progress).await?; } else { - return Err(format!("No VCS URL found for package {}", package).into()); + if let Some(cb) = progress { + cb("Downloading from archive...", "", 0, 0); + } + fetch_archive_sources(&info, Some(cwd.unwrap_or(Path::new("."))), progress).await?; } Ok(()) @@ -227,9 +288,9 @@ mod tests { let package_dir = cwd.join(package); assert!(package_dir.exists(), "Package directory not created"); - let git_repo_dir = package_dir.join(package); - assert!(git_repo_dir.exists(), "Package git repo directory not created"); - assert!(git_repo_dir.join(".git").exists(), "Git repo not cloned"); + let package_source_dir = package_dir.join(package); + assert!(package_source_dir.exists(), "Package git repo directory not created"); + assert!(package_source_dir.join("debian").exists(), "debian directory not present"); // Check for orig tarball in package dir let mut found_tarball = false; @@ -255,12 +316,7 @@ mod tests { } #[tokio::test] - #[should_panic(expected = "No VCS URL found for package agg")] - async fn test_get_agg_svn_should_fail() { - // agg uses Vcs-Svn, which is not supported. - // We expect this to fail with "No VCS URL found". - let temp_dir = tempfile::tempdir().unwrap(); - let cwd = temp_dir.path(); - get("agg", "", "sid", "", "", Some(cwd), None).await.unwrap(); + async fn test_get_agg_svn_fallback_ok() { + test_get_package_end_to_end("agg", "sid").await; } }