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(&[], ¶ms, 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}