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)]
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 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}