Skip to main content

rumtk_web/utils/
jobs.rs

1/*
2 * rumtk attempts to implement HL7 and medical protocols for interoperability in medicine.
3 * This toolkit aims to be reliable, simple, performant, and standards compliant.
4 * Copyright (C) 2025  Luis M. Santos, M.D. <lsantos@medicalmasses.com>
5 * Copyright (C) 2025  Ethan Dixon
6 * Copyright (C) 2025  MedicalMasses L.L.C. <contact@medicalmasses.com>
7 *
8 * This program is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
20 */
21use crate::components::sanitize::Sanitized;
22use rumtk_core::base::RUMResult;
23use rumtk_core::buffers::*;
24use rumtk_core::id::id_to_uuid;
25use rumtk_core::strings::rumtk_format;
26use rumtk_core::threading::threading_manager::{Task, TaskID, TaskManager};
27
28pub type JobID = TaskID;
29pub type JobBuffer = RUMBuffer;
30
31pub type JobResult = RUMResult<Option<Sanitized>>;
32pub type Job = Task<JobResult>;
33type JobManager = TaskManager<JobResult>;
34
35static mut TASK_MANAGER: Option<JobManager> = None;
36
37pub fn job_str_id_to_id(id: &str) -> RUMResult<JobID> {
38    id_to_uuid(id)
39}
40
41pub fn init_job_manager(workers: &usize) -> RUMResult<()> {
42    let manager = TaskManager::<JobResult>::new(workers)?;
43    unsafe {
44        TASK_MANAGER = Some(manager);
45    }
46    Ok(())
47}
48
49pub fn get_manager() -> RUMResult<&'static mut JobManager> {
50    unsafe {
51        match TASK_MANAGER.as_mut() {
52            Some(m) => Ok(m),
53            None => return Err(rumtk_format!("TaskManager is not initialized")),
54        }
55    }
56}
57
58#[macro_export]
59macro_rules! rumtk_web_init_job_manager {
60    ( $workers:expr ) => {{
61        use $crate::jobs::init_job_manager;
62        init_job_manager($workers)
63    }};
64}
65
66#[macro_export]
67macro_rules! rumtk_web_get_job_manager {
68    (  ) => {{
69        use $crate::jobs::get_manager;
70        get_manager()
71    }};
72}
73
74#[macro_export]
75macro_rules! rumtk_web_generate_job_id {
76    ( $id:expr ) => {{
77        use $crate::jobs::job_str_id_to_id;
78        job_str_id_to_id($id)
79    }};
80}
81
82///
83/// THis macro allows you to check if a background job has completed.
84///
85/// If the job has completed, return the result which is of type [JobResult].
86///
87/// If the job is still going, force render a drop in loader component set to retry the check. This
88/// loader gets passed the calling element name (`$element_name`) so that it can render the results
89/// as it sees fit.
90///
91/// ## Example
92///
93/// ### Loader Render
94/// ```
95/// use rumtk_core::{rumtk_async_sleep, rumtk_new_lock};
96/// use rumtk_core::strings::{RUMString};
97/// use rumtk_web::utils::testdata::data::{JOB_LOADER_TEST_PATTERN};
98/// use rumtk_web::defaults::{PARAMS_ID, PARAMS_CSS_CLASS, DEFAULT_TEXT_ITEM, DEFAULT_NO_TEXT};
99/// use rumtk_web::utils::jobs::{JobResult};
100/// use rumtk_web::{HTMLResult, SharedAppState, URLParams, URLPath, AppState, RUMWebResponse, RUMWebData};
101/// use rumtk_web::{rumtk_web_init_job_manager, rumtk_web_get_job_manager, rumtk_web_check_on_job, rumtk_web_get_text_item, rumtk_web_post_process_html};
102/// use rumtk_web::components::job_loader::{job_loader, JobLoader};
103/// use rumtk_web::ComponentResult;
104///
105/// let workers: usize = 5;
106/// rumtk_web_init_job_manager!(&workers);
107///
108/// async fn basic_processor() -> JobResult {
109///     rumtk_async_sleep!(100).await;
110///     Ok(None)
111/// }
112///
113/// fn my_element(_path_components: URLPath, params: URLParams, state: SharedAppState) -> ComponentResult<JobLoader> {
114///     job_loader(_path_components, params, state)
115/// }
116///
117/// let app_state = rumtk_new_lock!(AppState::default());
118/// let mut params = RUMWebData::new();
119/// let job_id = rumtk_web_get_job_manager!().unwrap().spawn_task(basic_processor()).unwrap();
120/// params.insert(RUMString::from(PARAMS_ID), job_id.to_string());
121/// let rendered = my_element(&[], &params, app_state.clone()).unwrap().to_string();
122///
123/// assert!(rendered.as_str().contains(JOB_LOADER_TEST_PATTERN), "Element did not render loader!");
124///
125/// ```
126///
127/// ### Component Render
128/// ```
129/// use rumtk_core::{rumtk_sleep, rumtk_new_lock};
130/// use rumtk_core::strings::{RUMString};
131/// use rumtk_web::utils::testdata::data::{JOB_LOADER_TEST_PATTERN};
132/// use rumtk_web::defaults::{PARAMS_ID, PARAMS_CSS_CLASS, DEFAULT_TEXT_ITEM, DEFAULT_NO_TEXT};
133/// use rumtk_web::utils::jobs::{JobResult};
134/// use rumtk_web::{HTMLResult, SharedAppState, URLParams, URLPath, AppState, RUMWebResponse, RUMWebData};
135/// use rumtk_web::{rumtk_web_init_job_manager, rumtk_web_get_job_manager, rumtk_web_check_on_job, rumtk_web_get_text_item};
136///
137/// use rumtk_web::components::job_loader::{job_loader, JobLoader};
138///
139/// use rumtk_web::components::sanitize::sanitized;
140///
141/// const HELLO_STR: &str = "Hello World";
142///
143/// let workers: usize = 5;
144/// rumtk_web_init_job_manager!(&workers);
145///
146/// async fn basic_processor() -> JobResult {
147///     let result = RUMString::from(HELLO_STR);
148///     let sanitized = sanitized(result)?;
149///     Ok(Some(sanitized))
150/// }
151///
152/// let app_state = rumtk_new_lock!(AppState::default());
153/// let job_id = rumtk_web_get_job_manager!().unwrap().spawn_task(basic_processor()).unwrap();
154///
155/// let results = rumtk_web_get_job_manager!().unwrap().wait_on(&job_id).unwrap().unwrap().unwrap().unwrap();
156///
157/// rumtk_sleep!(1);
158/// let rendered = results.to_string();
159///
160/// assert_eq!(&rendered, HELLO_STR, "Job returned wrong result!");
161///
162/// ```
163///
164#[macro_export]
165macro_rules! rumtk_web_check_on_job {
166    ( $job_id:expr, $state:expr ) => {{
167        use rumtk_core::id::id_to_uuid;
168        use $crate::components::job_loader::job_loader;
169        use $crate::defaults::{PARAMS_CSS_CLASS, PARAMS_ELEMENT, PARAMS_ID};
170        use $crate::{rumtk_web_get_job_manager};
171
172        let id = id_to_uuid($job_id)?;
173        let job_finished = rumtk_web_get_job_manager!()?.is_finished(&id);
174        let result = match job_finished {
175            true => rumtk_web_get_job_manager!()?.wait_on(&id)?,
176            false => None,
177        };
178
179        match result {
180            Some(r) => r?,
181            None => None,
182        }
183    }};
184}