diff --git a/src/distro_info.rs b/src/distro_info.rs index 8ba44fc..65dd029 100644 --- a/src/distro_info.rs +++ b/src/distro_info.rs @@ -4,6 +4,23 @@ use serde::Deserialize; use std::error::Error; use std::path::Path; +#[derive(Debug, Clone)] +/// Information about a specific distribution series +pub struct SeriesInformation { + /// Distribution series + pub series: String, + /// Codename, i.e. full name of series + pub codename: String, + /// Series version as numbers + pub version: Option, + /// Series creation date + pub created: NaiveDate, + /// Series release date + pub release: Option, + /// Series end-of-life date + pub eol: Option, +} + #[derive(Debug, Deserialize)] struct SeriesInfo { local: String, @@ -28,7 +45,7 @@ lazy_static! { static ref DATA: Data = serde_yaml::from_str(DATA_YAML).unwrap(); } -fn parse_series_csv(content: &str) -> Result, Box> { +fn parse_series_csv(content: &str) -> Result, Box> { let mut rdr = csv::ReaderBuilder::new() .flexible(true) .from_reader(content.as_bytes()); @@ -38,29 +55,63 @@ fn parse_series_csv(content: &str) -> Result, Box> { .iter() .position(|h| h == "series") .ok_or("Column 'series' not found")?; + let codename_idx = headers + .iter() + .position(|h| h == "codename") + .ok_or("Column 'codename' not found")?; + let version_idx = headers + .iter() + .position(|h| h == "version") + .ok_or("Column 'version' not found")?; let created_idx = headers .iter() .position(|h| h == "created") .ok_or("Column 'created' not found")?; + let release_idx = headers + .iter() + .position(|h| h == "release") + .ok_or("Column 'release' not found")?; + let eol_idx = headers + .iter() + .position(|h| h == "eol") + .ok_or("Column 'eol' not found")?; + + let mut series_info_list = Vec::new(); - let mut entries = Vec::new(); for result in rdr.records() { let record = result?; - if let (Some(s), Some(c)) = (record.get(series_idx), record.get(created_idx)) - && let Ok(date) = NaiveDate::parse_from_str(c, "%Y-%m-%d") - { - entries.push((s.to_string(), date)); - } + let series = record.get(series_idx).unwrap().to_string(); + let codename = record.get(codename_idx).unwrap().to_string(); + let version = record.get(version_idx).map(|s| s.to_string()); + let created = record + .get(created_idx) + .map(|date_str| NaiveDate::parse_from_str(date_str, "%Y-%m-%d").unwrap()) + .unwrap(); + let release = record + .get(release_idx) + .map(|date_str| NaiveDate::parse_from_str(date_str, "%Y-%m-%d").unwrap()); + let eol = record + .get(eol_idx) + .map(|date_str| NaiveDate::parse_from_str(date_str, "%Y-%m-%d").unwrap()); + + series_info_list.push(SeriesInformation { + series, + codename, + version, + created, + release, + eol, + }); } - // Sort by date descending (newest first) - entries.sort_by(|a, b| b.1.cmp(&a.1)); + // Revert to sort by most recent + series_info_list.reverse(); - Ok(entries.into_iter().map(|(s, _)| s).collect()) + Ok(series_info_list) } -/// Get time-ordered list of series for a distribution, development series first -pub async fn get_ordered_series(dist: &str) -> Result, Box> { +/// Get time-ordered list of series information for a distribution, development series first +pub async fn get_ordered_series(dist: &str) -> Result, Box> { let series_info = &DATA.dist.get(dist).unwrap().series; let content = if Path::new(series_info.local.as_str()).exists() { std::fs::read_to_string(format!("/usr/share/distro-info/{dist}.csv"))? @@ -71,22 +122,48 @@ pub async fn get_ordered_series(dist: &str) -> Result, Box Result, Box> { + let series = get_ordered_series(dist).await?; + Ok(series.iter().map(|info| info.series.clone()).collect()) +} + +/// Get the latest released series for a dist (excluding future releases and special cases like sid) +pub async fn get_latest_released_series(dist: &str) -> Result> { + let series_info_list = get_ordered_series(dist).await?; + + let today = chrono::Local::now().date_naive(); + let mut released_series = Vec::new(); + + for series_info in series_info_list { + // Skip 'sid' and series without release dates or with future release dates + if series_info.series != "sid" + && series_info.series != "experimental" + && series_info.release.is_some() + && series_info.release.unwrap() <= today + { + released_series.push(series_info); + } } - Ok(series) + // Sort by release date descending (newest first) + released_series.sort_by(|a, b| b.release.cmp(&a.release)); + + if let Some(latest) = released_series.first() { + Ok(latest.series.clone()) + } else { + Err("No released series found".into()) + } } /// Obtain the distribution (eg. debian, ubuntu) from a distribution series (eg. noble, bookworm) pub async fn get_dist_from_series(series: &str) -> Result> { for dist in DATA.dist.keys() { - if get_ordered_series(dist) + if get_ordered_series_name(dist) .await? .contains(&series.to_string()) { @@ -135,13 +212,11 @@ pub async fn get_keyring_url(series: &str) -> Result> { // For Debian, we need the series number to form the keyring URL if dist == "debian" { - // Special case for 'sid' - use the latest version - if series == "sid" { - let latest_series = get_ordered_series("debian").await?; - // Get the first non-sid series (which should be the latest stable) - let latest_stable = latest_series.iter().find(|&s| s != "sid").unwrap(); - let series_num = get_debian_series_number(latest_stable).await?.unwrap(); - // Replace {series_num} placeholder with the latest stable series number + // Special case for 'sid' - use the latest released version + if series == "sid" || series == "experimental" { + let latest_released = get_latest_released_series("debian").await?; + let series_num = get_debian_series_number(&latest_released).await?.unwrap(); + // Replace {series_num} placeholder with the latest released series number Ok(dist_data .archive_keyring .replace("{series_num}", &series_num)) @@ -237,14 +312,14 @@ mod tests { #[tokio::test] async fn test_get_debian_series() { - let series = get_ordered_series("debian").await.unwrap(); + let series = get_ordered_series_name("debian").await.unwrap(); assert!(series.contains(&"sid".to_string())); assert!(series.contains(&"bookworm".to_string())); } #[tokio::test] async fn test_get_ubuntu_series() { - let series = get_ordered_series("ubuntu").await.unwrap(); + let series = get_ordered_series_name("ubuntu").await.unwrap(); assert!(series.contains(&"noble".to_string())); assert!(series.contains(&"jammy".to_string())); } @@ -273,13 +348,26 @@ mod tests { #[tokio::test] async fn test_get_keyring_url_sid() { - // Test that 'sid' uses the latest stable version for keyring URL + // Test that 'sid' uses the latest released version for keyring URL let sid_keyring = get_keyring_url("sid").await.unwrap(); - let latest_series = get_ordered_series("debian").await.unwrap(); - let latest_stable = latest_series.iter().find(|&s| s != "sid").unwrap(); - let latest_keyring = get_keyring_url(latest_stable).await.unwrap(); + let latest_released = get_latest_released_series("debian").await.unwrap(); + let latest_keyring = get_keyring_url(&latest_released).await.unwrap(); - // The keyring URL for 'sid' should be the same as the latest stable version + // The keyring URL for 'sid' should be the same as the latest released version assert_eq!(sid_keyring, latest_keyring); } + + #[tokio::test] + async fn test_get_latest_released_debian_series() { + // Test that we get a valid released series + let latest_released = get_latest_released_series("debian").await.unwrap(); + + // Should not be 'sid' or 'experimental' + assert_ne!(latest_released, "sid"); + assert_ne!(latest_released, "experimental"); + + // Should have a version number + let version = get_debian_series_number(&latest_released).await.unwrap(); + assert!(version.is_some()); + } } diff --git a/src/package_info.rs b/src/package_info.rs index 9980176..8843876 100644 --- a/src/package_info.rs +++ b/src/package_info.rs @@ -254,7 +254,7 @@ async fn find_package( version: Option<&str>, progress: ProgressCallback<'_>, ) -> Result> { - let series_list = crate::distro_info::get_ordered_series(dist).await?; + let series_list = crate::distro_info::get_ordered_series_name(dist).await?; for (i, series) in series_list.iter().enumerate() { if let Some(cb) = progress { diff --git a/src/pull.rs b/src/pull.rs index f035b21..604e0b2 100644 --- a/src/pull.rs +++ b/src/pull.rs @@ -504,7 +504,7 @@ pub async fn pull( // Depending on target series, we pick target branch; if latest series is specified, // we target the development branch, i.e. the default branch - let branch_name = if crate::distro_info::get_ordered_series(package_info.dist.as_str()) + let branch_name = if crate::distro_info::get_ordered_series_name(package_info.dist.as_str()) .await?[0] != *series {