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::HTMLResult;
22use rumtk_core::core::RUMResult;
23use rumtk_core::id::id_to_uuid;
24use rumtk_core::strings::rumtk_format;
25use rumtk_core::threading::threading_manager::{Task, TaskID, TaskManager};
26use rumtk_core::types::RUMBuffer;
27
28pub type JobID = TaskID;
29pub type JobBuffer = RUMBuffer;
30
31pub type JobResult = RUMResult<Option<HTMLResult>>;
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, ToCompactString};
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, rumtk_web_init_components};
102///
103/// let workers: usize = 5;
104/// rumtk_web_init_job_manager!(&workers);
105/// rumtk_web_init_components!(
106///     Some(vec![
107///         ("my_element", my_element)
108///     ])
109/// );
110///
111/// async fn basic_processor() -> JobResult {
112///     rumtk_async_sleep!(100).await;
113///     Ok(None)
114/// }
115///
116/// fn my_element(_path_components: URLPath, params: URLParams, state: SharedAppState) -> HTMLResult {
117///     let job_id = rumtk_web_get_text_item!(params, PARAMS_ID, DEFAULT_NO_TEXT);
118///     let css_class = rumtk_web_get_text_item!(params, PARAMS_CSS_CLASS, DEFAULT_TEXT_ITEM);
119///
120///     let job_result = rumtk_web_check_on_job!("my_element", job_id, state);
121///
122///     let job_data = match job_result {
123///         Some(t) => t?.to_rumstring(),
124///         _ => RUMString::new("")
125///     };
126///
127///     rumtk_web_post_process_html!(job_data)
128/// }
129///
130/// let app_state = rumtk_new_lock!(AppState::default());
131/// let mut params = RUMWebData::new();
132/// let job_id = rumtk_web_get_job_manager!().unwrap().spawn_task(basic_processor()).unwrap();
133/// params.insert(RUMString::from(PARAMS_ID), job_id.to_compact_string());
134/// let rendered = my_element(&[], &params, app_state.clone()).unwrap().to_rumstring();
135///
136/// assert!(rendered.as_str().contains(JOB_LOADER_TEST_PATTERN), "Element did not render loader!");
137///
138/// ```
139///
140/// ### Component Render
141/// ```
142/// use rumtk_core::{rumtk_sleep, rumtk_new_lock};
143/// use rumtk_core::strings::{RUMString, ToCompactString};
144/// use rumtk_web::utils::testdata::data::{JOB_LOADER_TEST_PATTERN};
145/// use rumtk_web::defaults::{PARAMS_ID, PARAMS_CSS_CLASS, DEFAULT_TEXT_ITEM, DEFAULT_NO_TEXT};
146/// use rumtk_web::utils::jobs::{JobResult};
147/// use rumtk_web::{HTMLResult, SharedAppState, URLParams, URLPath, AppState, RUMWebResponse, RUMWebData};
148/// 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, rumtk_web_init_components};
149///
150/// const HELLO_STR: &str = "Hello World";
151///
152/// let workers: usize = 5;
153/// rumtk_web_init_job_manager!(&workers);
154/// rumtk_web_init_components!(
155///     Some(vec![
156///         ("my_element", my_element)
157///     ])
158/// );
159///
160/// async fn basic_processor() -> JobResult {
161///     Ok(Some(rumtk_web_post_process_html!(RUMString::new(HELLO_STR))))
162/// }
163///
164/// fn my_element(_path_components: URLPath, params: URLParams, state: SharedAppState) -> HTMLResult {
165///     let job_id = rumtk_web_get_text_item!(params, PARAMS_ID, DEFAULT_NO_TEXT);
166///     let css_class = rumtk_web_get_text_item!(params, PARAMS_CSS_CLASS, DEFAULT_TEXT_ITEM);
167///
168///     let job_result = rumtk_web_check_on_job!("my_element", job_id, state);
169///
170///     let job_data = job_result.unwrap()?.to_rumstring();
171///
172///     rumtk_web_post_process_html!(job_data)
173/// }
174///
175/// let app_state = rumtk_new_lock!(AppState::default());
176/// let mut params = RUMWebData::new();
177/// let job_id = rumtk_web_get_job_manager!().unwrap().spawn_task(basic_processor()).unwrap();
178/// params.insert(RUMString::from(PARAMS_ID), job_id.to_compact_string());
179///
180/// rumtk_sleep!(1);
181/// let rendered = my_element(&[], &params, app_state.clone()).unwrap().to_rumstring();
182///
183/// assert!(rendered.is_empty(), "Element results survived the rendering process's filtering!");
184///
185/// ```
186///
187#[macro_export]
188macro_rules! rumtk_web_check_on_job {
189    ( $element_name:expr, $job_id:expr, $state:expr ) => {{
190        use $crate::defaults::{DEFAULT_TEXT_ITEM};
191        rumtk_web_check_on_job!($element_name, $job_id, DEFAULT_TEXT_ITEM, $state)
192    }};
193    ( $element_name:expr, $job_id:expr, $css_class:expr, $state:expr ) => {{
194        use rumtk_core::id::id_to_uuid;
195        use $crate::defaults::{PARAMS_CSS_CLASS, PARAMS_ELEMENT, PARAMS_ID};
196        use $crate::{rumtk_web_get_job_manager, rumtk_web_render_component};
197
198        let id = id_to_uuid($job_id)?;
199        let job_finished = rumtk_web_get_job_manager!()?.is_finished(&id);
200        let result = match job_finished {
201            true => &rumtk_web_get_job_manager!()?.wait_on(&id)?.result,
202            false => {
203                return rumtk_web_render_component!(
204                    "job_loader",
205                    [
206                        (PARAMS_ID, $job_id),
207                        (PARAMS_ELEMENT, $element_name),
208                        (PARAMS_CSS_CLASS, $css_class),
209                    ], $state
210                );
211            },
212        };
213
214        match result {
215            Some(r) => r.clone()?,
216            None => None,
217        }
218    }};
219}