1use flams_ontology::uris::ArchiveId;
2use leptos::prelude::*;
3use std::num::NonZeroU32;
4
5use crate::GitState;
6
7#[server(prefix = "/api/gitlab", endpoint = "get_archives")]
8pub async fn get_archives()
9-> Result<Vec<(flams_git::Project, ArchiveId, GitState)>, ServerFnError<String>> {
10 server::get_archives().await
11}
12
13#[server(prefix = "/api/gitlab", endpoint = "get_branches")]
14pub async fn get_branches(id: u64) -> Result<Vec<flams_git::Branch>, ServerFnError<String>> {
15 let (oauth, secret) = flams_router_base::get_oauth()?;
16 oauth
17 .get_branches(id, secret)
18 .await
19 .map_err(|e| ServerFnError::WrappedServerError(e.to_string()))
20}
21
22#[server(prefix = "/api/gitlab", endpoint = "update_from_branch")]
23pub async fn update_from_branch(
24 id: Option<NonZeroU32>,
25 archive: ArchiveId,
26 url: String,
27 branch: String,
28) -> Result<(usize, NonZeroU32), ServerFnError<String>> {
29 server::update_from_branch(id, archive, url, branch).await
30}
31
32#[server(prefix = "/api/gitlab", endpoint = "clone_to_queue")]
33pub async fn clone_to_queue(
34 id: Option<NonZeroU32>,
35 archive: ArchiveId,
36 url: String,
37 branch: String,
38 has_release: bool,
39) -> Result<(usize, NonZeroU32), ServerFnError<String>> {
40 server::clone_to_queue(id, archive, url, branch, has_release).await
41}
42
43#[server(prefix = "/api/gitlab", endpoint = "get_new_commits")]
44pub async fn get_new_commits(
45 queue: Option<NonZeroU32>,
46 id: ArchiveId,
47) -> Result<Vec<(String, flams_git::Commit)>, ServerFnError<String>> {
48 server::get_new_commits(queue, id).await
49}
50
51#[cfg(feature = "ssr")]
52mod server {
53 use flams_ontology::uris::ArchiveId;
54 use flams_router_base::{LoginState, get_oauth};
55 use flams_router_buildqueue_base::LoginQueue;
56 use flams_utils::{impossible, unwrap};
57 use flams_web_utils::blocking_server_fn;
58 use leptos::prelude::*;
59 use std::num::NonZeroU32;
60
61 use crate::GitState;
62
63 #[allow(clippy::too_many_lines)]
64 pub(super) async fn get_archives()
65 -> Result<Vec<(flams_git::Project, ArchiveId, GitState)>, ServerFnError<String>> {
66 use flams_git::gl::auth::GitLabOAuth;
67 use flams_system::backend::{
68 AnyBackend, GlobalBackend, SandboxedRepository, archives::Archive,
69 };
70 use leptos::either::Either::{Left, Right};
71 async fn get(
72 oauth: GitLabOAuth,
73 secret: String,
74 p: flams_git::Project,
75 ) -> (
76 flams_git::Project,
77 Result<Option<ArchiveId>, flams_git::gl::Err>,
78 ) {
79 let id = if let Some(b) = &p.default_branch {
80 oauth.get_archive_id(p.id, secret, b).await
81 } else {
82 return (p, Ok(None));
83 };
84 (p, id)
85 }
86 let (oauth, secret) = get_oauth()?;
87 let r = oauth
88 .get_projects(secret.clone())
89 .await
90 .map_err(|e| ServerFnError::WrappedServerError(e.to_string()))?;
91 let mut r2 = Vec::new();
92 let mut js = tokio::task::JoinSet::new();
93 for p in r {
94 js.spawn(get(oauth.clone(), secret.clone(), p));
95 }
96 while let Some(r) = js.join_next().await {
97 match r {
98 Err(e) => return Err(e.to_string().into()),
99 Ok((p, Err(e))) => {
100 tracing::error!("error obtaining archive ID of {} ({}): {e}", p.path, p.id);
101 }
102 Ok((p, Ok(Some(id)))) => r2.push((p, id)),
103 Ok((_, Ok(None))) => (),
104 }
105 }
106 let r = blocking_server_fn(move || {
107 use flams_system::building::queue_manager::QueueManager;
108 let mut ret = Vec::new();
109 let backend = GlobalBackend::get();
110 let gitlab_url = unwrap!(flams_system::settings::Settings::get().gitlab_url.as_ref());
111 for a in backend.all_archives().iter() {
112 if let Archive::Local(a) = a {
113 if let Some((p, id)) = r2
114 .iter()
115 .position(|(_, id)| id == a.id())
116 .map(|i| r2.swap_remove(i))
117 {
118 if let Ok(git) = flams_git::repos::GitRepo::open(a.path()) {
119 if gitlab_url
120 .host_str()
121 .is_some_and(|s| git.is_managed(s).is_some())
122 {
123 ret.push((p, id, Left(git)));
124 } else {
125 ret.push((p, id, Right(GitState::None)));
126 }
127 } else {
128 ret.push((p, id, Right(GitState::None)));
129 }
130 }
131 }
132 }
133 ret.extend(r2.into_iter().map(|(p, id)| (p, id, Right(GitState::None))));
134 QueueManager::get().with_all_queues(|qs| {
136 for (qid, q) in qs {
137 if let AnyBackend::Sandbox(sb) = q.backend() {
138 sb.with_repos(|rs| {
139 for r in rs {
140 match r {
141 SandboxedRepository::Git {
142 id: rid, commit, ..
143 } => {
144 if let Some(e) = ret.iter_mut().find_map(|(_, id, e)| {
145 if id == rid { Some(e) } else { None }
146 }) {
147 *e = Right(GitState::Queued {
148 commit: commit.id.clone(),
149 queue: (*qid).into(),
150 });
151 }
152 }
154 SandboxedRepository::Copy(_) => (),
155 }
156 }
157 });
158 }
159 }
160 });
161
162 Ok(ret)
163 })
164 .await?;
165
166 let mut r2 = Vec::new();
167
168 let mut js = tokio::task::JoinSet::new();
169 for (p, id, e) in r {
170 match e {
171 Right(e) => r2.push((p, id, e)),
172 Left(git) => {
173 let secret = secret.clone();
174 js.spawn_blocking(move || {
175 if let Ok(rid) = git.current_commit() {
176 let newer = git
177 .get_new_commits_with_oauth(&secret)
178 .ok()
179 .unwrap_or_default();
180 (
181 p,
182 id,
183 GitState::Live {
184 commit: rid.id,
185 updates: newer,
186 },
187 )
188 } else {
189 (p, id, GitState::None)
190 }
191 });
192 }
193 }
194 }
195
196 while let Some(r) = js.join_next().await {
197 match r {
198 Err(e) => return Err(e.to_string().into()),
199 Ok((p, id, s)) => r2.push((p, id, s)),
200 }
201 }
202
203 Ok(r2)
204 }
205
206 pub(super) async fn update_from_branch(
207 id: Option<NonZeroU32>,
208 archive: ArchiveId,
209 url: String,
210 branch: String,
211 ) -> Result<(usize, NonZeroU32), ServerFnError<String>> {
212 use flams_system::backend::{AnyBackend, Backend, SandboxedRepository, archives::Archive};
213 use flams_system::formats::FormatOrTargets;
214 let (_, secret) = get_oauth()?;
215 let login = LoginState::get_server();
216 if matches!(login, LoginState::NoAccounts) {
217 return Err("Only allowed in public mode".to_string().into());
218 }
219 blocking_server_fn(move || {
220 login.with_opt_queue(id, |queue_id, queue| {
221 let AnyBackend::Sandbox(backend) = queue.backend() else {
222 unreachable!()
223 };
224 backend.require(&archive);
225 let path = backend.path_for(&archive);
226 if !path.exists() {
227 return Err(format!("Archive {archive} not found!"));
228 }
229 let repo = flams_git::repos::GitRepo::open(&path).map_err(|e| e.to_string())?;
230 repo.fetch_branch_from_oauth(&secret, &branch, false)
231 .map_err(|e| e.to_string())?;
232 let commit = repo
233 .current_remote_commit_on(&branch)
234 .map_err(|e| e.to_string())?;
235 repo.force_checkout(&commit.id).map_err(|e| e.to_string())?;
236 backend.add(
238 SandboxedRepository::Git {
239 id: archive.clone(),
240 commit,
241 branch: branch.into(),
242 remote: url.into(),
243 },
244 || (),
245 );
246 let formats = backend.with_archive(&archive, |a| {
247 let Some(Archive::Local(a)) = a else {
248 return Err("Archive not found".to_string());
249 };
250 Ok(a.file_state()
251 .formats
252 .iter()
253 .map(|(k, _)| *k)
254 .collect::<Vec<_>>())
255 })?;
256 let mut u = 0;
257 for f in formats {
258 u += queue.enqueue_archive(
259 &archive,
260 FormatOrTargets::Format(f),
261 true,
262 None,
263 false,
264 );
265 }
266 Ok((u, queue_id.into()))
267 })?
268 })
269 .await
270 }
271
272 pub async fn clone_to_queue(
273 id: Option<NonZeroU32>,
274 archive: ArchiveId,
275 url: String,
276 branch: String,
277 _has_release: bool,
278 ) -> Result<(usize, NonZeroU32), ServerFnError<String>> {
279 use flams_system::backend::{AnyBackend, Backend, SandboxedRepository, archives::Archive};
280 use flams_system::formats::FormatOrTargets;
281 let (_, secret) = get_oauth()?;
282 let login = LoginState::get_server();
283 if matches!(login, LoginState::NoAccounts) {
284 return Err("Only allowed in public mode".to_string().into());
285 }
286
287 tokio::task::spawn_blocking(move || {
288 login.with_opt_queue(id, |queue_id, queue| {
289 let AnyBackend::Sandbox(backend) = queue.backend() else {
290 unreachable!()
291 };
292 let path = backend.path_for(&archive);
293 if path.exists() {
294 let _ = std::fs::remove_dir_all(&path);
295 }
296 let commit = {
297 let repo = flams_git::repos::GitRepo::clone_from_oauth(
298 &secret, &url, &branch, &path, false,
299 )
300 .map_err(|e| e.to_string())?;
301 repo.current_commit().map_err(|e| e.to_string())?
302 };
306 backend.add(
307 SandboxedRepository::Git {
308 id: archive.clone(),
309 commit,
310 branch: branch.into(),
311 remote: url.into(),
312 },
313 || (),
314 );
315 let formats = backend.with_archive(&archive, |a| {
316 let Some(Archive::Local(a)) = a else {
317 return Err("Archive not found".to_string());
318 };
319 Ok(a.file_state()
320 .formats
321 .iter()
322 .map(|(k, _)| *k)
323 .collect::<Vec<_>>())
324 })?;
325 let mut u = 0;
326 for f in formats {
327 u += queue.enqueue_archive(
328 &archive,
329 FormatOrTargets::Format(f),
330 false,
331 None,
332 false,
333 );
334 }
335 Ok((u, queue_id.into()))
336 })
337 })
338 .await
339 .unwrap_or_else(|e| Err(e.to_string()))? }
341
342 pub(super) async fn get_new_commits(
343 queue: Option<NonZeroU32>,
344 id: ArchiveId,
345 ) -> Result<Vec<(String, flams_git::Commit)>, ServerFnError<String>> {
346 use flams_system::backend::AnyBackend;
347
348 let (_, secret) = get_oauth()?;
349 let login = LoginState::get_server();
350 if matches!(login, LoginState::NoAccounts) {
351 return Err("Only allowed in public mode".to_string().into());
352 }
353 blocking_server_fn(move || {
354 login.with_opt_queue(queue, |_, queue| {
355 let AnyBackend::Sandbox(backend) = queue.backend() else {
356 impossible!()
357 };
358 let path = backend.path_for(&id);
359 let r = flams_git::repos::GitRepo::open(path)
360 .ok()
361 .and_then(|git| {
362 let gitlab_url =
363 unwrap!(flams_system::settings::Settings::get().gitlab_url.as_ref());
364 if gitlab_url
365 .host_str()
366 .is_some_and(|s| git.is_managed(s).is_some())
367 {
368 git.get_new_commits_with_oauth(&secret).ok()
369 } else {
370 None
371 }
372 })
373 .unwrap_or_default();
374 Ok(r)
375 })?
376 })
377 .await
378 }
379}