ed_journals/modules/state/models/resolvers/
live_state_resolver.rspub mod live_state_entry;
pub mod live_state_entry_owned;
pub mod organic_location;
pub mod touchdown_location;
use crate::backpack::Backpack;
use crate::cargo::Cargo;
use crate::journal::{JournalEvent, JournalEventKind};
use crate::logs::{LogEvent, LogEventContent};
use crate::market::Market;
use crate::modules_info::ModulesInfo;
use crate::nav_route::NavRoute;
use crate::outfitting::Outfitting;
use crate::ship_locker::ShipLocker;
use crate::shipyard::Shipyard;
use crate::state::models::feed_result::FeedResult;
use crate::state::models::resolvers::live_state_resolver::organic_location::OrganicLocation;
use crate::state::models::resolvers::live_state_resolver::touchdown_location::TouchdownLocation;
use crate::state::traits::state_resolver::StateResolver;
use crate::status::{PlanetStatus, ShipStatus, Status};
use chrono::{DateTime, TimeDelta, Utc};
use serde::{Deserialize, Serialize};
use std::ops::Sub;
#[derive(Serialize, Deserialize, Default)]
pub struct LiveStateResolver {
pub touchdown_locations: Vec<TouchdownLocation>,
pub organic_locations: Vec<OrganicLocation>,
#[serde(skip)]
pub status: Option<Status>,
#[serde(skip)]
pub modules_info: Option<ModulesInfo>,
#[serde(skip)]
pub cargo: Option<Cargo>,
#[serde(skip)]
pub nav_route: Option<NavRoute>,
#[serde(skip)]
pub outfitting: Option<Outfitting>,
#[serde(skip)]
pub shipyard: Option<Shipyard>,
#[serde(skip)]
pub market: Option<Market>,
#[serde(skip)]
pub backpack: Option<Backpack>,
#[serde(skip)]
pub ship_locker: Option<ShipLocker>,
pub last_ship_status: Option<ShipStatus>,
}
impl StateResolver<JournalEvent> for LiveStateResolver {
fn feed(&mut self, input: &JournalEvent) -> FeedResult {
match &input.kind {
JournalEventKind::LogEvent(log_event) => {
self.feed_log_event(log_event);
}
JournalEventKind::StatusEvent(status) => {
self.status = Some(status.clone());
let Some(contents) = status.contents.as_ref() else {
return FeedResult::Skipped;
};
if let Some(ship_status) = contents.kind.ship_status() {
self.last_ship_status = Some(ship_status.clone());
}
}
JournalEventKind::OutfittingEvent(outfitting) => {
self.outfitting = Some(outfitting.clone());
}
JournalEventKind::ShipyardEvent(shipyard) => {
self.shipyard = Some(shipyard.clone());
}
JournalEventKind::MarketEvent(market) => {
self.market = Some(market.clone());
}
JournalEventKind::NavRoute(nav_route) => {
self.nav_route = Some(nav_route.clone());
}
JournalEventKind::ModulesInfo(modules_info) => {
self.modules_info = Some(modules_info.clone());
}
JournalEventKind::Backpack(backpack) => {
self.backpack = Some(backpack.clone());
}
JournalEventKind::Cargo(cargo) => {
self.cargo = Some(cargo.clone());
}
JournalEventKind::ShipLocker(ship_locker) => {
self.ship_locker = Some(ship_locker.clone());
}
}
FeedResult::Accepted
}
}
impl LiveStateResolver {
fn feed_log_event(&mut self, input: &LogEvent) -> FeedResult {
match &input.content {
LogEventContent::Touchdown(touchdown) => {
let Some(planet_status) = self.valid_planet_status(&input.timestamp) else {
return FeedResult::Skipped;
};
self.touchdown_locations.push(TouchdownLocation {
system_address: touchdown.system_address,
body_id: touchdown.body_id,
coordinates: (planet_status.latitude, planet_status.latitude),
})
}
LogEventContent::ScanOrganic(scan_organic) => {
let Some(planet_status) = self.valid_planet_status(&input.timestamp) else {
return FeedResult::Skipped;
};
self.organic_locations.push(OrganicLocation {
system_address: scan_organic.system_address,
body_id: scan_organic.body,
body_name: planet_status.body_name.to_string(),
species: scan_organic.species.clone(),
variant: scan_organic.variant.clone(),
coordinates: (planet_status.latitude, planet_status.longitude),
})
}
LogEventContent::NavRouteClear => {
if self.valid_nav_route(&input.timestamp).is_some() {
self.nav_route = None;
}
}
LogEventContent::Liftoff(_) => {
if self.valid_outfitting(&input.timestamp).is_some() {
self.outfitting = None;
}
if self.valid_shipyard(&input.timestamp).is_some() {
self.shipyard = None;
}
if self.valid_market(&input.timestamp).is_some() {
self.market = None;
}
}
_ => {}
}
FeedResult::Accepted
}
pub fn valid_status(&self, timestamp: &DateTime<Utc>) -> Option<&Status> {
let status = self.status.as_ref()?;
if Self::is_valid_live_state(&status.timestamp, timestamp) {
Some(status)
} else {
None
}
}
pub fn valid_module_info(&self, timestamp: &DateTime<Utc>) -> Option<&ModulesInfo> {
let module_info = self.modules_info.as_ref()?;
if Self::is_valid_live_state(&module_info.timestamp, timestamp) {
Some(module_info)
} else {
None
}
}
pub fn valid_cargo(&self, timestamp: &DateTime<Utc>) -> Option<&Cargo> {
let cargo = self.cargo.as_ref()?;
if Self::is_valid_live_state(&cargo.timestamp, timestamp) {
Some(cargo)
} else {
None
}
}
pub fn valid_nav_route(&self, timestamp: &DateTime<Utc>) -> Option<&NavRoute> {
let nav_route = self.nav_route.as_ref()?;
if Self::is_valid_live_state(&nav_route.timestamp, timestamp) {
Some(nav_route)
} else {
None
}
}
pub fn valid_outfitting(&self, timestamp: &DateTime<Utc>) -> Option<&Outfitting> {
let outfitting = self.outfitting.as_ref()?;
if Self::is_valid_live_state(&outfitting.timestamp, timestamp) {
Some(outfitting)
} else {
None
}
}
pub fn valid_shipyard(&self, timestamp: &DateTime<Utc>) -> Option<&Shipyard> {
let shipyard = self.shipyard.as_ref()?;
if Self::is_valid_live_state(&shipyard.timestamp, timestamp) {
Some(shipyard)
} else {
None
}
}
pub fn valid_market(&self, timestamp: &DateTime<Utc>) -> Option<&Market> {
let market = self.market.as_ref()?;
if Self::is_valid_live_state(&market.timestamp, timestamp) {
Some(market)
} else {
None
}
}
pub fn valid_backpack(&self, timestamp: &DateTime<Utc>) -> Option<&Backpack> {
let backpack = self.backpack.as_ref()?;
if Self::is_valid_live_state(&backpack.timestamp, timestamp) {
Some(backpack)
} else {
None
}
}
pub fn valid_ship_locker(&self, timestamp: &DateTime<Utc>) -> Option<&ShipLocker> {
let ship_locker = self.ship_locker.as_ref()?;
if Self::is_valid_live_state(&ship_locker.timestamp, timestamp) {
Some(ship_locker)
} else {
None
}
}
pub fn current_ship_status(&self) -> Option<&ShipStatus> {
self.status.as_ref()?.contents.as_ref()?.kind.ship_status()
}
pub fn valid_ship_status(&self, timestamp: &DateTime<Utc>) -> Option<&ShipStatus> {
self.valid_status(timestamp)
.as_ref()?
.contents
.as_ref()?
.kind
.ship_status()
}
pub fn current_planet_status(&self) -> Option<&PlanetStatus> {
self.status
.as_ref()?
.contents
.as_ref()?
.planet_status
.as_ref()
}
pub fn valid_planet_status(&self, timestamp: &DateTime<Utc>) -> Option<&PlanetStatus> {
self.valid_status(timestamp)
.as_ref()?
.contents
.as_ref()?
.planet_status
.as_ref()
}
fn is_valid_live_state(live_timestamp: &DateTime<Utc>, log_timestamp: &DateTime<Utc>) -> bool {
let grace_period = TimeDelta::new(2, 0).expect("This should always complete");
&log_timestamp.sub(grace_period) >= live_timestamp
}
}