use chrono::prelude::*;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::collections::BTreeMap;
use crate::packagesystem::*;
pub(crate) const BOOTUPD_UPDATES_DIR: &str = "usr/lib/bootupd/updates";
#[derive(Serialize, Deserialize, Clone, Debug, Hash, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub(crate) struct ContentMetadata {
pub(crate) timestamp: DateTime<Utc>,
pub(crate) version: String,
pub(crate) versions: Option<Vec<Module>>,
}
impl ContentMetadata {
pub(crate) fn can_upgrade_to(&self, target: &Self) -> Ordering {
if let (Some(self_versions), Some(target_versions)) = (&self.versions, &target.versions) {
compare_package_slices(self_versions, target_versions)
} else {
compare_package_versions(&self.version, &target.version)
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "kebab-case")]
pub(crate) struct InstalledContent {
pub(crate) meta: ContentMetadata,
pub(crate) filetree: Option<crate::filetree::FileTree>,
pub(crate) adopted_from: Option<ContentMetadata>,
}
#[derive(Serialize, Deserialize, Default, Debug)]
#[serde(rename_all = "kebab-case")]
#[serde(deny_unknown_fields)]
pub(crate) struct SavedState {
pub(crate) installed: BTreeMap<String, InstalledContent>,
pub(crate) pending: Option<BTreeMap<String, ContentMetadata>>,
pub(crate) static_configs: Option<ContentMetadata>,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum ComponentUpdatable {
NoUpdateAvailable,
AtLatestVersion,
Upgradable,
WouldDowngrade,
}
impl ComponentUpdatable {
pub(crate) fn from_metadata(from: &ContentMetadata, to: Option<&ContentMetadata>) -> Self {
match to {
Some(to) => {
match from.can_upgrade_to(to) {
Ordering::Equal => return ComponentUpdatable::AtLatestVersion, Ordering::Less => return ComponentUpdatable::Upgradable, Ordering::Greater => return ComponentUpdatable::WouldDowngrade, }
}
None => ComponentUpdatable::NoUpdateAvailable,
}
}
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
pub(crate) struct ComponentStatus {
pub(crate) installed: ContentMetadata,
pub(crate) interrupted: Option<ContentMetadata>,
pub(crate) update: Option<ContentMetadata>,
pub(crate) updatable: ComponentUpdatable,
pub(crate) adopted_from: Option<ContentMetadata>,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
pub(crate) struct Adoptable {
pub(crate) version: ContentMetadata,
pub(crate) confident: bool,
}
#[derive(Serialize, Deserialize, Default, Debug)]
#[serde(rename_all = "kebab-case")]
#[serde(deny_unknown_fields)]
pub(crate) struct Status {
pub(crate) components: BTreeMap<String, ComponentStatus>,
pub(crate) adoptable: BTreeMap<String, Adoptable>,
}
#[cfg(test)]
mod test {
use super::*;
use anyhow::Result;
use chrono::Duration;
#[test]
fn test_meta_compare() {
let t = Utc::now();
let a = ContentMetadata {
timestamp: t,
version: "grub2-efi-ia32-1:2.12-21.fc41.x86_64,grub2-efi-x64-1:2.12-21.fc41.x86_64,shim-ia32-15.8-3.x86_64,shim-x64-15.8-3.x86_64".into(),
versions: None,
};
let b = ContentMetadata {
timestamp: t + Duration::try_seconds(1).unwrap(),
version: "grub2-efi-ia32-1:2.12-28.fc41.x86_64,grub2-efi-x64-1:2.12-28.fc41.x86_64,shim-ia32-15.8-3.x86_64,shim-x64-15.8-3.x86_64".into(),
versions: None,
};
assert_eq!(a.can_upgrade_to(&b), Ordering::Less); assert_eq!(b.can_upgrade_to(&a), Ordering::Greater);
let a = ContentMetadata {
timestamp: t,
version: "test".into(),
versions: Some(vec![
Module {
name: "grub2".into(),
rpm_evr: "1:2.12-21.fc41".into(),
},
Module {
name: "shim".into(),
rpm_evr: "15.8-3".into(),
},
]),
};
let b = ContentMetadata {
timestamp: t + Duration::try_seconds(1).unwrap(),
version: "test".into(),
versions: Some(vec![
Module {
name: "grub2".into(),
rpm_evr: "1:2.12-28.fc41".into(),
},
Module {
name: "shim".into(),
rpm_evr: "15.8-3".into(),
},
]),
};
assert_eq!(a.can_upgrade_to(&b), Ordering::Less); assert_eq!(b.can_upgrade_to(&a), Ordering::Greater);
}
#[test]
fn test_deserialize_state() -> Result<()> {
let data = include_str!("../tests/fixtures/example-state-v0.json");
let state: SavedState = serde_json::from_str(data)?;
let efi = state.installed.get("EFI").expect("EFI");
assert_eq!(
efi.meta.version,
"grub2-efi-x64-1:2.04-23.fc32.x86_64,shim-x64-15-8.x86_64"
);
assert_eq!(efi.meta.versions, None);
let data = include_str!("../tests/fixtures/example-state-versions-v0.json");
let state: SavedState = serde_json::from_str(data)?;
let efi = state.installed.get("EFI").expect("EFI");
assert_eq!(efi.meta.version, "grub2-1:2.12-41.fc44,shim-15.8-4");
assert_eq!(
efi.meta.versions,
Some(vec![
Module {
name: "grub2".into(),
rpm_evr: "1:2.12-41.fc44".into(),
},
Module {
name: "shim".into(),
rpm_evr: "15.8-4".into(),
},
])
);
Ok(())
}
#[test]
fn test_deserialize_status() -> Result<()> {
let data = include_str!("../tests/fixtures/example-status-v0.json");
let status: Status = serde_json::from_str(data)?;
let efi = status.components.get("EFI").expect("EFI");
assert_eq!(
efi.installed.version,
"grub2-efi-x64-1:2.04-23.fc32.x86_64,shim-x64-15-8.x86_64"
);
assert_eq!(efi.installed.versions, None);
Ok(())
}
}