1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
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<Facet>,
/// Images to upload before creating the post record.
pub images: Vec<PostImage>,
pub langs: Vec<Language>,
pub tags: Vec<String>,
}
pub(crate) struct PostImage {
pub data: Vec<u8>,
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<Post>, Self::State), String>;
async fn report() -> Result<(Vec<Post>, 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<T>(path: &Path) -> Result<T, String>
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<T>(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()))
}
|