flams_router_buildqueue_base/
server_fns.rs1use 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 }
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 pub(super) async fn get_queues() -> Result<Vec<QueueInfo>, ServerFnError<String>> {
83 let login = LoginState::get_server();
84 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(), });
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 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)]
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 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}