Skip to main content

flams_router_buildqueue_base/
server_fns.rs

1use std::num::NonZeroU32;
2
3use crate::{FormatOrTarget, QueueInfo};
4use ftml_uris::ArchiveId;
5use leptos::prelude::*;
6
7#[server(prefix = "/api/buildqueue", endpoint = "get_queues")]
8pub async fn get_queues() -> Result<Vec<QueueInfo>, ServerFnError<String>> {
9    server::get_queues().await
10}
11
12#[server(prefix = "/api/buildqueue", endpoint = "run")]
13pub async fn run(id: NonZeroU32) -> Result<(), ServerFnError<String>> {
14    server::run(id).await
15}
16
17#[server(prefix = "/api/buildqueue", endpoint = "requeue")]
18pub async fn requeue(id: NonZeroU32) -> Result<(), ServerFnError<String>> {
19    server::requeue(id).await
20}
21
22#[server(prefix = "/api/buildqueue", endpoint = "enqueue")]
23pub async fn enqueue(
24    archive: Option<ArchiveId>,
25    target: FormatOrTarget,
26    path: Option<String>,
27    stale_only: Option<bool>,
28    queue: Option<NonZeroU32>,
29    clean: bool,
30) -> Result<usize, ServerFnError<String>> {
31    server::enqueue(archive, target, path, stale_only, queue, clean).await
32}
33
34#[server(prefix = "/api/buildqueue", endpoint = "log")]
35pub async fn get_log(
36    queue: NonZeroU32,
37    archive: ArchiveId,
38    rel_path: String,
39    target: String,
40) -> Result<either::Either<String, String>, ServerFnError<String>> {
41    let is_check = target == ftml_solver::CHECK.name;
42    let log = server::get_log(queue, archive, rel_path, target).await?;
43    if is_check {
44        return Ok(either::Right(log));
45        /*return ftml_solver_trace::results::DocumentCheckResult::from_json(&log).map_or_else(
46            |err| {
47                Ok(either::Left(format!(
48                    "Error deserializing type check log: {err}\n\n{log}"
49                )))
50            },
51            |l| Ok(either::Right(l)),
52        );*/
53    }
54    Ok(either::Left(log))
55}
56
57#[server(prefix = "/api/buildqueue", endpoint = "migrate")]
58pub async fn migrate(queue: NonZeroU32) -> Result<usize, ServerFnError<String>> {
59    server::migrate(queue).await
60}
61
62#[server(prefix = "/api/buildqueue", endpoint = "delete")]
63pub async fn delete(queue: NonZeroU32) -> Result<(), ServerFnError<String>> {
64    server::delete(queue).await
65}
66
67#[cfg(feature = "ssr")]
68pub mod server {
69    use std::num::NonZeroU32;
70
71    use crate::{FormatOrTarget, LoginQueue, QueueInfo, RepoInfo};
72    use flams_math_archives::backend::SandboxedRepository;
73    use flams_math_archives::utils::path_ext::RelPath;
74    use flams_router_base::LoginState;
75    use flams_system::building::Queue;
76    use flams_system::building::queue_manager::QueueManager;
77    use flams_web_utils::blocking_server_fn;
78    use ftml_uris::ArchiveId;
79    use leptos::prelude::*;
80
81    /// #### Errors
82    pub(super) async fn get_queues() -> Result<Vec<QueueInfo>, ServerFnError<String>> {
83        let login = LoginState::get_server();
84        //let oauth = get_oauth().ok();
85        blocking_server_fn(move || {
86            let ls = match login {
87                LoginState::None | LoginState::Loading => {
88                    return Err(format!("Not logged in: {login:?}"));
89                }
90                LoginState::NoAccounts
91                | LoginState::Admin
92                | LoginState::User { is_admin: true, .. } => QueueManager::get().all_queues(),
93                LoginState::User { name, .. } => QueueManager::get().queues_for_user(&name),
94            };
95            let mut ret = Vec::new();
96            for (k, v, d) in ls {
97                let archives = d.map(|d| {
98                    let mut archives = Vec::new();
99                    for ri in d {
100                        match ri {
101                            SandboxedRepository::Copy(id) => archives.push(RepoInfo::Copy(id)),
102                            SandboxedRepository::Git {
103                                id,
104                                branch,
105                                commit,
106                                remote,
107                            } => {
108                                archives.push(RepoInfo::Git {
109                                    id,
110                                    branch: branch.to_string(),
111                                    commit,
112                                    remote: remote.to_string(), //,updates
113                                });
114                            }
115                        }
116                    }
117                    archives
118                });
119
120                ret.push(QueueInfo {
121                    id: k.into(),
122                    name: v.to_string(),
123                    archives,
124                });
125            }
126            Ok(ret)
127        })
128        .await
129    }
130
131    /// #### Errors
132    pub(super) async fn run(id: NonZeroU32) -> Result<(), ServerFnError<String>> {
133        use flams_system::building::queue_manager::QueueManager;
134        let login = LoginState::get_server();
135        blocking_server_fn(move || {
136            login.with_queue(id, |_| ())?;
137            QueueManager::get()
138                .start_queue(id.into())
139                .map_err(|()| "Queue does not exist".to_string())?;
140            Ok(())
141        })
142        .await
143    }
144
145    pub(super) async fn requeue(id: NonZeroU32) -> Result<(), ServerFnError<String>> {
146        let login = LoginState::get_server();
147        blocking_server_fn(move || login.with_queue(id, Queue::requeue_failed)).await
148    }
149
150    pub(super) async fn enqueue(
151        archive: Option<ArchiveId>,
152        target: FormatOrTarget,
153        path: Option<String>,
154        stale_only: Option<bool>,
155        queue: Option<NonZeroU32>,
156        clean: bool,
157    ) -> Result<usize, ServerFnError<String>> {
158        use flams_math_archives::formats::BuildTarget;
159        use flams_math_archives::formats::FormatOrTargets;
160        use flams_math_archives::formats::SourceFormat;
161        use flams_math_archives::manager::ArchiveOrGroup as AoG;
162
163        let login = LoginState::get_server();
164
165        blocking_server_fn(move || {
166            login.with_opt_queue(queue, |_, queue| {
167                let stale_only = stale_only.unwrap_or(true);
168
169                #[allow(clippy::option_if_let_else)]
170                let tgts: Vec<_> = match &target {
171                    FormatOrTarget::Targets(t) => {
172                        let Some(v) = t
173                            .iter()
174                            .map(|s| BuildTarget::get(s))
175                            .collect::<Option<Vec<_>>>()
176                        else {
177                            return Err("Invalid target".to_string());
178                        };
179                        v
180                    }
181                    FormatOrTarget::Format(_) => Vec::new(),
182                };
183
184                let fot = match target {
185                    FormatOrTarget::Format(f) => FormatOrTargets::Format(
186                        SourceFormat::get(&f)
187                            .map_or_else(|| Err("Invalid format".to_string()), Ok)?,
188                    ),
189                    FormatOrTarget::Targets(_) => FormatOrTargets::Targets(tgts.as_slice()),
190                };
191
192                let Some(archive) = archive else {
193                    return Ok(queue.enqueue_all(fot, stale_only, clean));
194                };
195
196                let group = flams_math_archives::backend::GlobalBackend.with_tree(
197                    |tree| -> Result<bool, String> {
198                        match tree.get_group_or_archive(&archive) {
199                            Some(AoG::Archive(_)) => Ok(false),
200                            Some(AoG::Group(_)) => Ok(true),
201                            None => Err(format!("Archive {archive} not found")),
202                        }
203                    },
204                )?;
205
206                if group && path.is_some() {
207                    return Err(
208                        "Must specify either an archive with optional path or a group".to_string(),
209                    );
210                }
211
212                if group {
213                    Ok(queue.enqueue_group(&archive, fot, stale_only, clean))
214                } else {
215                    Ok(queue.enqueue_archive(
216                        &archive,
217                        fot,
218                        stale_only,
219                        path.as_deref().map(RelPath::new),
220                        clean && path.is_none(),
221                    ))
222                }
223            })?
224        })
225        .await
226    }
227
228    pub(super) async fn get_log(
229        queue: NonZeroU32,
230        archive: ArchiveId,
231        rel_path: String,
232        target: String,
233    ) -> Result<String, ServerFnError<String>> {
234        use flams_math_archives::BuildableArchive;
235        use flams_math_archives::backend::LocalBackend;
236
237        let Some(target) = flams_math_archives::formats::BuildTarget::get(&target) else {
238            return Err(format!("Target {target} not found").into());
239        };
240        let login = LoginState::get_server();
241        let id = archive.clone();
242        let Some(path) = tokio::task::spawn_blocking(move || {
243            login.with_queue(queue, |q| {
244                q.backend()
245                    .with_local_archive(&id, |a| a.map(|a| a.get_log(&rel_path, target)))
246            })
247        })
248        .await
249        .map_err(|e| e.to_string())??
250        else {
251            return Err(format!("Archive {archive} not found").into());
252        };
253        let v = tokio::fs::read(&path)
254            .await
255            .map_err(|e| format!("{e}: {}", path.display()))?;
256        Ok(String::from_utf8_lossy(&v).to_string())
257    }
258
259    pub(super) async fn migrate(queue: NonZeroU32) -> Result<usize, ServerFnError<String>> {
260        let login = LoginState::get_server();
261        if matches!(login, LoginState::NoAccounts) {
262            return Err("Migration only makes sense in public mode"
263                .to_string()
264                .into());
265        }
266        //let oauth = get_oauth().ok();
267        blocking_server_fn(move || {
268            login.with_queue(queue, |_| ())?;
269            let ((), n) = flams_system::building::queue_manager::QueueManager::get()
270                .migrate::<()>(queue.into(), |_| Ok(()))
271                .map_err(|r| format!("{r:#}"))?;
272            Ok(n)
273        })
274        .await
275    }
276
277    pub(super) async fn delete(queue: NonZeroU32) -> Result<(), ServerFnError<String>> {
278        use flams_system::building::queue_manager::QueueManager;
279        let login = LoginState::get_server();
280        blocking_server_fn(move || {
281            login.with_queue(queue, |_| ())?;
282            QueueManager::get().delete(queue.into());
283            Ok(())
284        })
285        .await
286    }
287}