1use std::cmp::PartialEq;
2use std::collections::{HashMap, HashSet};
3use std::sync::Arc;
4
5use tokio::sync::{Mutex, OwnedMutexGuard, RwLock};
6
7use crate::movement::{Movement, MovementId, MovementStatus, MovementSubsystem};
8use crate::movement::error::MovementError;
9use crate::movement::update::MovementUpdate;
10use crate::notification::NotificationDispatch;
11use crate::persist::BarkPersister;
12use crate::subsystem::Subsystem;
13
14pub struct MovementManager {
17 db: Arc<dyn BarkPersister>,
18 subsystem_ids: RwLock<HashSet<Subsystem>>,
19 active_movements: RwLock<HashMap<MovementId, Arc<Mutex<Movement>>>>,
20 notifications: NotificationDispatch,
21}
22
23impl MovementManager {
24 pub(crate) fn new(
26 db: Arc<dyn BarkPersister>,
27 notifications: NotificationDispatch,
28 ) -> Self {
29 Self {
30 db, notifications,
31 subsystem_ids: RwLock::new(HashSet::new()),
32 active_movements: RwLock::new(HashMap::new()),
33 }
34 }
35
36 pub async fn register_subsystem(&self, id: Subsystem) -> anyhow::Result<(), MovementError> {
40 let mut guard = self.subsystem_ids.write().await;
41 if guard.contains(&id) {
42 Err(MovementError::SubsystemError {
43 id, error: "Subsystem already registered".into(),
44 })
45 } else {
46 guard.insert(id);
47 Ok(())
48 }
49 }
50
51 async fn persist_new_movement(
55 &self,
56 subsystem_id: Subsystem,
57 movement_kind: impl Into<String>,
58 action_id: Option<&str>,
59 ) -> anyhow::Result<MovementId, MovementError> {
60 self.db.create_new_movement(
61 MovementStatus::Pending,
62 &MovementSubsystem {
63 name: subsystem_id.as_name().to_string(),
64 kind: movement_kind.into(),
65 },
66 chrono::Local::now(),
67 action_id,
68 ).await.map_err(|e| MovementError::CreationError { e })
69 }
70
71 pub async fn new_movement(
89 &self,
90 subsystem_id: Subsystem,
91 movement_kind: impl Into<String>,
92 ) -> anyhow::Result<MovementId, MovementError> {
93 let id = self.persist_new_movement(subsystem_id, movement_kind, None).await?;
94 let movement = self.db.get_movement_by_id(id).await
95 .map_err(|e| MovementError::LoadError { id, e })?;
96 self.notifications.dispatch_movement_created(movement);
97 Ok(id)
98 }
99
100 pub async fn new_guarded_movement(
114 self: &Arc<Self>,
115 subsystem_id: Subsystem,
116 movement_kind: impl Into<String>,
117 on_drop: OnDropStatus,
118 ) -> anyhow::Result<MovementGuard, MovementError> {
119 Ok(MovementGuard::new(
120 self.new_movement(subsystem_id, movement_kind).await?, self.clone(), on_drop,
121 ))
122 }
123
124 pub async fn new_movement_with_update(
139 &self,
140 subsystem_id: Subsystem,
141 movement_kind: impl Into<String>,
142 update: MovementUpdate,
143 ) -> anyhow::Result<MovementId, MovementError> {
144 let id = self.persist_new_movement(subsystem_id, movement_kind, None).await?;
145 self.update_movement(id, update).await?;
146 let movement = self.db.get_movement_by_id(id).await
147 .map_err(|e| MovementError::LoadError { id, e })?;
148 self.notifications.dispatch_movement_created(movement);
149 Ok(id)
150 }
151
152 pub async fn get_or_create_movement_with_action(
161 &self,
162 subsystem_id: Subsystem,
163 movement_kind: impl Into<String>,
164 action_id: &str,
165 update: MovementUpdate,
166 ) -> anyhow::Result<MovementId, MovementError> {
167 let subsystem = MovementSubsystem {
168 name: subsystem_id.as_name().to_string(),
169 kind: movement_kind.into(),
170 };
171 let (id, created) = self.db.get_or_create_movement_for_action(
172 &subsystem, chrono::Local::now(), action_id, update,
173 ).await.map_err(|e| MovementError::CreationError { e })?;
174
175 if created {
177 let movement = self.db.get_movement_by_id(id).await
178 .map_err(|e| MovementError::LoadError { id, e })?;
179 self.notifications.dispatch_movement_created(movement);
180 }
181 Ok(id)
182 }
183
184 pub async fn new_guarded_movement_with_update(
200 self: &Arc<Self>,
201 subsystem_id: Subsystem,
202 movement_kind: impl Into<String>,
203 on_drop: OnDropStatus,
204 update: MovementUpdate,
205 ) -> anyhow::Result<MovementGuard, MovementError> {
206 Ok(MovementGuard::new(
207 self.new_movement_with_update(subsystem_id, movement_kind, update).await?,
208 self.clone(),
209 on_drop,
210 ))
211 }
212
213 pub async fn new_finished_movement(
232 &self,
233 subsystem_id: Subsystem,
234 movement_kind: impl Into<String>,
235 status: MovementStatus,
236 details: MovementUpdate,
237 ) -> anyhow::Result<MovementId, MovementError> {
238 if status == MovementStatus::Pending {
239 return Err(MovementError::IncorrectPendingStatus);
240 }
241 let id = self.persist_new_movement(subsystem_id, movement_kind, None).await?;
242 let mut movement = self.db.get_movement_by_id(id).await
243 .map_err(|e| MovementError::LoadError { id, e })?;
244 let at = chrono::Local::now();
245 details.apply_to(&mut movement, at);
246 movement.status = status;
247 movement.time.completed_at = Some(at);
248 self.db.update_movement(&movement).await
249 .map_err(|e| MovementError::PersisterError { id, e })?;
250 self.notifications.dispatch_movement_created(movement);
251 Ok(id)
252 }
253
254 pub async fn update_movement(
270 &self,
271 id: MovementId,
272 update: MovementUpdate,
273 ) -> anyhow::Result<(), MovementError> {
274 let mut guard = self.get_cached_movement(id).await?;
276
277 update.apply_to(&mut *guard, chrono::Local::now());
279
280 self.db.update_movement(&guard).await
282 .map_err(|e| MovementError::PersisterError { id, e })?;
283
284 self.notifications.dispatch_movement_updated(guard.clone());
285
286 if guard.status != MovementStatus::Pending {
288 drop(guard);
289 self.unload_movement_from_cache(id).await?;
290 }
291 Ok(())
292 }
293
294 pub async fn patch_metadata(
297 &self,
298 id: MovementId,
299 patch: &serde_json::Value,
300 ) -> anyhow::Result<(), MovementError> {
301 let mut guard = self.get_cached_movement(id).await?;
302
303 let mut value = serde_json::Value::Object(std::mem::take(&mut guard.metadata));
304 crate::utils::json_patch::merge(&mut value, patch);
305 guard.metadata = match value {
306 serde_json::Value::Object(map) => map,
307 _ => serde_json::Map::new(),
308 };
309 guard.time.updated_at = chrono::Local::now();
310
311 self.db.update_movement(&guard).await
312 .map_err(|e| MovementError::PersisterError { id, e })?;
313 self.notifications.dispatch_movement_updated(guard.clone());
314
315 if guard.status != MovementStatus::Pending {
316 drop(guard);
317 self.unload_movement_from_cache(id).await?;
318 }
319 Ok(())
320 }
321
322 pub async fn finish_movement(
337 &self,
338 id: MovementId,
339 new_status: MovementStatus,
340 ) -> anyhow::Result<(), MovementError> {
341 if new_status == MovementStatus::Pending {
342 return Err(MovementError::IncorrectPendingStatus);
343 }
344
345 let mut guard = self.get_cached_movement(id).await?;
347
348 guard.status = new_status;
350 guard.time.completed_at = Some(chrono::Local::now());
351 self.db.update_movement(&*guard).await
352 .map_err(|e| MovementError::PersisterError { id, e })?;
353
354 self.notifications.dispatch_movement_updated(guard.clone());
355
356 drop(guard);
357 self.unload_movement_from_cache(id).await
358 }
359
360 pub async fn finish_movement_with_update(
375 &self,
376 id: MovementId,
377 new_status: MovementStatus,
378 update: MovementUpdate,
379 ) -> anyhow::Result<(), MovementError> {
380 if new_status == MovementStatus::Pending {
381 return Err(MovementError::IncorrectPendingStatus);
382 }
383
384 let mut guard = self.get_cached_movement(id).await?;
385
386 update.apply_to(&mut *guard, chrono::Local::now());
387 guard.status = new_status;
388 guard.time.completed_at = Some(chrono::Local::now());
389 self.db.update_movement(&*guard).await
390 .map_err(|e| MovementError::PersisterError { id, e })?;
391
392 self.notifications.dispatch_movement_updated(guard.clone());
393
394 drop(guard);
395 self.unload_movement_from_cache(id).await
396 }
397
398 async fn get_cached_movement(
399 &self,
400 id: MovementId,
401 ) -> anyhow::Result<OwnedMutexGuard<Movement>, MovementError> {
402 if let Some(lock) = self.active_movements.read().await.get(&id).cloned() {
403 return Ok(lock.lock_owned().await);
404 }
405
406 let movement_lock = {
407 let active_guard = self.active_movements.write().await;
409 if let Some(lock) = active_guard.get(&id).cloned() {
410 lock
411 } else {
412 Arc::new(Mutex::new(
413 self.db.get_movement_by_id(id).await
414 .map_err(|e| MovementError::LoadError { id, e })?
415 ))
416 }
417 };
418 Ok(movement_lock.lock_owned().await)
419 }
420
421 async fn unload_movement_from_cache(&self, id: MovementId) -> anyhow::Result<(), MovementError> {
422 let mut lock = self.active_movements.write().await;
423 lock.remove(&id);
424 Ok(())
425 }
426}
427
428#[derive(Debug, Copy, Clone, PartialEq, Eq)]
432pub enum OnDropStatus {
433 Canceled,
435 Failed,
437}
438
439impl From<OnDropStatus> for MovementStatus {
440 fn from(status: OnDropStatus) -> Self {
441 match status {
442 OnDropStatus::Canceled => MovementStatus::Canceled,
443 OnDropStatus::Failed => MovementStatus::Failed,
444 }
445 }
446}
447
448pub struct MovementGuard {
456 id: MovementId,
457 manager: Arc<MovementManager>,
458 on_drop: OnDropStatus,
459 has_finished: bool,
460}
461
462impl<'a> MovementGuard {
463 pub fn new(
470 id: MovementId,
471 manager: Arc<MovementManager>,
472 on_drop: OnDropStatus,
473 ) -> Self {
474 Self {
475 id,
476 manager,
477 on_drop,
478 has_finished: false,
479 }
480 }
481
482 pub fn id(&self) -> MovementId {
484 self.id
485 }
486
487 pub fn set_on_drop_status(&mut self, status: OnDropStatus) {
492 self.on_drop = status;
493 }
494
495 pub async fn apply_update(
503 &self,
504 update: MovementUpdate,
505 ) -> anyhow::Result<(), MovementError> {
506 self.manager.update_movement(self.id, update).await
507 }
508
509 pub async fn cancel(&mut self) -> anyhow::Result<(), MovementError> {
511 self.stop();
512 self.manager.finish_movement(self.id, MovementStatus::Canceled).await
513 }
514
515 pub async fn fail(&mut self) -> anyhow::Result<(), MovementError> {
517 self.stop();
518 self.manager.finish_movement(self.id, MovementStatus::Failed).await
519 }
520
521 pub async fn success(
526 &mut self,
527 ) -> anyhow::Result<(), MovementError> {
528 self.stop();
529 self.manager.finish_movement(self.id, MovementStatus::Successful).await
530 }
531
532 pub fn stop(&mut self) {
535 self.has_finished = true;
536 }
537}
538
539impl Drop for MovementGuard {
540 fn drop(&mut self) {
541 if !self.has_finished {
542 let manager = self.manager.clone();
544 let id = self.id;
545 let on_drop = self.on_drop;
546
547 crate::utils::spawn(async move {
548 if let Err(e) = manager.finish_movement(id, on_drop.into()).await {
549 log::error!("An error occurred in MovementGuard::drop(): {:#}", e);
550 }
551 });
552 }
553 }
554}