1use std::path::{Path, PathBuf};
8use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
9use std::sync::{Arc, Mutex, mpsc};
10
11use serde_json::{Value, json};
12use speccoder_core::actions::{Runnable, Via};
13use speccoder_core::{CommandResult, CommandStatus};
14use speccoder_runtime::service::approvals::{Answer, Approver, Presentation, Who};
15use speccoder_runtime::service::progress::{Event, Progress};
16use tokio::sync::broadcast;
17
18#[derive(Debug)]
20pub enum Reply {
21 Answer(Answer),
23 Text(Option<String>),
25}
26
27#[derive(Debug)]
28struct Waiting {
29 id: u64,
30 reply: mpsc::Sender<Reply>,
31 event: Value,
32}
33
34#[derive(Debug, Clone, serde::Serialize)]
36pub struct JobView {
37 pub id: u64,
39 pub command: String,
41 pub running: bool,
43 pub result: Option<Value>,
45}
46
47#[derive(Debug)]
49pub struct Hub {
50 root: crate::Root,
51 pub issuer: Arc<crate::fixes::Issuer>,
53 events: broadcast::Sender<String>,
54 next: AtomicU64,
55 job: Mutex<Option<JobView>>,
56 waiting: Mutex<Option<Waiting>>,
57 cancel: Mutex<Option<Arc<AtomicBool>>>,
58}
59
60impl Hub {
61 #[must_use]
64 pub fn new(root: crate::Root, issuer: Arc<crate::fixes::Issuer>) -> Arc<Self> {
65 let (events, _) = broadcast::channel(1024);
66 Arc::new(Self {
67 root,
68 issuer,
69 events,
70 next: AtomicU64::new(1),
71 job: Mutex::new(None),
72 waiting: Mutex::new(None),
73 cancel: Mutex::new(None),
74 })
75 }
76
77 #[must_use]
79 pub fn subscribe(&self) -> broadcast::Receiver<String> {
80 self.events.subscribe()
81 }
82
83 fn send(&self, v: &Value) {
84 let _ = self.events.send(v.to_string());
85 }
86
87 #[must_use]
90 pub fn progress(self: &Arc<Self>) -> Arc<dyn Progress> {
91 Arc::new(Stream(Arc::clone(self)))
92 }
93
94 #[must_use]
96 pub fn job(&self) -> Option<JobView> {
97 self.job.lock().ok().and_then(|j| j.clone())
98 }
99
100 #[must_use]
102 pub fn waiting(&self) -> Option<Value> {
103 self.waiting
104 .lock()
105 .ok()
106 .and_then(|w| w.as_ref().map(|w| w.event.clone()))
107 }
108
109 pub fn answer(&self, id: u64, reply: Reply) -> Result<(), String> {
111 let w = self
112 .waiting
113 .lock()
114 .map_err(|_| "the host is broken".to_owned())?
115 .take();
116 match w {
117 Some(w) if w.id == id => {
118 let _ = w.reply.send(reply);
119 self.send(&json!({ "type": "answered", "id": id }));
120 Ok(())
121 }
122 Some(w) => {
123 let other = w.id;
124 if let Ok(mut slot) = self.waiting.lock() {
125 *slot = Some(w);
126 }
127 Err(format!("{other} waits, not {id}"))
128 }
129 None => Err("nothing waits for an answer".to_owned()),
130 }
131 }
132
133 pub fn stop(&self) {
136 if let Some(c) = self.cancel.lock().ok().and_then(|c| c.clone()) {
137 c.store(true, Ordering::SeqCst);
138 }
139 let who = Who::local_in(&self.root.get(), Via::Web);
140 if let Some(w) = self.waiting.lock().ok().and_then(|mut w| w.take()) {
141 let _ = w.reply.send(match w.event["type"].as_str() {
142 Some("question") => Reply::Text(None),
143 _ => Reply::Answer(Answer::stop(Some(who))),
144 });
145 }
146 self.send(&json!({ "type": "stopping" }));
147 }
148
149 fn wait_for(&self, mut event: Value) -> Option<Reply> {
150 let id = self.next.fetch_add(1, Ordering::SeqCst);
151 event["id"] = json!(id);
152 let (tx, rx) = mpsc::channel();
153 if let Ok(mut w) = self.waiting.lock() {
154 *w = Some(Waiting {
155 id,
156 reply: tx,
157 event: event.clone(),
158 });
159 }
160 self.send(&event);
161 rx.recv().ok()
162 }
163
164 pub fn exclusive<T>(&self, f: impl FnOnce() -> T) -> Result<T, String> {
171 let job = self
172 .job
173 .lock()
174 .map_err(|_| "the host is broken".to_owned())?;
175 if job.as_ref().is_some_and(|j| j.running) {
176 return Err("a job is working; do this when it ends".to_owned());
177 }
178 let out = f();
179 drop(job);
180 Ok(out)
181 }
182
183 fn begin(&self, command: &str) -> Result<(u64, Arc<AtomicBool>), String> {
186 let id = self.next.fetch_add(1, Ordering::SeqCst);
187 {
188 let mut job = self
189 .job
190 .lock()
191 .map_err(|_| "the host is broken".to_owned())?;
192 if job.as_ref().is_some_and(|j| j.running) {
193 return Err("a job is working; stop it or wait for it".to_owned());
194 }
195 *job = Some(JobView {
196 id,
197 command: command.to_owned(),
198 running: true,
199 result: None,
200 });
201 }
202 let cancel = Arc::new(AtomicBool::new(false));
203 if let Ok(mut c) = self.cancel.lock() {
204 *c = Some(Arc::clone(&cancel));
205 }
206 self.send(&json!({ "type": "started", "id": id, "command": command }));
207 Ok((id, cancel))
208 }
209
210 fn finish(&self, id: u64, root: &Path, result: &CommandResult) {
212 let value = self.issuer.signing(root.to_path_buf(), || {
213 serde_json::to_value(result).unwrap_or(Value::Null)
214 });
215 self.finish_value(id, value);
216 }
217
218 fn finish_value(&self, id: u64, value: Value) {
220 if let Ok(mut job) = self.job.lock()
221 && let Some(j) = job.as_mut()
222 {
223 j.running = false;
224 j.result = Some(value.clone());
225 }
226 if let Ok(mut c) = self.cancel.lock() {
227 *c = None;
228 }
229 self.send(&json!({ "type": "done", "id": id, "result": value }));
230 }
231
232 pub fn start(
236 self: &Arc<Self>,
237 run: Runnable,
238 input: Option<String>,
239 feature: Option<String>,
240 command: String,
241 root: PathBuf,
242 ) -> Result<u64, String> {
243 let (id, cancel) = self.begin(&command)?;
244 let hub = Arc::clone(self);
245 std::thread::spawn(move || {
246 let peer: Arc<Peer> = Arc::new(Peer {
247 hub: Arc::clone(&hub),
248 who: Who::local_in(&root, Via::Web),
249 });
250 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
253 hub.issuer.signing(hub.root.get(), || {
254 execute(
255 &root,
256 feature.as_deref(),
257 &run,
258 input.as_deref(),
259 &peer,
260 cancel,
261 )
262 })
263 }))
264 .unwrap_or_else(|_| {
265 CommandResult::new("job", CommandStatus::Error, "the job stopped unexpectedly")
266 });
267 let result = speccoder_runtime::service::next::attach(result, feature.as_deref());
268 if let Some(bound) = crate::welcome::bind_after(&hub.root, &run, &result) {
271 hub.send(&json!({ "type": "bound", "root": bound.display().to_string() }));
272 }
273 hub.finish(id, &hub.root.get(), &result);
274 });
275 Ok(id)
276 }
277
278 pub fn start_fix(
282 self: &Arc<Self>,
283 argvs: Vec<Vec<String>>,
284 root: PathBuf,
285 ) -> Result<u64, String> {
286 use speccoder_runtime::service::person;
287 let (id, cancel) = self.begin(&person::shown(&argvs))?;
288 let hub = Arc::clone(self);
289 std::thread::spawn(move || {
290 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
291 person::run(&root, &argvs, person::TIMEOUT, &cancel, &|stream, line| {
292 hub.send(&json!({ "type": "output", "id": id, "stream": stream.as_str(), "line": line }));
293 })
294 }))
295 .unwrap_or_else(|_| {
296 CommandResult::new("fix", CommandStatus::Error, "the command stopped unexpectedly")
297 });
298 hub.finish(id, &root, &result);
299 });
300 Ok(id)
301 }
302
303 pub fn start_speccoder(
309 self: &Arc<Self>,
310 args: Vec<String>,
311 root: PathBuf,
312 ) -> Result<u64, String> {
313 use speccoder_runtime::service::person;
314 let mut shown = vec!["speccoder".to_owned()];
315 shown.extend(args.iter().cloned());
316 let (id, cancel) = self.begin(&person::shown(&[shown]))?;
317 let hub = Arc::clone(self);
318 std::thread::spawn(move || {
319 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
320 person::speccoder(&root, &args, &cancel, &|stream, line| {
321 hub.send(&json!({ "type": "output", "id": id, "stream": stream.as_str(), "line": line }));
322 })
323 }))
324 .unwrap_or_else(|_| Err("it stopped unexpectedly".to_owned()));
325 match result {
326 Ok(v) => hub.finish_value(id, v),
328 Err(e) => hub.finish(
329 id,
330 &root,
331 &CommandResult::new(
332 "job",
333 CommandStatus::Error,
334 &format!("the command ended without a result: {e}"),
335 ),
336 ),
337 }
338 });
339 Ok(id)
340 }
341}
342
343#[derive(Debug)]
345struct Stream(Arc<Hub>);
346
347impl Progress for Stream {
348 fn event(&self, event: &Event) {
349 self.0.send(&json!({ "type": "progress", "event": event }));
350 }
351}
352
353#[derive(Debug)]
355struct Peer {
356 hub: Arc<Hub>,
357 who: Who,
358}
359
360impl Progress for Peer {
361 fn event(&self, event: &Event) {
362 self.hub
363 .send(&json!({ "type": "progress", "event": event }));
364 }
365}
366
367impl Approver for Peer {
368 fn ask(&self, p: &Presentation) -> Answer {
369 match self
370 .hub
371 .wait_for(json!({ "type": "ask", "presentation": p }))
372 {
373 Some(Reply::Answer(mut a)) => {
374 a.by = Some(self.who.clone());
375 a
376 }
377 _ => Answer::stop(Some(self.who.clone())),
378 }
379 }
380
381 fn answer(&self, question: &str) -> Option<String> {
382 match self
383 .hub
384 .wait_for(json!({ "type": "question", "question": question }))
385 {
386 Some(Reply::Text(t)) => t,
387 _ => None,
388 }
389 }
390
391 fn via(&self) -> Via {
392 Via::Web
393 }
394}
395
396fn execute(
397 root: &Path,
398 feature: Option<&str>,
399 run: &Runnable,
400 input: Option<&str>,
401 peer: &Arc<Peer>,
402 cancel: Arc<AtomicBool>,
403) -> CommandResult {
404 use speccoder_runtime::service::actions::{Dispatch, Interface, Offer, offered, perform};
405 if offered(Interface::WebJobs, run.kind()) == Offer::Refuse {
406 return Interface::WebJobs.refusal();
407 }
408 let options = speccoder_runtime::service::RunOptions {
409 approver: Some(Arc::clone(peer) as Arc<dyn Approver>),
410 progress: Some(Arc::clone(peer) as Arc<dyn Progress>),
411 cancel: Some(cancel),
412 ..speccoder_runtime::service::RunOptions::default()
413 };
414 perform(
415 root,
416 feature,
417 Dispatch {
418 run,
419 input,
420 via: Via::Web,
421 options,
422 },
423 )
424}