use anyhow::{anyhow, Result}; use serde::{Serialize, Serializer}; use std::{ collections::HashMap, sync::{Arc, RwLock}, }; use tracing::warn; use uuid::Uuid; #[derive(Default, Serialize, Clone)] pub struct Job { pub job_type: Option, pub title: String, pub progress: Option, pub state: JobState, } pub type JobType = String; #[derive(Default, Serialize, Clone, Copy, PartialEq)] pub enum JobState { #[default] InProgress, Done, Failed, } #[derive(Default)] pub struct JobContainerData { jobs: HashMap, } #[derive(Debug, Clone)] pub struct JobInProgessError(String); impl std::fmt::Display for JobInProgessError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "job of type {} is already in progress", self.0) } } impl std::error::Error for JobInProgessError {} #[derive(Clone)] pub struct JobContainer(Arc>); impl JobContainer { pub fn new() -> Self { JobContainer(Arc::new(RwLock::new(JobContainerData::default()))) } pub fn add_job(&mut self, job_type: IS, title: S) -> Result where S: AsRef, IS: Into>, { let jobs = &mut self .0 .write() .map_err(|err| anyhow!("Couldn't lock job container for writing! {err:?}"))? .jobs; let job = Job { job_type: job_type.into().map(|jt| String::from(jt.as_ref())), title: String::from(title.as_ref()), ..Default::default() }; if let Some(job_type) = &job.job_type { if jobs .iter() .any(|(_, j)| j.state == JobState::InProgress && j.job_type == job.job_type) { return Err(JobInProgessError(format!( r#"Job of type "{}" currently in progress."#, job_type )) .into()); } } let job_id = JobId(Uuid::new_v4()); jobs.insert(job_id, job); Ok(JobHandle { job_id, container: self.0.clone(), }) } pub fn get_jobs(&self) -> Result> { let jobs = &self .0 .read() .map_err(|err| anyhow!("Couldn't lock job container for writing! {err:?}"))? .jobs; Ok(jobs.clone()) } } impl Default for JobContainer { fn default() -> Self { Self::new() } } #[derive(Clone, Hash, PartialEq, Eq, Copy)] pub struct JobId(Uuid); impl Serialize for JobId { fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where S: Serializer, { serializer.serialize_str(format!("{}", self.0).as_str()) } } pub struct JobHandle { job_id: JobId, container: Arc>, } impl JobHandle { pub fn update_progress(&mut self, progress: f32) -> Result<()> { let jobs = &mut self .container .write() .map_err(|err| anyhow!("Couldn't lock job container for writing! {err:?}"))? .jobs; if let Some(job) = jobs.get_mut(&self.job_id) { job.progress = Some(progress); if progress >= 100.0 { job.state = JobState::Done; } Ok(()) } else { Err(anyhow!("No such job.")) } } pub fn update_state(&mut self, state: JobState) -> Result<()> { let jobs = &mut self .container .write() .map_err(|err| anyhow!("Couldn't lock job container for writing! {err:?}"))? .jobs; if let Some(job) = jobs.get_mut(&self.job_id) { job.state = state; Ok(()) } else { Err(anyhow!("No such job.")) } } } impl Drop for JobHandle { fn drop(&mut self) { let update_result = self.update_state(JobState::Failed); if let Err(err) = update_result { warn!("Handle dropped, but couldn't set self as failed! {:?}", err); } } }