use std::collections::{BTreeMap, BTreeSet}; use std::sync::Arc; use chrono::{DateTime, Utc}; use emver::VersionRange; use isocountry::CountryCode; use patch_db::json_ptr::JsonPointer; use patch_db::{HasModel, Map, MapModel, OptionModel}; use reqwest::Url; use serde::{Deserialize, Serialize}; use serde_json::Value; use torut::onion::TorSecretKeyV3; use crate::config::spec::{PackagePointerSpec, SystemPointerSpec}; use crate::install::progress::InstallProgress; use crate::net::interface::InterfaceId; use crate::s9pk::manifest::{Manifest, ManifestModel, PackageId}; use crate::status::health_check::HealthCheckId; use crate::status::Status; use crate::util::Version; use crate::version::{Current, VersionT}; #[derive(Debug, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] pub struct Database { #[model] pub server_info: ServerInfo, #[model] pub package_data: AllPackageData, #[model] pub recovered_packages: BTreeMap, pub ui: Value, } impl Database { pub fn init( id: String, hostname: &str, tor_key: &TorSecretKeyV3, password_hash: String, ) -> Self { // TODO Database { server_info: ServerInfo { id, version: Current::new().semver().into(), last_backup: None, last_wifi_region: None, eos_version_compat: Current::new().compat().clone(), lan_address: format!("https://{}.local", hostname).parse().unwrap(), tor_address: format!("http://{}", tor_key.public().get_onion_address()) .parse() .unwrap(), status_info: ServerStatus { backup_progress: None, updated: false, update_progress: None, }, wifi: WifiInfo { ssids: Vec::new(), connected: None, selected: None, }, unread_notification_count: 0, connection_addresses: ConnectionAddresses { tor: Vec::new(), clearnet: Vec::new(), }, password_hash, }, package_data: AllPackageData::default(), recovered_packages: BTreeMap::new(), ui: Value::Object(Default::default()), } } } impl DatabaseModel { pub fn new() -> Self { Self::from(JsonPointer::default()) } } #[derive(Debug, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] pub struct ServerInfo { pub id: String, pub version: Version, pub last_backup: Option>, /// Used in the wifi to determine the region to set the system to pub last_wifi_region: Option, pub eos_version_compat: VersionRange, pub lan_address: Url, pub tor_address: Url, #[model] #[serde(default)] pub status_info: ServerStatus, pub wifi: WifiInfo, pub unread_notification_count: u64, pub connection_addresses: ConnectionAddresses, pub password_hash: String, } #[derive(Debug, Default, Deserialize, Serialize, HasModel)] pub struct BackupProgress { pub complete: bool, } #[derive(Debug, Default, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] pub struct ServerStatus { #[model] pub backup_progress: Option>, pub updated: bool, #[model] pub update_progress: Option, } #[derive(Debug, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] pub struct UpdateProgress { pub size: Option, pub downloaded: u64, } #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] pub struct WifiInfo { pub ssids: Vec, pub selected: Option, pub connected: Option, } #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] pub struct ServerSpecs { pub cpu: String, pub disk: String, pub memory: String, } #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] pub struct ConnectionAddresses { pub tor: Vec, pub clearnet: Vec, } #[derive(Debug, Default, Deserialize, Serialize)] pub struct AllPackageData(pub BTreeMap); impl Map for AllPackageData { type Key = PackageId; type Value = PackageDataEntry; fn get(&self, key: &Self::Key) -> Option<&Self::Value> { self.0.get(key) } } impl HasModel for AllPackageData { type Model = MapModel; } #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] pub struct StaticFiles { license: String, instructions: String, icon: String, } impl StaticFiles { pub fn local(id: &PackageId, version: &Version, icon_type: &str) -> Self { StaticFiles { license: format!("/public/package-data/{}/{}/LICENSE.md", id, version), instructions: format!("/public/package-data/{}/{}/INSTRUCTIONS.md", id, version), icon: format!("/public/package-data/{}/{}/icon.{}", id, version, icon_type), } } } #[derive(Debug, Deserialize, Serialize, HasModel)] #[serde(tag = "state")] #[serde(rename_all = "kebab-case")] pub enum PackageDataEntry { #[serde(rename_all = "kebab-case")] Installing { static_files: StaticFiles, manifest: Manifest, install_progress: Arc, }, #[serde(rename_all = "kebab-case")] Updating { static_files: StaticFiles, manifest: Manifest, installed: InstalledPackageDataEntry, install_progress: Arc, }, #[serde(rename_all = "kebab-case")] Restoring { static_files: StaticFiles, manifest: Manifest, install_progress: Arc, }, #[serde(rename_all = "kebab-case")] Removing { static_files: StaticFiles, manifest: Manifest, removing: InstalledPackageDataEntry, }, #[serde(rename_all = "kebab-case")] Installed { static_files: StaticFiles, manifest: Manifest, installed: InstalledPackageDataEntry, }, } impl PackageDataEntry { pub fn installed(&self) -> Option<&InstalledPackageDataEntry> { match self { Self::Installing { .. } | Self::Restoring { .. } | Self::Removing { .. } => None, Self::Updating { installed, .. } | Self::Installed { installed, .. } => Some(installed), } } pub fn installed_mut(&mut self) -> Option<&mut InstalledPackageDataEntry> { match self { Self::Installing { .. } | Self::Restoring { .. } | Self::Removing { .. } => None, Self::Updating { installed, .. } | Self::Installed { installed, .. } => Some(installed), } } pub fn into_installed(self) -> Option { match self { Self::Installing { .. } | Self::Restoring { .. } | Self::Removing { .. } => None, Self::Updating { installed, .. } | Self::Installed { installed, .. } => Some(installed), } } pub fn manifest(self) -> Manifest { match self { PackageDataEntry::Installing { manifest, .. } => manifest, PackageDataEntry::Updating { manifest, .. } => manifest, PackageDataEntry::Restoring { manifest, .. } => manifest, PackageDataEntry::Removing { manifest, .. } => manifest, PackageDataEntry::Installed { manifest, .. } => manifest, } } } impl PackageDataEntryModel { pub fn installed(self) -> OptionModel { self.0.child("installed").into() } pub fn removing(self) -> OptionModel { self.0.child("removing").into() } pub fn install_progress(self) -> OptionModel { self.0.child("install-progress").into() } pub fn manifest(self) -> ManifestModel { self.0.child("manifest").into() } } #[derive(Debug, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] pub struct InstalledPackageDataEntry { #[model] pub status: Status, pub marketplace_url: Option, #[serde(default)] #[serde(with = "crate::util::serde::ed25519_pubkey")] pub developer_key: ed25519_dalek::PublicKey, #[model] pub manifest: Manifest, pub last_backup: Option>, #[model] pub system_pointers: Vec, #[model] pub dependency_info: BTreeMap, #[model] pub current_dependents: CurrentDependents, #[model] pub current_dependencies: CurrentDependencies, #[model] pub interface_addresses: InterfaceAddressMap, } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct CurrentDependents(pub BTreeMap); impl CurrentDependents { pub fn map( mut self, transform: impl Fn( BTreeMap, ) -> BTreeMap, ) -> Self { self.0 = transform(self.0); self } } impl Map for CurrentDependents { type Key = PackageId; type Value = CurrentDependencyInfo; fn get(&self, key: &Self::Key) -> Option<&Self::Value> { self.0.get(key) } } impl HasModel for CurrentDependents { type Model = MapModel; } #[derive(Debug, Clone, Default, Deserialize, Serialize)] pub struct CurrentDependencies(pub BTreeMap); impl CurrentDependencies { pub fn map( mut self, transform: impl Fn( BTreeMap, ) -> BTreeMap, ) -> Self { self.0 = transform(self.0); self } } impl Map for CurrentDependencies { type Key = PackageId; type Value = CurrentDependencyInfo; fn get(&self, key: &Self::Key) -> Option<&Self::Value> { self.0.get(key) } } impl HasModel for CurrentDependencies { type Model = MapModel; } #[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] pub struct StaticDependencyInfo { pub manifest: Option, pub icon: String, } #[derive(Clone, Debug, Default, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] pub struct CurrentDependencyInfo { pub pointers: Vec, pub health_checks: BTreeSet, } #[derive(Debug, Deserialize, Serialize)] pub struct InterfaceAddressMap(pub BTreeMap); impl Map for InterfaceAddressMap { type Key = InterfaceId; type Value = InterfaceAddresses; fn get(&self, key: &Self::Key) -> Option<&Self::Value> { self.0.get(key) } } impl HasModel for InterfaceAddressMap { type Model = MapModel; } #[derive(Debug, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] pub struct InterfaceAddresses { #[model] pub tor_address: Option, #[model] pub lan_address: Option, } #[derive(Debug, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] pub struct RecoveredPackageInfo { pub title: String, pub icon: String, pub version: Version, }