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                // only used to have tgts.as_slice() later
170                #[allow(clippy::option_if_let_else)]
171                let tgts: Vec<_> = match &target {
172                    FormatOrTarget::Targets(t) => {
173                        let Some(v) = t
174                            .iter()
175                            .map(|s| BuildTarget::get(s))
176                            .collect::<Option<Vec<_>>>()
177                        else {
178                            return Err("Invalid target".to_string());
179                        };
180                        v
181                    }
182                    FormatOrTarget::Format(f) => Vec::new(),
183                };
184
185                let fot = match target {
186                    FormatOrTarget::Format(f) => FormatOrTargets::Format(
187                        SourceFormat::get(&f)
188                            .map_or_else(|| Err("Invalid format".to_string()), Ok)?,
189                    ),
190                    FormatOrTarget::Targets(_) => FormatOrTargets::Targets(tgts.as_slice()),
191                };
192
193                let Some(archive) = archive else {
194                    return Ok(queue.enqueue_all(fot, stale_only, clean));
195                };
196
197                let group = flams_math_archives::backend::GlobalBackend.with_tree(
198                    |tree| -> Result<bool, String> {
199                        match tree.get_group_or_archive(&archive) {
200                            Some(AoG::Archive(_)) => Ok(false),
201                            Some(AoG::Group(_)) => Ok(true),
202                            None => Err(format!("Archive {archive} not found")),
203                        }
204                    },
205                )?;
206
207                if group && path.is_some() {
208                    return Err(
209                        "Must specify either an archive with optional path or a group".to_string(),
210                    );
211                }
212
213                if group {
214                    Ok(queue.enqueue_group(&archive, fot, stale_only, clean))
215                } else {
216                    Ok(queue.enqueue_archive(
217                        &archive,
218                        fot,
219                        stale_only,
220                        path.as_deref().map(RelPath::new),
221                        clean && path.is_none(),
222                    ))
223                }
224            })?
225        })
226        .await
227    }
228
229    pub(super) async fn get_log(
230        queue: NonZeroU32,
231        archive: ArchiveId,
232        rel_path: String,
233        target: String,
234    ) -> Result<String, ServerFnError<String>> {
235        use flams_math_archives::BuildableArchive;
236        use flams_math_archives::backend::LocalBackend;
237
238        let Some(target) = flams_math_archives::formats::BuildTarget::get(&target) else {
239            return Err(format!("Target {target} not found").into());
240        };
241        let login = LoginState::get_server();
242        let id = archive.clone();
243        let Some(path) = tokio::task::spawn_blocking(move || {
244            login.with_queue(queue, |q| {
245                q.backend()
246                    .with_local_archive(&id, |a| a.map(|a| a.get_log(&rel_path, target)))
247            })
248        })
249        .await
250        .map_err(|e| e.to_string())??
251        else {
252            return Err(format!("Archive {archive} not found").into());
253        };
254        let v = tokio::fs::read(&path)
255            .await
256            .map_err(|e| format!("{e}: {}", path.display()))?;
257        Ok(String::from_utf8_lossy(&v).to_string())
258    }
259
260    pub(super) async fn migrate(queue: NonZeroU32) -> Result<usize, ServerFnError<String>> {
261        let login = LoginState::get_server();
262        if matches!(login, LoginState::NoAccounts) {
263            return Err("Migration only makes sense in public mode"
264                .to_string()
265                .into());
266        }
267        //let oauth = get_oauth().ok();
268        blocking_server_fn(move || {
269            login.with_queue(queue, |_| ())?;
270            let ((), n) = flams_system::building::queue_manager::QueueManager::get()
271                .migrate::<()>(queue.into(), |_| Ok(()))
272                .map_err(|r| format!("{r:#}"))?;
273            Ok(n)
274        })
275        .await
276    }
277
278    pub(super) async fn delete(queue: NonZeroU32) -> Result<(), ServerFnError<String>> {
279        use flams_system::building::queue_manager::QueueManager;
280        let login = LoginState::get_server();
281        blocking_server_fn(move || {
282            login.with_queue(queue, |_| ())?;
283            QueueManager::get().delete(queue.into());
284            Ok(())
285        })
286        .await
287    }
288}