2021-02-20 17:36:19 +01:00
|
|
|
use anyhow::{anyhow, Result};
|
|
|
|
use serde::{Serialize, Serializer};
|
2022-03-02 01:14:23 +01:00
|
|
|
use std::{
|
|
|
|
collections::HashMap,
|
|
|
|
sync::{Arc, RwLock},
|
|
|
|
};
|
2022-10-23 15:59:10 +02:00
|
|
|
use tracing::warn;
|
2021-02-20 17:36:19 +01:00
|
|
|
use uuid::Uuid;
|
|
|
|
|
|
|
|
#[derive(Default, Serialize, Clone)]
|
|
|
|
pub struct Job {
|
2022-02-10 15:20:15 +01:00
|
|
|
pub job_type: Option<JobType>,
|
|
|
|
pub title: String,
|
2022-02-19 16:31:51 +01:00
|
|
|
pub progress: Option<f32>,
|
2022-03-02 01:14:23 +01:00
|
|
|
pub state: JobState,
|
2021-02-20 17:36:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
pub type JobType = String;
|
|
|
|
|
2023-04-24 17:43:49 +02:00
|
|
|
#[derive(Default, Serialize, Clone, Copy, PartialEq)]
|
2022-03-02 01:14:23 +01:00
|
|
|
pub enum JobState {
|
2023-04-24 17:43:49 +02:00
|
|
|
#[default]
|
2021-02-20 17:36:19 +01:00
|
|
|
InProgress,
|
|
|
|
Done,
|
2021-06-20 16:46:45 +02:00
|
|
|
Failed,
|
2021-02-20 17:36:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Default)]
|
2022-03-02 01:14:23 +01:00
|
|
|
pub struct JobContainerData {
|
2021-02-20 17:36:19 +01:00
|
|
|
jobs: HashMap<JobId, Job>,
|
|
|
|
}
|
|
|
|
|
2022-01-30 16:00:29 +01:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub struct JobInProgessError(String);
|
|
|
|
|
2022-02-28 19:49:42 +01:00
|
|
|
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 {}
|
|
|
|
|
2022-03-02 01:14:23 +01:00
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct JobContainer(Arc<RwLock<JobContainerData>>);
|
|
|
|
|
2021-02-20 17:36:19 +01:00
|
|
|
impl JobContainer {
|
2022-03-02 01:14:23 +01:00
|
|
|
pub fn new() -> Self {
|
|
|
|
JobContainer(Arc::new(RwLock::new(JobContainerData::default())))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn add_job<S, IS>(&mut self, job_type: IS, title: S) -> Result<JobHandle>
|
|
|
|
where
|
|
|
|
S: AsRef<str>,
|
|
|
|
IS: Into<Option<S>>,
|
|
|
|
{
|
|
|
|
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()
|
|
|
|
};
|
|
|
|
|
2022-02-10 10:47:10 +01:00
|
|
|
if let Some(job_type) = &job.job_type {
|
2022-03-02 01:14:23 +01:00
|
|
|
if jobs
|
2022-02-10 10:47:10 +01:00
|
|
|
.iter()
|
2022-03-02 01:14:23 +01:00
|
|
|
.any(|(_, j)| j.state == JobState::InProgress && j.job_type == job.job_type)
|
2022-02-10 10:47:10 +01:00
|
|
|
{
|
|
|
|
return Err(JobInProgessError(format!(
|
2022-03-28 20:06:00 +02:00
|
|
|
r#"Job of type "{}" currently in progress."#,
|
2022-02-10 10:47:10 +01:00
|
|
|
job_type
|
2022-03-02 01:14:23 +01:00
|
|
|
))
|
|
|
|
.into());
|
2022-02-10 10:47:10 +01:00
|
|
|
}
|
2022-01-30 16:00:29 +01:00
|
|
|
}
|
2022-02-10 10:47:10 +01:00
|
|
|
|
2022-03-02 01:14:23 +01:00
|
|
|
let job_id = JobId(Uuid::new_v4());
|
|
|
|
jobs.insert(job_id, job);
|
|
|
|
Ok(JobHandle {
|
|
|
|
job_id,
|
|
|
|
container: self.0.clone(),
|
|
|
|
})
|
2021-02-20 17:36:19 +01:00
|
|
|
}
|
|
|
|
|
2022-03-02 01:14:23 +01:00
|
|
|
pub fn get_jobs(&self) -> Result<HashMap<JobId, Job>> {
|
|
|
|
let jobs = &self
|
|
|
|
.0
|
|
|
|
.read()
|
|
|
|
.map_err(|err| anyhow!("Couldn't lock job container for writing! {err:?}"))?
|
|
|
|
.jobs;
|
|
|
|
|
|
|
|
Ok(jobs.clone())
|
2021-02-20 17:36:19 +01:00
|
|
|
}
|
2022-03-02 01:14:23 +01:00
|
|
|
}
|
2021-02-20 17:36:19 +01:00
|
|
|
|
2023-04-24 17:43:49 +02:00
|
|
|
impl Default for JobContainer {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::new()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-02 01:14:23 +01:00
|
|
|
#[derive(Clone, Hash, PartialEq, Eq, Copy)]
|
|
|
|
pub struct JobId(Uuid);
|
|
|
|
|
|
|
|
impl Serialize for JobId {
|
|
|
|
fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
|
|
|
|
where
|
|
|
|
S: Serializer,
|
|
|
|
{
|
|
|
|
serializer.serialize_str(format!("{}", self.0).as_str())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct JobHandle {
|
|
|
|
job_id: JobId,
|
|
|
|
container: Arc<RwLock<JobContainerData>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2022-02-19 16:31:51 +01:00
|
|
|
job.progress = Some(progress);
|
2021-02-20 17:36:19 +01:00
|
|
|
if progress >= 100.0 {
|
2022-03-02 01:14:23 +01:00
|
|
|
job.state = JobState::Done;
|
2021-02-20 17:36:19 +01:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(anyhow!("No such job."))
|
|
|
|
}
|
|
|
|
}
|
2021-06-20 16:46:45 +02:00
|
|
|
|
2022-03-02 01:14:23 +01:00
|
|
|
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) {
|
2021-06-20 16:46:45 +02:00
|
|
|
job.state = state;
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(anyhow!("No such job."))
|
|
|
|
}
|
|
|
|
}
|
2021-02-20 17:36:19 +01:00
|
|
|
}
|
2022-03-02 01:14:23 +01:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|