use std::{collections::HashMap, path::PathBuf}; use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serialize}; use serde_json::Value; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Plan { plan: Vec, current: usize, #[serde(flatten)] metadata: PlanMetadata, } #[derive(Deserialize, Debug, Clone)] pub struct PlanMetadata { stored_path: Option, update_url: Option, latest_server_etag: Option, identifier: Option, #[serde(default)] #[serde(deserialize_with = "deserialize_option_string")] last_stored_time: Option, } impl PlanMetadata { pub fn set_stored_path(&mut self, file: Option) { self.stored_path = file; } } impl Serialize for PlanMetadata { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { let mut state = serializer.serialize_struct("PlanMetadata", 6)?; state.serialize_field("update_url", &self.update_url)?; state.serialize_field("stored_path", &self.stored_path)?; state.serialize_field("latest_server_etag", &self.latest_server_etag)?; state.serialize_field("identifier", &self.identifier)?; state.serialize_field("last_stored_time", &self.last_stored_time)?; if let Some(path) = &self.stored_path { if let Some(name) = path.file_name() { state.serialize_field("name", &name.to_str())?; } } state.end() } } // Custom deserializer that accepts both strings and legacy number for the stored_time field fn deserialize_option_string<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { let v: Option = Option::deserialize(deserializer)?; Ok(match v { Some(Value::String(s)) => Some(s), Some(Value::Number(n)) => Some(n.to_string()), _ => None, }) } impl From for Option { fn from(metadata: PlanMetadata) -> Self { Some(serde_json::from_slice(&std::fs::read(metadata.stored_path?).ok()?).ok()?) } } impl Plan { pub fn set_stored_path(&mut self, file: Option) { self.metadata.stored_path = file; } } #[derive(Debug, Deserialize, Serialize, Clone)] pub struct PlanElement { area_key: String, notes: String, #[serde(default = "PlanElement::generate_uuid")] uuid: uuid::Uuid, #[serde(default = "PlanElement::edited")] edited: bool, anchor_act: Option, #[serde(default, skip_serializing_if = "is_false")] checkpoint: bool, } fn is_false(flag: &bool) -> bool { !flag } impl PlanElement { pub fn generate_uuid() -> uuid::Uuid { uuid::Uuid::new_v4() } pub fn edited() -> bool { false } } #[allow(dead_code)] #[derive(Debug, Deserialize)] struct OldPlanElement { area: OldArea, note: String, } #[allow(dead_code)] #[derive(Debug, Deserialize)] struct OldPlan { elements: Vec, position: usize, } #[derive(Debug, Deserialize)] struct OldArea { _rid: usize, } #[allow(dead_code)] pub fn convert_old(path: PathBuf) -> Option { let plan: OldPlan = serde_json::from_str(&std::fs::read_to_string(path).expect("Could not convert old")) .expect("could not convert old"); let map = poe_data::world_area::load_world_areas_map(include_str!( "../../data/processed_world_areas.json" )); let map = map .iter() .map(|(k, v)| (v.key_id, k)) .collect::>(); Some(Plan { current: plan.position, plan: plan .elements .into_iter() .map(|plan_element| PlanElement { area_key: map[&plan_element.area._rid].to_owned(), notes: plan_element.note, uuid: PlanElement::generate_uuid(), edited: PlanElement::edited(), anchor_act: None, checkpoint: false, }) .collect::>(), metadata: PlanMetadata { stored_path: None, update_url: None, latest_server_etag: None, identifier: None, last_stored_time: None, }, }) }