1use std::sync::RwLock;
2use std::time::{SystemTime, UNIX_EPOCH};
3
4use serde::{Deserialize, Serialize};
5
6use crate::error::{Result, ServerError};
7
8#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
9#[serde(rename_all = "snake_case")]
10pub enum Mode {
11 Normal,
12 ReadOnly,
13 Maintenance,
14}
15
16#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
17#[serde(rename_all = "snake_case")]
18pub enum OperationStatus {
19 Queued,
20 Running,
21 Completed,
22 Failed,
23 Cancelled,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
27pub struct Progress {
28 pub percent: Option<u8>,
29 pub bytes_processed: Option<u64>,
30}
31
32impl Progress {
33 pub fn percent(value: u8) -> Self {
34 Self {
35 percent: Some(value),
36 bytes_processed: None,
37 }
38 }
39
40 pub fn bytes(value: u64) -> Self {
41 Self {
42 percent: None,
43 bytes_processed: Some(value),
44 }
45 }
46
47 pub fn validate(&self) -> Result<()> {
48 if self.percent.is_none() && self.bytes_processed.is_none() {
49 return Err(ServerError::BadRequest(
50 "progress must include percent or bytes_processed".to_string(),
51 ));
52 }
53 Ok(())
54 }
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
58pub struct OperationState {
59 pub status: OperationStatus,
60 pub started_at_ms: Option<u64>,
61 pub finished_at_ms: Option<u64>,
62 pub progress: Option<Progress>,
63 pub reason: Option<String>,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
67pub struct RestoreMetadata {
68 pub backup_id: String,
69 pub location: String,
70 pub restored_at_ms: u64,
71 pub size_bytes: u64,
72}
73
74impl OperationState {
75 pub fn queued() -> Self {
76 Self {
77 status: OperationStatus::Queued,
78 started_at_ms: None,
79 finished_at_ms: None,
80 progress: None,
81 reason: None,
82 }
83 }
84
85 pub fn running() -> Self {
86 Self {
87 status: OperationStatus::Running,
88 started_at_ms: Some(now_ms()),
89 finished_at_ms: None,
90 progress: None,
91 reason: None,
92 }
93 }
94
95 pub fn completed(progress: Option<Progress>) -> Result<Self> {
96 if let Some(progress) = &progress {
97 progress.validate()?;
98 }
99 Ok(Self {
100 status: OperationStatus::Completed,
101 started_at_ms: None,
102 finished_at_ms: Some(now_ms()),
103 progress,
104 reason: None,
105 })
106 }
107
108 pub fn failed(reason: impl Into<String>) -> Self {
109 Self {
110 status: OperationStatus::Failed,
111 started_at_ms: None,
112 finished_at_ms: Some(now_ms()),
113 progress: None,
114 reason: Some(reason.into()),
115 }
116 }
117
118 pub fn cancelled(reason: impl Into<String>) -> Self {
119 Self {
120 status: OperationStatus::Cancelled,
121 started_at_ms: None,
122 finished_at_ms: Some(now_ms()),
123 progress: None,
124 reason: Some(reason.into()),
125 }
126 }
127
128 pub fn mark_running(&mut self) {
129 self.status = OperationStatus::Running;
130 self.started_at_ms = Some(now_ms());
131 self.finished_at_ms = None;
132 }
133
134 pub fn mark_finished(&mut self, status: OperationStatus, reason: Option<String>) {
135 self.status = status;
136 self.finished_at_ms = Some(now_ms());
137 self.reason = reason;
138 }
139
140 pub fn set_progress(&mut self, progress: Progress) -> Result<()> {
141 progress.validate()?;
142 self.progress = Some(progress);
143 Ok(())
144 }
145}
146
147#[derive(Debug, Clone)]
148struct LifecycleState {
149 mode: Mode,
150 backup_state: OperationState,
151 restore_state: OperationState,
152 restore_metadata: Option<RestoreMetadata>,
153}
154
155#[derive(Debug)]
156pub struct LifecycleStateManager {
157 inner: RwLock<LifecycleState>,
158}
159
160impl LifecycleStateManager {
161 pub fn new(initial_mode: Mode) -> Self {
162 Self {
163 inner: RwLock::new(LifecycleState {
164 mode: initial_mode,
165 backup_state: OperationState::queued(),
166 restore_state: OperationState::queued(),
167 restore_metadata: None,
168 }),
169 }
170 }
171
172 pub fn current_mode(&self) -> Mode {
173 self.inner
174 .read()
175 .expect("lifecycle state lock poisoned")
176 .mode
177 }
178
179 pub fn set_mode(&self, mode: Mode) {
180 self.inner
181 .write()
182 .expect("lifecycle state lock poisoned")
183 .mode = mode;
184 }
185
186 pub fn backup_state(&self) -> OperationState {
187 self.inner
188 .read()
189 .expect("lifecycle state lock poisoned")
190 .backup_state
191 .clone()
192 }
193
194 pub fn set_backup_state(&self, state: OperationState) {
195 self.inner
196 .write()
197 .expect("lifecycle state lock poisoned")
198 .backup_state = state;
199 }
200
201 pub fn restore_state(&self) -> OperationState {
202 self.inner
203 .read()
204 .expect("lifecycle state lock poisoned")
205 .restore_state
206 .clone()
207 }
208
209 pub fn set_restore_state(&self, state: OperationState) {
210 self.inner
211 .write()
212 .expect("lifecycle state lock poisoned")
213 .restore_state = state;
214 }
215
216 pub fn restore_metadata(&self) -> Option<RestoreMetadata> {
217 self.inner
218 .read()
219 .expect("lifecycle state lock poisoned")
220 .restore_metadata
221 .clone()
222 }
223
224 pub fn set_restore_metadata(&self, metadata: Option<RestoreMetadata>) {
225 self.inner
226 .write()
227 .expect("lifecycle state lock poisoned")
228 .restore_metadata = metadata;
229 }
230
231 pub fn should_block_writes(&self) -> bool {
232 matches!(self.current_mode(), Mode::ReadOnly | Mode::Maintenance)
233 }
234
235 pub fn check_write_allowed(&self) -> Result<()> {
236 match self.current_mode() {
237 Mode::Normal => Ok(()),
238 Mode::ReadOnly => Err(ServerError::Conflict(
239 "writes are blocked in read_only mode".to_string(),
240 )),
241 Mode::Maintenance => Err(ServerError::Conflict(
242 "writes are blocked in maintenance mode".to_string(),
243 )),
244 }
245 }
246}
247
248fn now_ms() -> u64 {
249 SystemTime::now()
250 .duration_since(UNIX_EPOCH)
251 .unwrap_or_default()
252 .as_millis() as u64
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258
259 #[test]
260 fn progress_validation_rejects_empty() {
261 let invalid = Progress {
262 percent: None,
263 bytes_processed: None,
264 };
265 assert!(invalid.validate().is_err());
266 let mut state = OperationState::running();
267 assert!(state.set_progress(invalid).is_err());
268 }
269
270 #[test]
271 fn progress_validation_accepts_percent_and_bytes() {
272 let percent = Progress::percent(10);
273 assert!(percent.validate().is_ok());
274 let bytes = Progress::bytes(2048);
275 assert!(bytes.validate().is_ok());
276 }
277
278 #[test]
279 fn lifecycle_write_guard_blocks_non_normal_modes() {
280 let manager = LifecycleStateManager::new(Mode::Normal);
281 assert_eq!(manager.current_mode(), Mode::Normal);
282 assert!(!manager.should_block_writes());
283 assert!(manager.check_write_allowed().is_ok());
284
285 manager.set_mode(Mode::ReadOnly);
286 assert!(manager.should_block_writes());
287 assert!(manager.check_write_allowed().is_err());
288
289 manager.set_mode(Mode::Maintenance);
290 assert!(manager.should_block_writes());
291 assert!(manager.check_write_allowed().is_err());
292 }
293
294 #[test]
295 fn operation_state_transitions_capture_timestamps() {
296 let mut state = OperationState::queued();
297 assert_eq!(state.status, OperationStatus::Queued);
298 assert!(state.started_at_ms.is_none());
299 assert!(state.finished_at_ms.is_none());
300
301 state.mark_running();
302 assert_eq!(state.status, OperationStatus::Running);
303 assert!(state.started_at_ms.is_some());
304 assert!(state.finished_at_ms.is_none());
305
306 state.mark_finished(OperationStatus::Completed, None);
307 assert_eq!(state.status, OperationStatus::Completed);
308 assert!(state.finished_at_ms.is_some());
309 }
310
311 #[test]
312 fn restore_metadata_is_stored_and_loaded() {
313 let manager = LifecycleStateManager::new(Mode::Normal);
314 let metadata = RestoreMetadata {
315 backup_id: "backup-1".to_string(),
316 location: "/tmp/backup".to_string(),
317 restored_at_ms: 1234,
318 size_bytes: 512,
319 };
320 manager.set_restore_metadata(Some(metadata.clone()));
321 assert_eq!(manager.restore_metadata(), Some(metadata));
322 manager.set_restore_metadata(None);
323 assert!(manager.restore_metadata().is_none());
324 }
325}