use std::{fs, io, path::Path}; use atrium_api::{app::bsky::richtext::facet::Main as Facet, types::string::Language}; use serde::{Serialize, de::DeserializeOwned}; pub(crate) struct Post { pub text: String, pub facets: Vec, /// Images to upload before creating the post record. pub images: Vec, pub langs: Vec, pub tags: Vec, } pub(crate) struct PostImage { pub data: Vec, pub alt: String, } pub(crate) trait Report: Sized { type State: Default + DeserializeOwned + Serialize; const STATE_FILE: &'static str; async fn generate(state: Self::State) -> Result<(Vec, Self::State), String>; async fn report() -> Result<(Vec, impl FnOnce() -> Result<(), String>), String> { let state_path = Path::new(Self::STATE_FILE); let state = load_state(state_path)?; let (posts, new_state) = Self::generate(state).await?; Ok((posts, move || save_state(state_path, &new_state))) } } fn load_state(path: &Path) -> Result where T: Default + DeserializeOwned, { match fs::read(path) { Ok(data) => serde_json::from_slice(&data) .map_err(|error| format!("failed to parse {}: {error}", path.display())), Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(T::default()), Err(error) => Err(format!("failed to read {}: {error}", path.display())), } } fn save_state(path: &Path, state: &T) -> Result<(), String> where T: Serialize, { let data = serde_json::to_vec_pretty(state) .map_err(|error| format!("failed to serialize {}: {error}", path.display()))?; let temporary_path = path.with_extension("json.tmp"); fs::write(&temporary_path, data) .map_err(|error| format!("failed to write {}: {error}", temporary_path.display()))?; fs::rename(&temporary_path, path) .map_err(|error| format!("failed to replace {}: {error}", path.display())) }