use crate::file_attr::{RelativeFileAttr, RelativeFileAttrBuilder};
use relative_path::RelativePathBuf;
use std::fs::File;
use std::io::{self, BufRead, BufReader};
use std::path::Path;
#[derive(Debug, thiserror::Error)]
pub enum PackageError {
#[error("{0}")]
Io(#[from] io::Error),
#[error("size error for line: {0}")]
Size(String),
}
fn parse_packages_file_lines(
lines: impl Iterator<Item = io::Result<String>>,
) -> Result<Vec<RelativeFileAttr>, PackageError> {
let mut files = Vec::new();
let mut current_package = RelativeFileAttrBuilder::default();
for line in lines {
let line = line?;
if line.starts_with(' ') {
continue; }
if line.is_empty() {
files.push(current_package.build().unwrap());
current_package = RelativeFileAttrBuilder::default();
continue;
}
if let Some((key, value)) = line.split_once(':') {
let value = value.trim();
match key {
"Filename" => {
current_package.path(RelativePathBuf::from(value));
}
"Size" => {
let size = value
.parse::<usize>()
.map_err(|_| PackageError::Size(line))?;
current_package.size(size);
}
"MD5sum" => {
current_package.md5sum(value.to_string());
}
"SHA256" => {
current_package.sha256sum(value.to_string());
}
"SHA512" => {
current_package.sha512sum(value.to_string());
}
_ => {}
}
}
}
Ok(files)
}
fn parse_packages_file_none(path: &Path) -> Result<Vec<RelativeFileAttr>, PackageError> {
let file = File::open(path)?;
let reader = BufReader::new(file);
parse_packages_file_lines(reader.lines())
}
fn parse_packages_file_gz(path: &Path) -> Result<Vec<RelativeFileAttr>, PackageError> {
let file = File::open(path)?;
let dec = flate2::read::GzDecoder::new(file);
let reader = BufReader::new(dec);
parse_packages_file_lines(reader.lines())
}
fn parse_packages_file_xz(path: &Path) -> Result<Vec<RelativeFileAttr>, PackageError> {
let file = File::open(path)?;
let dec = xz2::read::XzDecoder::new(file);
let reader = BufReader::new(dec);
parse_packages_file_lines(reader.lines())
}
pub fn parse_packages_file(path: &Path) -> Result<Vec<RelativeFileAttr>, PackageError> {
match path.extension().and_then(|s| s.to_str()) {
None => parse_packages_file_none(path),
Some("gz") => parse_packages_file_gz(path),
Some("xz") => parse_packages_file_xz(path),
_ => panic!("unsupported extension: {:?}", path) }
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use flate2::write::GzEncoder;
use flate2::Compression;
use xz2::write::XzEncoder;
use tempfile::NamedTempFile;
#[test]
fn test_parse_packages_file() {
let packages_content = r#"Package: 0ad
Version: 0.0.26-3
Architecture: amd64
Description: Real-time strategy game of ancient warfare
Filename: pool/main/0/0ad/0ad_0.0.26-3_amd64.deb
Size: 7891488
MD5sum: 4d471183a39a3a11d00cd35bf9f6803d
SHA256: 3a2118df47bf3f04285649f0455c2fc6fe2dc7f0b237073038aa00af41f0d5f2
Package: 0ad-data
Version: 0.0.26-1
Architecture: all
Description: Real-time strategy game of ancient warfare (data files)
Filename: pool/main/0/0ad-data/0ad-data_0.0.26-1_all.deb
Size: 1377557908
MD5sum: fc5ed8a20ce1861950c7ed3a5a615be0
SHA256: 53745ae74d05bccf6783400fa98f3932b21729ab9d2e86151aa2c331c3455178
"#;
let mut test_file = NamedTempFile::new().unwrap();
test_file.write_all(packages_content.as_bytes()).unwrap();
test_file.flush().unwrap();
let result = parse_packages_file_none(test_file.path()).unwrap();
assert_eq!(result.len(), 2);
let first_file = &result[0];
assert_eq!(first_file.path, "pool/main/0/0ad/0ad_0.0.26-3_amd64.deb");
assert_eq!(first_file.size, Some(7891488));
assert_eq!(
first_file.md5sum,
Some("4d471183a39a3a11d00cd35bf9f6803d".to_string())
);
assert_eq!(
first_file.sha256sum,
Some("3a2118df47bf3f04285649f0455c2fc6fe2dc7f0b237073038aa00af41f0d5f2".to_string())
);
assert_eq!(first_file.sha512sum, None);
let second_file = &result[1];
assert_eq!(
second_file.path,
"pool/main/0/0ad-data/0ad-data_0.0.26-1_all.deb"
);
assert_eq!(second_file.size, Some(1377557908));
assert_eq!(
second_file.md5sum,
Some("fc5ed8a20ce1861950c7ed3a5a615be0".to_string())
);
assert_eq!(
second_file.sha256sum,
Some("53745ae74d05bccf6783400fa98f3932b21729ab9d2e86151aa2c331c3455178".to_string())
);
assert_eq!(second_file.sha512sum, None);
}
#[test]
fn test_parse_packages_file_empty() {
let packages_content = "";
let mut test_file = NamedTempFile::new().unwrap();
test_file.write_all(packages_content.as_bytes()).unwrap();
test_file.flush().unwrap();
let result = parse_packages_file_none(test_file.path()).unwrap();
assert_eq!(result.len(), 0);
}
#[test]
fn test_parse_packages_file_gz() {
let packages_content = r#"Package: 0ad
Version: 0.0.26-3
Architecture: amd64
Description: Real-time strategy game of ancient warfare
Filename: pool/main/0/0ad/0ad_0.0.26-3_amd64.deb
Size: 7891488
MD5sum: 4d471183a39a3a11d00cd35bf9f6803d
SHA256: 3a2118df47bf3f04285649f0455c2fc6fe2dc7f0b237073038aa00af41f0d5f2
Package: 0ad-data
Version: 0.0.26-1
Architecture: all
Description: Real-time strategy game of ancient warfare (data files)
Filename: pool/main/0/0ad-data/0ad-data_0.0.26-1_all.deb
Size: 1377557908
MD5sum: fc5ed8a20ce1861950c7ed3a5a615be0
SHA256: 53745ae74d05bccf6783400fa98f3932b21729ab9d2e86151aa2c331c3455178
"#;
let mut test_file = NamedTempFile::new().unwrap();
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder.write_all(packages_content.as_bytes()).unwrap();
let compressed_data = encoder.finish().unwrap();
test_file.write_all(&compressed_data).unwrap();
test_file.flush().unwrap();
let result = parse_packages_file_gz(test_file.path()).unwrap();
assert_eq!(result.len(), 2);
let first_file = &result[0];
assert_eq!(first_file.path, "pool/main/0/0ad/0ad_0.0.26-3_amd64.deb");
assert_eq!(first_file.size, Some(7891488));
assert_eq!(
first_file.md5sum,
Some("4d471183a39a3a11d00cd35bf9f6803d".to_string())
);
assert_eq!(
first_file.sha256sum,
Some("3a2118df47bf3f04285649f0455c2fc6fe2dc7f0b237073038aa00af41f0d5f2".to_string())
);
assert_eq!(first_file.sha512sum, None);
let second_file = &result[1];
assert_eq!(
second_file.path,
"pool/main/0/0ad-data/0ad-data_0.0.26-1_all.deb"
);
assert_eq!(second_file.size, Some(1377557908));
assert_eq!(
second_file.md5sum,
Some("fc5ed8a20ce1861950c7ed3a5a615be0".to_string())
);
assert_eq!(
second_file.sha256sum,
Some("53745ae74d05bccf6783400fa98f3932b21729ab9d2e86151aa2c331c3455178".to_string())
);
assert_eq!(second_file.sha512sum, None);
}
#[test]
fn test_parse_packages_file_xz() {
let packages_content = r#"Package: 0ad
Version: 0.0.26-3
Architecture: amd64
Description: Real-time strategy game of ancient warfare
Filename: pool/main/0/0ad/0ad_0.0.26-3_amd64.deb
Size: 7891488
MD5sum: 4d471183a39a3a11d00cd35bf9f6803d
SHA256: 3a2118df47bf3f04285649f0455c2fc6fe2dc7f0b237073038aa00af41f0d5f2
Package: 0ad-data
Version: 0.0.26-1
Architecture: all
Description: Real-time strategy game of ancient warfare (data files)
Filename: pool/main/0/0ad-data/0ad-data_0.0.26-1_all.deb
Size: 1377557908
MD5sum: fc5ed8a20ce1861950c7ed3a5a615be0
SHA256: 53745ae74d05bccf6783400fa98f3932b21729ab9d2e86151aa2c331c3455178
"#;
let mut test_file = NamedTempFile::new().unwrap();
let mut encoder = XzEncoder::new(Vec::new(), 6);
encoder.write_all(packages_content.as_bytes()).unwrap();
let compressed_data = encoder.finish().unwrap();
test_file.write_all(&compressed_data).unwrap();
test_file.flush().unwrap();
let result = parse_packages_file_xz(test_file.path()).unwrap();
assert_eq!(result.len(), 2);
let first_file = &result[0];
assert_eq!(first_file.path, "pool/main/0/0ad/0ad_0.0.26-3_amd64.deb");
assert_eq!(first_file.size, Some(7891488));
assert_eq!(
first_file.md5sum,
Some("4d471183a39a3a11d00cd35bf9f6803d".to_string())
);
assert_eq!(
first_file.sha256sum,
Some("3a2118df47bf3f04285649f0455c2fc6fe2dc7f0b237073038aa00af41f0d5f2".to_string())
);
assert_eq!(first_file.sha512sum, None);
let second_file = &result[1];
assert_eq!(
second_file.path,
"pool/main/0/0ad-data/0ad-data_0.0.26-1_all.deb"
);
assert_eq!(second_file.size, Some(1377557908));
assert_eq!(
second_file.md5sum,
Some("fc5ed8a20ce1861950c7ed3a5a615be0".to_string())
);
assert_eq!(
second_file.sha256sum,
Some("53745ae74d05bccf6783400fa98f3932b21729ab9d2e86151aa2c331c3455178".to_string())
);
assert_eq!(second_file.sha512sum, None);
}
}