made_client/
progress_checkpoint.rs1use std::path::Path;
2
3use serde::{Deserialize, Serialize};
4use tokio::io::AsyncWriteExt;
5use uuid::Uuid;
6
7use crate::MadeClientError;
8
9#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
11pub struct ProgressCheckpoint {
12 ceremony_id: String,
13 after_sequence: u64,
14}
15
16impl ProgressCheckpoint {
17 pub fn new(ceremony_id: impl Into<String>, after_sequence: u64) -> Self {
18 Self {
19 ceremony_id: ceremony_id.into(),
20 after_sequence,
21 }
22 }
23
24 #[must_use]
25 pub fn ceremony_id(&self) -> &str {
26 &self.ceremony_id
27 }
28
29 #[must_use]
30 pub fn after_sequence(&self) -> u64 {
31 self.after_sequence
32 }
33
34 pub async fn load(path: &Path, expected_ceremony_id: &str) -> Result<Self, MadeClientError> {
35 let bytes = tokio::fs::read(path)
36 .await
37 .map_err(|error| MadeClientError::io(path, error))?;
38 let checkpoint: Self = serde_json::from_slice(&bytes)
39 .map_err(|error| MadeClientError::CursorCheckpointCorrupt(error.to_string()))?;
40 if checkpoint.ceremony_id != expected_ceremony_id {
41 return Err(MadeClientError::CursorScopeMismatch {
42 expected: expected_ceremony_id.to_owned(),
43 actual: checkpoint.ceremony_id,
44 });
45 }
46 Ok(checkpoint)
47 }
48
49 pub async fn save(&self, path: &Path) -> Result<(), MadeClientError> {
50 let parent = path.parent().unwrap_or_else(|| Path::new("."));
51 tokio::fs::create_dir_all(parent)
52 .await
53 .map_err(|error| MadeClientError::io(parent, error))?;
54 let name = path
55 .file_name()
56 .and_then(|name| name.to_str())
57 .unwrap_or("made-cursor");
58 let temporary = parent.join(format!(".{name}.{}.part", Uuid::new_v4()));
59 let payload = serde_json::to_vec(self)
60 .map_err(|error| MadeClientError::CursorCheckpointCorrupt(error.to_string()))?;
61 let result = async {
62 let mut file = tokio::fs::File::create(&temporary)
63 .await
64 .map_err(|error| MadeClientError::io(&temporary, error))?;
65 file.write_all(&payload)
66 .await
67 .map_err(|error| MadeClientError::io(&temporary, error))?;
68 file.sync_all()
69 .await
70 .map_err(|error| MadeClientError::io(&temporary, error))?;
71 drop(file);
72 tokio::fs::rename(&temporary, path)
73 .await
74 .map_err(|error| MadeClientError::io(path, error))
75 }
76 .await;
77 if result.is_err() {
78 let _ = tokio::fs::remove_file(&temporary).await;
79 }
80 result
81 }
82}