1use crate::model::app::AppContext;
2use crate::{FieldUpdate, MuxBox, Updatable};
4use bincode;
5use log::error;
6use std::collections::hash_map::DefaultHasher;
7use std::collections::HashMap;
8use std::hash::{Hash, Hasher};
9use std::sync::mpsc::{self, Sender};
10use std::thread;
11use uuid::Uuid;
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum Message {
15 Exit,
16 Terminate,
17 Pause,
18 Start,
19 NextMuxBox(),
20 PreviousMuxBox(),
21 ScrollMuxBoxDown(),
22 ScrollMuxBoxUp(),
23 ScrollMuxBoxLeft(),
24 ScrollMuxBoxRight(),
25 ScrollMuxBoxPageUp(),
26 ScrollMuxBoxPageDown(),
27 ScrollMuxBoxPageLeft(),
28 ScrollMuxBoxPageRight(),
29 ScrollMuxBoxToBeginning(), ScrollMuxBoxToEnd(), ScrollMuxBoxToTop(), ScrollMuxBoxToBottom(), CopyFocusedMuxBoxContent(),
34 Resize,
35 RedrawMuxBox(String),
36 RedrawApp,
37 RedrawAppDiff, MuxBoxEventRefresh(String),
39 MuxBoxScriptUpdate(String, Vec<String>),
40 ReplaceMuxBox(String, MuxBox),
41 StopBoxRefresh(String),
42 StartBoxRefresh(String),
43 SwitchActiveLayout(String),
44 KeyPress(String),
45 ExecuteHotKeyChoice(String),
46 MouseClick(u16, u16), MouseMove(u16, u16), MouseDragStart(u16, u16), MouseDrag(u16, u16), MouseDragEnd(u16, u16), MouseScrollUp(u16, u16),
54 MouseScrollDown(u16, u16),
55 MouseScrollLeft(u16, u16),
56 MouseScrollRight(u16, u16),
57 MuxBoxBorderDrag(String, u16, u16), MuxBoxResizeComplete(String), MuxBoxMove(String, u16, u16), MuxBoxMoveComplete(String), SaveYamlState, SaveActiveLayout(String), SaveMuxBoxContent(String, String), SaveMuxBoxScroll(String, usize, usize), PTYInput(String, String), PTYInputWithModes(String, String, bool, bool), PTYMouseEvent(
68 String,
69 crossterm::event::MouseEventKind,
70 u16,
71 u16,
72 crossterm::event::KeyModifiers,
73 ), ExternalMessage(String),
75 AddBox(String, MuxBox),
76 RemoveBox(String),
77 SwitchTab(String, usize), ScrollTabsLeft(String), ScrollTabsRight(String), SwitchToStream(String, String), AddStream(String, crate::model::common::StreamSource), RemoveStream(String, String), CloseTab(String, String), UpdateStreamContent(String, String, String), ExecuteScriptMessage(crate::model::common::ExecuteScript), StreamUpdateMessage(crate::model::common::StreamUpdate), SourceActionMessage(crate::model::common::SourceAction), }
91
92impl Hash for Message {
93 fn hash<H: Hasher>(&self, state: &mut H) {
94 match self {
95 Message::Exit => "exit".hash(state),
96 Message::Terminate => "terminate".hash(state),
97 Message::NextMuxBox() => "next_muxbox".hash(state),
98 Message::PreviousMuxBox() => "previous_muxbox".hash(state),
99 Message::Resize => "resize".hash(state),
100 Message::RedrawMuxBox(muxbox_id) => {
101 "redraw_muxbox".hash(state);
102 muxbox_id.hash(state);
103 }
104 Message::RedrawApp => "redraw_app".hash(state),
105 Message::RedrawAppDiff => "redraw_app_diff".hash(state),
106 Message::SwitchActiveLayout(layout_id) => {
107 "switch_active_layout".hash(state);
108 layout_id.hash(state);
109 }
110 Message::MuxBoxEventRefresh(muxbox_id) => {
111 "muxbox_event_refresh".hash(state);
112 muxbox_id.hash(state);
113 }
114 Message::ScrollMuxBoxDown() => "scroll_muxbox_down".hash(state),
115 Message::ScrollMuxBoxUp() => "scroll_muxbox_up".hash(state),
116 Message::ScrollMuxBoxLeft() => "scroll_muxbox_left".hash(state),
117 Message::ScrollMuxBoxRight() => "scroll_muxbox_right".hash(state),
118 Message::ScrollMuxBoxPageUp() => "scroll_muxbox_page_up".hash(state),
119 Message::ScrollMuxBoxPageDown() => "scroll_muxbox_page_down".hash(state),
120 Message::ScrollMuxBoxPageLeft() => "scroll_muxbox_page_left".hash(state),
121 Message::ScrollMuxBoxPageRight() => "scroll_muxbox_page_right".hash(state),
122 Message::ScrollMuxBoxToBeginning() => "scroll_muxbox_to_beginning".hash(state),
123 Message::ScrollMuxBoxToEnd() => "scroll_muxbox_to_end".hash(state),
124 Message::ScrollMuxBoxToTop() => "scroll_muxbox_to_top".hash(state),
125 Message::ScrollMuxBoxToBottom() => "scroll_muxbox_to_bottom".hash(state),
126 Message::CopyFocusedMuxBoxContent() => "copy_focused_muxbox_content".hash(state),
127 Message::MuxBoxScriptUpdate(muxbox_id, script) => {
128 "muxbox_script_update".hash(state);
129 muxbox_id.hash(state);
130 script.hash(state);
131 }
132 Message::ReplaceMuxBox(muxbox_id, muxbox) => {
133 "replace_muxbox".hash(state);
134 muxbox_id.hash(state);
135 muxbox.hash(state);
136 }
137 Message::KeyPress(pressed_key) => {
138 "key_press".hash(state);
139 pressed_key.hash(state);
140 }
141 Message::ExecuteHotKeyChoice(choice_id) => {
142 "execute_hot_key_choice".hash(state);
143 choice_id.hash(state);
144 }
145 Message::MouseClick(x, y) => {
146 "mouse_click".hash(state);
147 x.hash(state);
148 y.hash(state);
149 }
150 Message::MouseMove(x, y) => {
151 "mouse_move".hash(state);
152 x.hash(state);
153 y.hash(state);
154 }
155 Message::MouseDragStart(x, y) => {
156 "mouse_drag_start".hash(state);
157 x.hash(state);
158 y.hash(state);
159 }
160 Message::MouseDrag(x, y) => {
161 "mouse_drag".hash(state);
162 x.hash(state);
163 y.hash(state);
164 }
165 Message::MouseScrollUp(x, y) => {
166 "mouse_scroll_up".hash(state);
167 x.hash(state);
168 y.hash(state);
169 }
170 Message::MouseScrollDown(x, y) => {
171 "mouse_scroll_down".hash(state);
172 x.hash(state);
173 y.hash(state);
174 }
175 Message::MouseScrollLeft(x, y) => {
176 "mouse_scroll_left".hash(state);
177 x.hash(state);
178 y.hash(state);
179 }
180 Message::MouseScrollRight(x, y) => {
181 "mouse_scroll_right".hash(state);
182 x.hash(state);
183 y.hash(state);
184 }
185 Message::MouseDragEnd(x, y) => {
186 "mouse_drag_end".hash(state);
187 x.hash(state);
188 y.hash(state);
189 }
190 Message::MuxBoxBorderDrag(muxbox_id, x, y) => {
191 "muxbox_border_drag".hash(state);
192 muxbox_id.hash(state);
193 x.hash(state);
194 y.hash(state);
195 }
196 Message::MuxBoxResizeComplete(muxbox_id) => {
197 "muxbox_resize_complete".hash(state);
198 muxbox_id.hash(state);
199 }
200 Message::MuxBoxMove(muxbox_id, x, y) => {
201 "muxbox_move".hash(state);
202 muxbox_id.hash(state);
203 x.hash(state);
204 y.hash(state);
205 }
206 Message::MuxBoxMoveComplete(muxbox_id) => {
207 "muxbox_move_complete".hash(state);
208 muxbox_id.hash(state);
209 }
210 Message::SaveYamlState => "save_yaml_state".hash(state),
211 Message::SaveActiveLayout(layout_id) => {
212 "save_active_layout".hash(state);
213 layout_id.hash(state);
214 }
215 Message::SaveMuxBoxContent(muxbox_id, content) => {
216 "save_muxbox_content".hash(state);
217 muxbox_id.hash(state);
218 content.hash(state);
219 }
220 Message::SaveMuxBoxScroll(muxbox_id, x, y) => {
221 "save_muxbox_scroll".hash(state);
222 muxbox_id.hash(state);
223 x.hash(state);
224 y.hash(state);
225 }
226 Message::PTYInput(muxbox_id, input) => {
227 "pty_input".hash(state);
228 muxbox_id.hash(state);
229 input.hash(state);
230 }
231 Message::PTYInputWithModes(muxbox_id, input, cursor_key_mode, keypad_mode) => {
232 "pty_input_with_modes".hash(state);
233 muxbox_id.hash(state);
234 input.hash(state);
235 cursor_key_mode.hash(state);
236 keypad_mode.hash(state);
237 }
238 Message::PTYMouseEvent(muxbox_id, kind, column, row, modifiers) => {
239 "pty_mouse_event".hash(state);
240 muxbox_id.hash(state);
241 std::mem::discriminant(kind).hash(state);
243 column.hash(state);
244 row.hash(state);
245 modifiers.bits().hash(state);
247 }
248 Message::Pause => "pause".hash(state),
249 Message::ExternalMessage(msg) => {
250 "external_message".hash(state);
251 msg.hash(state);
252 }
253 Message::Start => "start".hash(state),
254 Message::StopBoxRefresh(box_id) => {
255 "stop_box_refresh".hash(state);
256 box_id.hash(state);
257 }
258 Message::StartBoxRefresh(box_id) => {
259 "start_box_refresh".hash(state);
260 box_id.hash(state);
261 }
262 Message::AddBox(box_id, muxbox) => {
263 "add_box".hash(state);
264 box_id.hash(state);
265 muxbox.hash(state);
266 }
267 Message::RemoveBox(box_id) => {
268 "remove_box".hash(state);
269 box_id.hash(state);
270 }
271 Message::SwitchTab(muxbox_id, tab_index) => {
273 "switch_tab".hash(state);
274 muxbox_id.hash(state);
275 tab_index.hash(state);
276 }
277 Message::ScrollTabsLeft(muxbox_id) => {
278 "scroll_tabs_left".hash(state);
279 muxbox_id.hash(state);
280 }
281 Message::ScrollTabsRight(muxbox_id) => {
282 "scroll_tabs_right".hash(state);
283 muxbox_id.hash(state);
284 }
285 Message::SwitchToStream(muxbox_id, stream_id) => {
286 "switch_to_stream".hash(state);
287 muxbox_id.hash(state);
288 stream_id.hash(state);
289 }
290 Message::AddStream(muxbox_id, stream) => {
291 "add_stream".hash(state);
292 muxbox_id.hash(state);
293 stream.hash(state);
294 }
295 Message::RemoveStream(muxbox_id, stream_id) => {
296 "remove_stream".hash(state);
297 muxbox_id.hash(state);
298 stream_id.hash(state);
299 }
300 Message::CloseTab(muxbox_id, stream_id) => {
301 "close_tab".hash(state);
302 muxbox_id.hash(state);
303 stream_id.hash(state);
304 }
305 Message::UpdateStreamContent(muxbox_id, stream_id, content) => {
306 "update_stream_content".hash(state);
307 muxbox_id.hash(state);
308 stream_id.hash(state);
309 content.hash(state);
310 }
311 Message::ExecuteScriptMessage(execute_script) => {
313 "execute_script_message".hash(state);
314 execute_script.hash(state);
315 }
316 Message::StreamUpdateMessage(stream_update) => {
317 "stream_update_message".hash(state);
318 stream_update.hash(state);
319 }
320 Message::SourceActionMessage(source_action) => {
321 "source_action_message".hash(state);
322 source_action.hash(state);
323 }
324 }
325 }
326}
327
328pub trait Runnable: Send + 'static {
329 fn run(&mut self) -> Result<bool, Box<dyn std::error::Error>>;
330 fn receive_updates(&mut self) -> (AppContext, Vec<Message>);
331 fn process(&mut self, app_context: AppContext, messages: Vec<Message>);
332
333 fn update_app_context(&mut self, app_context: AppContext);
334 fn set_uuid(&mut self, uuid: Uuid);
335 fn get_uuid(&self) -> Uuid;
336 fn set_app_context_sender(
337 &mut self,
338 app_context_sender: mpsc::Sender<(Uuid, Vec<FieldUpdate>)>,
339 );
340 fn set_message_sender(&mut self, message_sender: mpsc::Sender<(Uuid, Message)>);
341 fn set_app_context_receiver(
342 &mut self,
343 app_context_receiver: mpsc::Receiver<(Uuid, Vec<FieldUpdate>)>,
344 );
345 fn set_message_receiver(&mut self, message_receiver: mpsc::Receiver<(Uuid, Message)>);
346 fn get_app_context(&self) -> &AppContext;
347 fn get_app_context_sender(&self) -> &Option<mpsc::Sender<(Uuid, Vec<FieldUpdate>)>>;
348 fn get_message_sender(&self) -> &Option<mpsc::Sender<(Uuid, Message)>>;
349
350 fn send_app_context_update(&self, old_app_context: AppContext);
351 fn send_message(&self, msg: Message);
352}
353
354#[derive(Debug, Clone, PartialEq, Eq)]
355pub enum RunnableState {
356 Created,
357 Running,
358 Paused,
359 Terminated,
360}
361
362pub struct RunnableImpl {
363 pub app_context: AppContext,
364 uuid: Uuid,
365 running_state: RunnableState,
366 app_context_sender: Option<mpsc::Sender<(Uuid, Vec<FieldUpdate>)>>,
367 message_sender: Option<mpsc::Sender<(Uuid, Message)>>,
368 app_context_receiver: Option<mpsc::Receiver<(Uuid, Vec<FieldUpdate>)>>,
369 message_receiver: Option<mpsc::Receiver<(Uuid, Message)>>,
370}
371
372impl RunnableImpl {
373 pub fn new(app_context: AppContext) -> Self {
374 RunnableImpl {
375 app_context,
376 uuid: Uuid::new_v4(),
377 running_state: RunnableState::Created,
378 app_context_sender: None,
379 message_sender: None,
380 app_context_receiver: None,
381 message_receiver: None,
382 }
383 }
384
385 pub fn get_message_sender(&self) -> Option<&mpsc::Sender<(Uuid, Message)>> {
386 self.message_sender.as_ref()
387 }
388
389 pub fn get_message_sender_option_ref(&self) -> &Option<mpsc::Sender<(Uuid, Message)>> {
390 &self.message_sender
391 }
392
393 pub fn _run(
394 &mut self,
395 process_fn: &mut dyn FnMut(&mut Self, AppContext, Vec<Message>) -> (bool, AppContext),
396 ) -> Result<bool, Box<dyn std::error::Error>> {
397 let (updated_app_context, new_messages) = self.receive_updates();
398 let original_app_context = updated_app_context.clone();
399 let mut should_continue = true;
400 for message in new_messages.iter() {
401 match message {
402 Message::Exit => {
403 self.running_state = RunnableState::Terminated;
404 return Ok(false);
405 }
406 Message::Terminate => {
407 self.running_state = RunnableState::Terminated;
408 return Ok(false);
409 }
410 Message::Pause => {
411 self.running_state = RunnableState::Paused;
412 }
413 Message::Start => {
414 self.running_state = RunnableState::Running;
415 }
416 _ => {}
417 }
418 }
419
420 if updated_app_context != self.app_context {
422 self.app_context = updated_app_context;
423 }
424
425 log::trace!(
426 "RunnableImpl _run: state={:?}, messages={}",
427 self.running_state,
428 new_messages.len()
429 );
430 if self.running_state == RunnableState::Running {
431 log::trace!(
432 "RunnableImpl calling process function with {} messages",
433 new_messages.len()
434 );
435 let (process_should_continue, result_app_context) =
436 process_fn(self, self.app_context.clone(), new_messages);
437 if result_app_context != original_app_context {
438 self.app_context = result_app_context;
439 self.send_app_context_update(original_app_context);
440 }
441 should_continue = process_should_continue;
442 log::trace!(
443 "RunnableImpl process function returned should_continue={}",
444 should_continue
445 );
446 } else {
447 log::debug!(
448 "RunnableImpl NOT calling process function - state is {:?}",
449 self.running_state
450 );
451 }
452
453 if !should_continue {
454 return Ok(false);
455 }
456 Ok(true)
457 }
458}
459
460impl Runnable for RunnableImpl {
461 fn receive_updates(&mut self) -> (AppContext, Vec<Message>) {
462 let mut app_context_updates = Vec::new();
463 let mut new_messages = Vec::new();
464
465 if let Some(ref app_context_receiver) = self.app_context_receiver {
466 while let Ok((_, received_field_updates)) = app_context_receiver.try_recv() {
467 if !received_field_updates.is_empty() {
468 log::trace!(
469 "Received app_context update: {:?} in thread {}",
470 received_field_updates,
471 self.uuid
472 );
473 app_context_updates = received_field_updates;
474 }
475 }
476 }
477
478 let mut updated_app_context = self.app_context.clone();
479 updated_app_context.apply_updates(app_context_updates);
480
481 if let Some(ref message_receiver) = self.message_receiver {
482 while let Ok((_, message)) = message_receiver.try_recv() {
483 new_messages.push(message);
484 }
485 }
486
487 (updated_app_context, new_messages)
488 }
489
490 fn process(&mut self, app_context: AppContext, messages: Vec<Message>) {
491 self.update_app_context(app_context);
493
494 for message in messages {
496 match message {
497 Message::Terminate => {
498 self.running_state = RunnableState::Terminated;
499 }
500 Message::Pause => {
501 self.running_state = RunnableState::Paused;
502 }
503 Message::Start => {
504 self.running_state = RunnableState::Running;
505 }
506 Message::ExecuteScriptMessage(execute_script) => {
507 log::info!("ThreadManager processing ExecuteScript for target_box_id: {}, execution_mode: {:?}",
508 execute_script.target_box_id, execute_script.execution_mode);
509
510 self.handle_execute_script(execute_script);
511 }
512 _ => {
513 }
515 }
516 }
517 }
518
519 fn update_app_context(&mut self, app_context: AppContext) {
520 let old_app_context = self.app_context.clone();
521 self.app_context = app_context;
522 self.send_app_context_update(old_app_context);
523 }
524
525 fn set_uuid(&mut self, uuid: Uuid) {
526 self.uuid = uuid;
527 }
528
529 fn get_uuid(&self) -> Uuid {
530 self.uuid
531 }
532
533 fn set_app_context_sender(&mut self, app_context_sender: Sender<(Uuid, Vec<FieldUpdate>)>) {
534 self.app_context_sender = Some(app_context_sender);
535 }
536
537 fn set_message_sender(&mut self, message_sender: mpsc::Sender<(Uuid, Message)>) {
538 self.message_sender = Some(message_sender);
539 }
540
541 fn set_app_context_receiver(
542 &mut self,
543 app_context_receiver: mpsc::Receiver<(Uuid, Vec<FieldUpdate>)>,
544 ) {
545 self.app_context_receiver = Some(app_context_receiver);
546 }
547
548 fn set_message_receiver(&mut self, message_receiver: mpsc::Receiver<(Uuid, Message)>) {
549 self.message_receiver = Some(message_receiver);
550 }
551
552 fn get_app_context(&self) -> &AppContext {
553 &self.app_context
554 }
555
556 fn get_app_context_sender(&self) -> &Option<mpsc::Sender<(Uuid, Vec<FieldUpdate>)>> {
557 &self.app_context_sender
558 }
559
560 fn get_message_sender(&self) -> &Option<mpsc::Sender<(Uuid, Message)>> {
561 &self.message_sender
562 }
563
564 fn send_app_context_update(&self, old_app_context: AppContext) {
565 if let Some(ref app_context_sender) = self.get_app_context_sender() {
566 if let Err(e) = app_context_sender.send((
567 self.get_uuid(),
568 self.get_app_context().generate_diff(&old_app_context),
569 )) {
570 error!("Failed to send update to main thread: {}", e);
571 }
572 }
573 }
574
575 fn send_message(&self, msg: Message) {
576 if let Some(message_sender) = self.get_message_sender() {
577 if let Err(e) = message_sender.send((self.get_uuid(), msg)) {
578 error!("Failed to send message to main thread: {}", e);
579 }
580 }
581 }
582
583 fn run(&mut self) -> Result<bool, Box<dyn std::error::Error>> {
584 Ok(false)
587 }
588}
589
590impl RunnableImpl {
591 fn handle_execute_script(&mut self, execute_script: crate::model::common::ExecuteScript) {
592 use crate::model::common::ExecutionMode;
593
594 log::info!(
595 "T0315 FIXED: ThreadManager properly handling ExecuteScript for target_box: {}",
596 execute_script.target_box_id
597 );
598
599 match execute_script.execution_mode {
601 ExecutionMode::Immediate => {
602 log::info!("T0315: Immediate execution - running script synchronously");
603 self.execute_immediate_script(execute_script);
604 }
605 ExecutionMode::Thread => {
606 log::info!("T0315: Thread execution - dispatching to thread pool");
607 self.execute_threaded_script(execute_script);
608 }
609 ExecutionMode::Pty => {
610 panic!("PTY ExecuteScript reached ThreadManager - this indicates a critical routing bug that should never happen");
612 }
613 }
614 }
615
616 fn execute_immediate_script(&mut self, execute_script: crate::model::common::ExecuteScript) {
617
618 let output = crate::utils::detached_command("sh")
620 .arg("-c")
621 .arg(execute_script.script.join(" "))
622 .output();
625
626 let content = match output {
627 Ok(output) => {
628 let stdout = String::from_utf8_lossy(&output.stdout);
629 let stderr = String::from_utf8_lossy(&output.stderr);
630 if stderr.is_empty() {
631 stdout.to_string()
632 } else {
633 format!("{}\n{}", stdout, stderr)
634 }
635 }
636 Err(e) => {
637 format!("Error executing script: {}", e)
638 }
639 };
640
641 let stream_id = execute_script.stream_id.clone();
643
644 let target_box_id = if let Some(ref redirect_to) = execute_script.redirect_output {
647 log::info!(
648 "THREADMANAGER REDIRECT FIX UNKNOWN: Using redirect destination: {} (was {})",
649 redirect_to,
650 execute_script.target_box_id
651 );
652 redirect_to.clone()
653 } else {
654 log::info!(
655 "THREADMANAGER REDIRECT FIX UNKNOWN: No redirect, using source box: {}",
656 execute_script.target_box_id
657 );
658 execute_script.target_box_id.clone()
659 };
660
661 let stream_update = crate::model::common::StreamUpdate {
662 stream_id: stream_id.clone(),
663 target_box_id,
664 content_update: content,
665 source_state: crate::model::common::SourceState::Batch(
666 crate::model::common::BatchSourceState {
667 task_id: stream_id.clone(),
668 queue_wait_time: std::time::Duration::from_millis(0),
669 execution_time: std::time::Duration::from_millis(50), exit_code: Some(0),
671 status: crate::model::common::BatchStatus::Completed,
672 },
673 ),
674 execution_mode: execute_script.execution_mode,
675 };
676
677 self.send_message(Message::StreamUpdateMessage(stream_update));
678 }
679
680 fn execute_threaded_script(&mut self, execute_script: crate::model::common::ExecuteScript) {
681 use std::thread;
682
683 log::info!("T0315: Thread execution - spawning background thread for script");
684
685 let target_box_id = execute_script.target_box_id.clone();
687 let script = execute_script.script.clone();
688 let execution_mode = execute_script.execution_mode.clone();
689 let stream_id = execute_script.stream_id.clone();
690
691 let start_update = crate::model::common::StreamUpdate {
693 stream_id: stream_id.clone(),
694 target_box_id: target_box_id.clone(),
695 content_update: format!("Starting thread execution: {}\n", script.join(" ")),
696 source_state: crate::model::common::SourceState::Thread(
697 crate::model::common::ThreadSourceState {
698 thread_id: "pending".to_string(),
699 execution_time: std::time::Duration::from_millis(0),
700 exit_code: None,
701 status: crate::model::common::ExecutionThreadStatus::Running,
702 },
703 ),
704 execution_mode: execution_mode.clone(),
705 };
706
707 log::info!(
710 "ThreadManager would send StreamUpdate message: {:?}",
711 start_update
712 );
713
714 let (_thread_sender, _thread_receiver) =
716 std::sync::mpsc::channel::<crate::model::common::StreamUpdate>();
717
718 thread::spawn(move || {
723 let thread_id = format!("{:?}", thread::current().id());
724
725 let output = crate::utils::detached_command("sh")
727 .arg("-c")
728 .arg(script.join(" "))
729 .output();
730
731 let (content, exit_code, status) = match output {
732 Ok(output) => {
733 let stdout = String::from_utf8_lossy(&output.stdout);
734 let stderr = String::from_utf8_lossy(&output.stderr);
735 let content = if stderr.is_empty() {
736 stdout.to_string()
737 } else {
738 format!("{}\n{}", stdout, stderr)
739 };
740 let exit_code = output.status.code();
741 let status = if output.status.success() {
742 crate::model::common::ExecutionThreadStatus::Completed
743 } else {
744 crate::model::common::ExecutionThreadStatus::Failed(
745 "Script execution failed".to_string(),
746 )
747 };
748 (content, exit_code, status)
749 }
750 Err(e) => (
751 format!("Error executing script: {}", e),
752 Some(1),
753 crate::model::common::ExecutionThreadStatus::Failed(format!(
754 "Execution error: {}",
755 e
756 )),
757 ),
758 };
759
760 let final_update = crate::model::common::StreamUpdate {
762 stream_id,
763 target_box_id,
764 content_update: content,
765 source_state: crate::model::common::SourceState::Thread(
766 crate::model::common::ThreadSourceState {
767 thread_id,
768 execution_time: std::time::Duration::from_millis(100), exit_code,
770 status,
771 },
772 ),
773 execution_mode,
774 };
775
776 log::info!(
778 "Thread execution completed, would send StreamUpdate: {:?}",
779 final_update
780 );
781 });
782 }
783}
784
785#[derive(Debug)]
786pub struct ThreadManager {
787 threads: HashMap<Uuid, thread::JoinHandle<()>>,
788 app_context_senders: HashMap<Uuid, mpsc::Sender<(Uuid, Vec<FieldUpdate>)>>,
789 app_context_receivers: HashMap<Uuid, mpsc::Receiver<(Uuid, Vec<FieldUpdate>)>>,
790 message_senders: HashMap<Uuid, mpsc::Sender<(Uuid, Message)>>,
791 message_receivers: HashMap<Uuid, mpsc::Receiver<(Uuid, Message)>>,
792 app_context: AppContext,
793}
794
795impl ThreadManager {
796 pub fn new(app_context: AppContext) -> Self {
797 ThreadManager {
798 threads: HashMap::new(),
799 app_context_senders: HashMap::new(),
800 message_senders: HashMap::new(),
801 app_context_receivers: HashMap::new(),
802 message_receivers: HashMap::new(),
803 app_context,
804 }
805 }
806
807 pub fn stop(&self) {
808 self.send_message_to_all_threads((Uuid::new_v4(), Message::Exit));
809 }
810
811 pub fn pause(&self) {
812 self.send_message_to_all_threads((Uuid::new_v4(), Message::Pause));
813 }
814
815 pub fn run(&mut self) {
816 for message_sender in self.message_senders.values() {
817 if let Err(e) = message_sender.send((Uuid::new_v4(), Message::Start)) {
818 error!("Failed to send start message to thread: {}", e);
819 }
820 }
821 let mut should_continue: bool = true;
822 while should_continue {
823 let mut has_updates = false;
824
825 for reciever in self.app_context_receivers.values() {
827 if let Ok((uuid, app_context_updates)) = reciever.try_recv() {
828 if app_context_updates.is_empty() {
829 continue;
831 } else {
832 let app_context_updates_size_in_bytes =
833 bincode::serialize(&app_context_updates)
834 .unwrap_or_default()
835 .len();
836 log::trace!(
837 "Received {} updates from thread {} with total size {} bytes. Will relay to all other threads.",
838 app_context_updates.len(),
839 uuid,
840 app_context_updates_size_in_bytes
841 );
842 }
843
844 let original_app_context = self.app_context.clone();
845
846 self.app_context.app.apply_updates(app_context_updates);
851 self.send_app_context_update_to_all_threads((
852 uuid,
853 self.app_context.generate_diff(&original_app_context),
854 ));
855 has_updates = true;
856 }
857 }
858
859 let mut messages_to_process = Vec::new();
861 for reciever in self.message_receivers.values() {
862 if let Ok((uuid, received_msg)) = reciever.try_recv() {
863 messages_to_process.push((uuid, received_msg));
864 }
865 }
866
867 for (uuid, received_msg) in messages_to_process {
869 match received_msg {
871 Message::Exit => {
872 self.send_message_to_all_threads((Uuid::new_v4(), Message::Terminate));
873 should_continue = false;
874 }
875 Message::ExecuteScriptMessage(execute_script) => {
876 log::info!("ThreadManager processing ExecuteScript from thread {}: target_box_id={}, execution_mode={:?}",
877 uuid, execute_script.target_box_id, execute_script.execution_mode);
878
879 self.handle_execute_script(execute_script);
881 has_updates = true;
882 }
883 _ => {
884 self.send_message_to_all_threads((uuid, received_msg));
886 has_updates = true;
887 }
888 }
889 }
890
891 if !has_updates {
893 std::thread::sleep(std::time::Duration::from_millis(
894 self.app_context.config.frame_delay,
895 ));
896 }
897 }
898 }
899
900 pub fn spawn_thread<R: Runnable + 'static>(&mut self, mut runnable: R) -> Uuid {
901 let uuid = Uuid::new_v4();
902 let (s_tm_t_s, s_tm_t_r) = mpsc::channel::<(Uuid, Vec<FieldUpdate>)>();
903 let (s_t_tm_s, s_t_tm_r) = mpsc::channel::<(Uuid, Vec<FieldUpdate>)>();
904 let (m_tm_t_s, m_tm_t_r) = mpsc::channel::<(Uuid, Message)>();
905 let (m_t_tm_s, m_t_tm_r) = mpsc::channel::<(Uuid, Message)>();
906
907 runnable.set_uuid(uuid);
908 runnable.set_app_context_sender(s_t_tm_s);
909 runnable.set_message_sender(m_t_tm_s);
910 runnable.set_app_context_receiver(s_tm_t_r);
911 runnable.set_message_receiver(m_tm_t_r);
912
913 self.app_context_senders.insert(uuid, s_tm_t_s);
914 self.message_senders.insert(uuid, m_tm_t_s);
915 self.app_context_receivers.insert(uuid, s_t_tm_r);
916 self.message_receivers.insert(uuid, m_t_tm_r);
917
918 let runnable_class_name = std::any::type_name::<R>();
919 let thread_name = format!("{}_{}", runnable_class_name, uuid);
920
921 let handle = thread::Builder::new()
922 .name(thread_name)
923 .spawn(move || {
924 let mut continue_running = true;
925 while continue_running {
926 let result = runnable.run();
927 if let Err(e) = result {
928 error!("Runnable encountered an error: {}", e);
929 continue_running = false;
930 } else if let Ok(should_continue) = result {
931 continue_running = should_continue;
932 if !continue_running {
933 log::trace!("Stopping thread as directed by run method");
934 }
935 }
936 }
937 })
938 .unwrap();
939
940 self.threads.insert(uuid, handle);
941
942 log::trace!("Thread spawned: {}", uuid);
943
944 uuid
945 }
946
947 pub fn send_app_context_update_to_thread(&self, field_updates: Vec<FieldUpdate>, uuid: Uuid) {
948 if let Some(sender) = self.app_context_senders.get(&uuid) {
949 if let Err(e) = sender.send((uuid, field_updates)) {
950 error!("Failed to send data to thread: {}", e);
951 }
952 }
953 }
954
955 pub fn send_app_context_update_to_all_threads(&self, field_updates: (Uuid, Vec<FieldUpdate>)) {
956 for (&uuid, sender) in &self.app_context_senders {
957 if uuid != field_updates.0 {
958 if let Err(e) = sender.send(field_updates.clone()) {
959 error!("Failed to send update to thread: {}", e);
960 }
961 } else {
962 log::trace!("Skipping sending update to thread: {}", uuid);
963 }
964 }
965 }
966
967 pub fn send_message_to_thread(&self, msg: (Uuid, Message), uuid: Uuid) {
968 if let Some(sender) = self.message_senders.get(&uuid) {
969 log::debug!("ThreadManager found message sender for thread: {}", uuid);
970 if let Err(e) = sender.send(msg) {
971 log::error!("Failed to send message to thread {}: {}", uuid, e);
972 } else {
973 log::debug!(
974 "ThreadManager successfully sent message to thread: {}",
975 uuid
976 );
977 }
978 } else {
979 log::error!(
980 "ThreadManager could not find message sender for thread: {}",
981 uuid
982 );
983 }
984 }
985
986 pub fn send_message_to_all_threads(&self, msg: (Uuid, Message)) {
987 for (&uuid, sender) in &self.message_senders {
988 if uuid != msg.0 {
989 if let Err(e) = sender.send(msg.clone()) {
990 error!("Failed to send message to thread: {}", e);
991 }
992 }
993 }
994 }
995
996 pub fn join_threads(&mut self) {
997 for handle in self.threads.drain() {
998 if let Err(e) = handle.1.join() {
999 error!("Failed to join thread: {:?}", e);
1000 }
1001 }
1002 }
1003
1004 pub fn get_hash<T: Hash>(&self, t: &T) -> u64 {
1005 let mut hasher = DefaultHasher::new();
1006 t.hash(&mut hasher);
1007 hasher.finish()
1008 }
1009
1010 pub fn remove_thread(&mut self, uuid: Uuid) {
1011 if let Some(handle) = self.threads.remove(&uuid) {
1012 if let Err(e) = handle.join() {
1013 error!("Failed to join thread: {:?}", e);
1014 }
1015 }
1016 let msg = (Uuid::new_v4(), Message::Exit);
1017 self.send_message_to_thread(msg, uuid);
1018 self.app_context_senders.remove(&uuid);
1019 self.message_senders.remove(&uuid);
1020 }
1021
1022 fn handle_execute_script(&mut self, execute_script: crate::model::common::ExecuteScript) {
1023 use crate::model::common::ExecutionMode;
1024
1025 log::info!(
1026 "T0315 FIXED: ThreadManager properly handling ExecuteScript for target_box: {}",
1027 execute_script.target_box_id
1028 );
1029
1030 match execute_script.execution_mode {
1032 ExecutionMode::Immediate => {
1033 log::info!("T0315: Immediate execution - running script synchronously");
1034 self.execute_immediate_script(execute_script);
1035 }
1036 ExecutionMode::Thread => {
1037 log::info!("T0315: Thread execution - dispatching to thread pool");
1038 self.execute_threaded_script(execute_script);
1039 }
1040 ExecutionMode::Pty => {
1041 panic!("PTY ExecuteScript reached ThreadManager - this indicates a critical routing bug that should never happen");
1043 }
1044 }
1045 }
1046
1047 fn execute_immediate_script(&mut self, execute_script: crate::model::common::ExecuteScript) {
1048
1049 let output = crate::utils::detached_command("sh")
1051 .arg("-c")
1052 .arg(execute_script.script.join(" "))
1053 .output();
1056
1057 let content = match output {
1058 Ok(output) => {
1059 let stdout = String::from_utf8_lossy(&output.stdout);
1060 let stderr = String::from_utf8_lossy(&output.stderr);
1061 if stderr.is_empty() {
1062 stdout.to_string()
1063 } else {
1064 format!("{}\n{}", stdout, stderr)
1065 }
1066 }
1067 Err(e) => {
1068 format!("Error executing script: {}", e)
1069 }
1070 };
1071
1072 let stream_id = execute_script.stream_id.clone();
1074
1075 let target_box_id = if let Some(ref redirect_to) = execute_script.redirect_output {
1078 log::info!(
1079 "THREADMANAGER REDIRECT FIX IMMEDIATE: Using redirect destination: {} (was {})",
1080 redirect_to,
1081 execute_script.target_box_id
1082 );
1083 redirect_to.clone()
1084 } else {
1085 log::info!(
1086 "THREADMANAGER REDIRECT FIX IMMEDIATE: No redirect, using source box: {}",
1087 execute_script.target_box_id
1088 );
1089 execute_script.target_box_id.clone()
1090 };
1091
1092 let stream_update = crate::model::common::StreamUpdate {
1093 stream_id,
1094 target_box_id,
1095 content_update: content,
1096 source_state: crate::model::common::SourceState::Batch(
1097 crate::model::common::BatchSourceState {
1098 task_id: "immediate".to_string(),
1099 queue_wait_time: std::time::Duration::from_millis(0),
1100 execution_time: std::time::Duration::from_millis(50), exit_code: Some(0),
1102 status: crate::model::common::BatchStatus::Completed,
1103 },
1104 ),
1105 execution_mode: execute_script.execution_mode,
1106 };
1107
1108 self.send_message_to_all_threads((
1110 uuid::Uuid::new_v4(),
1111 Message::StreamUpdateMessage(stream_update),
1112 ));
1113 }
1114
1115 fn execute_threaded_script(&mut self, execute_script: crate::model::common::ExecuteScript) {
1116 log::info!("T0315: Thread execution - using existing thread pool infrastructure");
1117
1118 let stream_id = execute_script.stream_id.clone();
1120
1121 let libs = if execute_script.libs.is_empty() {
1123 None
1124 } else {
1125 Some(execute_script.libs.clone())
1126 };
1127
1128 let target_box_id = if let Some(ref redirect_to) = execute_script.redirect_output {
1130 log::info!(
1131 "THREADMANAGER REDIRECT FIX THREAD: Using redirect destination: {} (was {})",
1132 redirect_to,
1133 execute_script.target_box_id
1134 );
1135 redirect_to.clone()
1136 } else {
1137 log::info!(
1138 "THREADMANAGER REDIRECT FIX THREAD: No redirect, using source box: {}",
1139 execute_script.target_box_id
1140 );
1141 execute_script.target_box_id.clone()
1142 };
1143 let script = execute_script.script.clone();
1144 let execution_mode = execute_script.execution_mode.clone();
1145 let redirect_target = execute_script.redirect_output.clone();
1146 let message_senders = self.message_senders.clone();
1147 let thread_manager_uuid = uuid::Uuid::new_v4();
1148
1149 std::thread::spawn(move || {
1151 let result = crate::utils::run_script_with_pty_and_redirect(
1152 libs,
1153 &script,
1154 &execution_mode,
1155 None, None, None, redirect_target,
1159 );
1160
1161 let (content, is_success) = match result {
1162 Ok(output) => (output, true),
1163 Err(e) => (format!("Thread execution error: {}", e), false),
1164 };
1165
1166 let final_update = crate::model::common::StreamUpdate {
1168 stream_id,
1169 target_box_id,
1170 content_update: content,
1171 source_state: crate::model::common::SourceState::Thread(
1172 crate::model::common::ThreadSourceState {
1173 thread_id: format!("{:?}", std::thread::current().id()),
1174 execution_time: std::time::Duration::from_millis(100), exit_code: Some(if is_success { 0 } else { 1 }), status: crate::model::common::ExecutionThreadStatus::Completed,
1177 },
1178 ),
1179 execution_mode,
1180 };
1181
1182 for (uuid, sender) in message_senders.iter() {
1184 if let Err(e) = sender.send((
1185 thread_manager_uuid,
1186 Message::StreamUpdateMessage(final_update.clone()),
1187 )) {
1188 log::error!(
1189 "Failed to send thread execution result to thread {}: {}",
1190 uuid,
1191 e
1192 );
1193 }
1194 }
1195 });
1196 }
1197}
1198
1199#[macro_export]
1200macro_rules! create_runnable {
1201 ($name:ident, $init_body:expr, $process_body:expr) => {
1202 pub struct $name {
1203 inner: RunnableImpl,
1204 }
1205
1206 impl $name {
1207 pub fn new(app_context: AppContext) -> Self {
1208 $name {
1209 inner: RunnableImpl::new(app_context),
1210 }
1211 }
1212 }
1213
1214 impl Runnable for $name {
1215 fn run(&mut self) -> Result<bool, Box<dyn std::error::Error>> {
1216 {
1218 let inner = &mut self.inner;
1219 let app_context = inner.app_context.clone();
1220 let messages = Vec::new();
1221 let init_result = $init_body(inner, app_context, messages);
1222 if !init_result {
1223 return Ok(false);
1224 }
1225 }
1226 self.inner._run(&mut |inner, app_context, messages| {
1227 $process_body(inner, app_context, messages)
1228 })
1229 }
1230
1231 fn receive_updates(&mut self) -> (AppContext, Vec<Message>) {
1232 self.inner.receive_updates()
1233 }
1234
1235 fn process(&mut self, app_context: AppContext, messages: Vec<Message>) {
1236 self.inner.process(app_context, messages)
1237 }
1238
1239 fn update_app_context(&mut self, app_context: AppContext) {
1240 self.inner.update_app_context(app_context)
1241 }
1242
1243 fn set_uuid(&mut self, uuid: Uuid) {
1244 self.inner.set_uuid(uuid)
1245 }
1246
1247 fn get_uuid(&self) -> Uuid {
1248 self.inner.get_uuid()
1249 }
1250
1251 fn set_app_context_sender(
1252 &mut self,
1253 app_context_sender: mpsc::Sender<(Uuid, Vec<FieldUpdate>)>,
1254 ) {
1255 self.inner.set_app_context_sender(app_context_sender)
1256 }
1257
1258 fn set_message_sender(&mut self, message_sender: mpsc::Sender<(Uuid, Message)>) {
1259 self.inner.set_message_sender(message_sender)
1260 }
1261
1262 fn set_app_context_receiver(
1263 &mut self,
1264 app_context_receiver: mpsc::Receiver<(Uuid, Vec<FieldUpdate>)>,
1265 ) {
1266 self.inner.set_app_context_receiver(app_context_receiver)
1267 }
1268
1269 fn set_message_receiver(&mut self, message_receiver: mpsc::Receiver<(Uuid, Message)>) {
1270 self.inner.set_message_receiver(message_receiver)
1271 }
1272
1273 fn get_app_context(&self) -> &AppContext {
1274 self.inner.get_app_context()
1275 }
1276
1277 fn get_app_context_sender(&self) -> &Option<mpsc::Sender<(Uuid, Vec<FieldUpdate>)>> {
1278 self.inner.get_app_context_sender()
1279 }
1280
1281 fn get_message_sender(&self) -> &Option<mpsc::Sender<(Uuid, Message)>> {
1282 self.inner.get_message_sender_option_ref()
1283 }
1284
1285 fn send_app_context_update(&self, old_app_context: AppContext) {
1286 self.inner.send_app_context_update(old_app_context)
1287 }
1288
1289 fn send_message(&self, msg: Message) {
1290 self.inner.send_message(msg)
1291 }
1292 }
1293 };
1294}
1295
1296create_runnable!(
1298 ChoiceExecutionRunnable,
1299 |inner: &mut RunnableImpl, _app_context: AppContext, _messages: Vec<Message>| -> bool {
1300 log::debug!(
1302 "ChoiceExecutionRunnable initialization: state={:?}",
1303 inner.running_state
1304 );
1305 inner.running_state = RunnableState::Running;
1306 log::debug!("ChoiceExecutionRunnable set state to Running");
1307 true
1308 },
1309 |_inner: &mut RunnableImpl,
1310 app_context: AppContext,
1311 messages: Vec<Message>|
1312 -> (bool, AppContext) {
1313 let message_count = messages.len();
1315 log::debug!(
1316 "ChoiceExecutionRunnable processing function called with {} messages",
1317 message_count
1318 );
1319 for message in messages {
1320 {
1323 log::debug!(
1324 "ChoiceExecutionRunnable: Unhandled message type: {:?}",
1325 message
1326 );
1327 }
1328 }
1329
1330 let should_continue = true; log::debug!(
1334 "ChoiceExecutionRunnable: messages={}, should_continue={}",
1335 message_count,
1336 should_continue
1337 );
1338 (should_continue, app_context)
1339 }
1340);
1341
1342#[macro_export]
1345macro_rules! create_runnable_with_dynamic_input {
1346 ($name:ident, $vec_fn:expr, $init_body:expr, $process_body:expr) => {
1347 pub struct $name {
1348 inner: RunnableImpl,
1349 vec_fn: Box<dyn Fn() -> Vec<String> + Send>,
1350 }
1351
1352 impl $name {
1353 pub fn new(
1354 app_context: AppContext,
1355 vec_fn: Box<dyn Fn() -> Vec<String> + Send>,
1356 ) -> Self {
1357 $name {
1358 inner: RunnableImpl::new(app_context),
1359 vec_fn,
1360 }
1361 }
1362 }
1363
1364 impl Runnable for $name {
1365 fn run(&mut self) -> Result<bool, Box<dyn std::error::Error>> {
1366 {
1368 let inner = &mut self.inner;
1369 let app_context = inner.app_context.clone();
1370 let messages = Vec::new();
1371 let vec = (self.vec_fn)();
1372 let init_result = $init_body(inner, app_context, messages, vec);
1373 if !init_result {
1374 return Ok(false);
1375 }
1376 }
1377 self.inner._run(&mut |inner, app_context, messages| {
1378 let vec = (self.vec_fn)();
1379 $process_body(inner, app_context, messages, vec)
1380 })
1381 }
1382
1383 fn receive_updates(&mut self) -> (AppContext, Vec<Message>) {
1384 self.inner.receive_updates()
1385 }
1386
1387 fn process(&mut self, app_context: AppContext, messages: Vec<Message>) {
1388 self.inner.process(app_context.clone(), messages.clone());
1389 }
1390
1391 fn update_app_context(&mut self, app_context: AppContext) {
1392 self.inner.update_app_context(app_context)
1393 }
1394
1395 fn set_uuid(&mut self, uuid: Uuid) {
1396 self.inner.set_uuid(uuid)
1397 }
1398
1399 fn get_uuid(&self) -> Uuid {
1400 self.inner.get_uuid()
1401 }
1402
1403 fn set_app_context_sender(
1404 &mut self,
1405 app_context_sender: mpsc::Sender<(Uuid, Vec<FieldUpdate>)>,
1406 ) {
1407 self.inner.set_app_context_sender(app_context_sender)
1408 }
1409
1410 fn set_message_sender(&mut self, message_sender: mpsc::Sender<(Uuid, Message)>) {
1411 self.inner.set_message_sender(message_sender)
1412 }
1413
1414 fn set_app_context_receiver(
1415 &mut self,
1416 app_context_receiver: mpsc::Receiver<(Uuid, Vec<FieldUpdate>)>,
1417 ) {
1418 self.inner.set_app_context_receiver(app_context_receiver)
1419 }
1420
1421 fn set_message_receiver(&mut self, message_receiver: mpsc::Receiver<(Uuid, Message)>) {
1422 self.inner.set_message_receiver(message_receiver)
1423 }
1424
1425 fn get_app_context(&self) -> &AppContext {
1426 self.inner.get_app_context()
1427 }
1428
1429 fn get_app_context_sender(&self) -> &Option<mpsc::Sender<(Uuid, Vec<FieldUpdate>)>> {
1430 self.inner.get_app_context_sender()
1431 }
1432
1433 fn get_message_sender(&self) -> &Option<mpsc::Sender<(Uuid, Message)>> {
1434 self.inner.get_message_sender_option_ref()
1435 }
1436
1437 fn send_app_context_update(&self, old_app_context: AppContext) {
1438 self.inner.send_app_context_update(old_app_context)
1439 }
1440
1441 fn send_message(&self, msg: Message) {
1442 self.inner.send_message(msg)
1443 }
1444 }
1445 };
1446}
1447
1448pub fn run_script_in_thread(
1449 app_context: AppContext,
1450 manager: &mut ThreadManager,
1451 muxbox_id: String,
1452 choice_id: String,
1453) -> Uuid {
1454 let vec_fn = move || vec![muxbox_id.clone(), choice_id.clone()];
1455
1456 create_runnable_with_dynamic_input!(
1457 ChoiceScriptRunner,
1458 Box::new(vec_fn),
1459 |_inner: &mut RunnableImpl,
1460 _app_context: AppContext,
1461 _messages: Vec<Message>,
1462 _vec: Vec<String>|
1463 -> bool { true },
1464 |inner: &mut RunnableImpl,
1465 app_context: AppContext,
1466 _messages: Vec<Message>,
1467 vec: Vec<String>|
1468 -> (bool, AppContext) {
1469 let mut app_context_unwrapped = app_context.clone();
1470 let app_graph = app_context_unwrapped.app.generate_graph();
1471 let libs = app_context_unwrapped.app.libs.clone();
1472 let muxbox = app_context_unwrapped
1473 .app
1474 .get_muxbox_by_id_mut(&vec[0])
1475 .unwrap();
1476 let choice = muxbox
1477 .choices
1478 .as_mut()
1479 .unwrap()
1480 .iter_mut()
1481 .find(|c| c.id == vec[1])
1482 .unwrap();
1483 let execution_mode = &choice.execution_mode;
1485 let pty_manager = app_context_unwrapped.pty_manager.as_ref();
1486 let message_sender = inner
1487 .get_message_sender()
1488 .map(|sender| (sender.clone(), inner.get_uuid()));
1489
1490 match crate::utils::run_script_with_pty(
1491 libs,
1492 choice.script.clone().unwrap().as_ref(),
1493 execution_mode, pty_manager.map(|arc| arc.as_ref()),
1495 Some(choice.id.clone()),
1496 message_sender,
1497 ) {
1498 Ok(output) => {
1499 let stream_id = format!("{}_{}", choice.id, execution_mode.as_stream_suffix());
1501 let stream_update = crate::model::common::StreamUpdate {
1503 stream_id,
1504 target_box_id: vec[0].clone(),
1505 content_update: output,
1506 source_state: crate::model::common::SourceState::Thread(
1507 crate::model::common::ThreadSourceState {
1508 thread_id: format!("{:?}", std::thread::current().id()),
1509 execution_time: std::time::Duration::from_millis(0),
1510 exit_code: Some(0),
1511 status: crate::model::common::ExecutionThreadStatus::Completed,
1512 },
1513 ),
1514 execution_mode: execution_mode.clone(),
1515 };
1516 inner.send_message(Message::StreamUpdateMessage(stream_update))
1517 }
1518 Err(e) => {
1519 let stream_id = format!("{}_{}", choice.id, execution_mode.as_stream_suffix());
1520 let stream_update = crate::model::common::StreamUpdate {
1522 stream_id,
1523 target_box_id: vec[0].clone(),
1524 content_update: e.to_string(),
1525 source_state: crate::model::common::SourceState::Thread(
1526 crate::model::common::ThreadSourceState {
1527 thread_id: format!("{:?}", std::thread::current().id()),
1528 execution_time: std::time::Duration::from_millis(0),
1529 exit_code: Some(1),
1530 status: crate::model::common::ExecutionThreadStatus::Failed(
1531 e.to_string(),
1532 ),
1533 },
1534 ),
1535 execution_mode: execution_mode.clone(),
1536 };
1537 inner.send_message(Message::StreamUpdateMessage(stream_update))
1538 }
1539 }
1540 std::thread::sleep(std::time::Duration::from_millis(
1541 muxbox.calc_refresh_interval(&app_context, &app_graph),
1542 ));
1543 (false, app_context_unwrapped)
1544 }
1545 );
1546
1547 let choice_refresh_loop = ChoiceScriptRunner::new(app_context.clone(), Box::new(vec_fn));
1548 manager.spawn_thread(choice_refresh_loop)
1549}
1550
1551#[cfg(test)]
1828mod tests {
1829 use super::*;
1830 use crate::model::app::App;
1831 use crate::model::common::{Config, EntityType, FieldUpdate};
1832 use crate::model::layout::Layout;
1833 use crate::model::muxbox::MuxBox;
1834 use serde_json::Value;
1835 use std::sync::mpsc;
1836
1837 fn create_test_app_context() -> AppContext {
1839 let mut app = App::new();
1840 let layout = create_test_layout("test_layout");
1841 app.layouts.push(layout);
1842 let config = Config::default();
1843 AppContext::new(app, config)
1844 }
1845
1846 fn create_test_muxbox(id: &str) -> MuxBox {
1848 MuxBox {
1849 id: id.to_string(),
1850 position: crate::model::common::InputBounds {
1851 x1: "0%".to_string(),
1852 y1: "0%".to_string(),
1853 x2: "100%".to_string(),
1854 y2: "100%".to_string(),
1855 },
1856 ..Default::default()
1857 }
1858 }
1859
1860 fn create_test_layout(id: &str) -> Layout {
1862 Layout {
1863 id: id.to_string(),
1864 ..Default::default()
1865 }
1866 }
1867
1868 fn create_test_field_update(
1870 entity_type: EntityType,
1871 entity_id: &str,
1872 field_name: &str,
1873 value: Value,
1874 ) -> FieldUpdate {
1875 FieldUpdate {
1876 entity_type,
1877 entity_id: Some(entity_id.to_string()),
1878 field_name: field_name.to_string(),
1879 new_value: value,
1880 }
1881 }
1882
1883 #[test]
1886 fn test_message_hash() {
1887 let mut hasher1 = DefaultHasher::new();
1888 let mut hasher2 = DefaultHasher::new();
1889
1890 let msg1 = Message::Exit;
1891 let msg2 = Message::Exit;
1892 msg1.hash(&mut hasher1);
1893 msg2.hash(&mut hasher2);
1894 assert_eq!(hasher1.finish(), hasher2.finish());
1895
1896 let msg3 = Message::RedrawMuxBox("muxbox1".to_string());
1897 let msg4 = Message::RedrawMuxBox("muxbox1".to_string());
1898 let mut hasher3 = DefaultHasher::new();
1899 let mut hasher4 = DefaultHasher::new();
1900 msg3.hash(&mut hasher3);
1901 msg4.hash(&mut hasher4);
1902 assert_eq!(hasher3.finish(), hasher4.finish());
1903 }
1904
1905 #[test]
1908 fn test_message_equality() {
1909 assert_eq!(Message::Exit, Message::Exit);
1910 assert_eq!(Message::Terminate, Message::Terminate);
1911 assert_eq!(
1912 Message::RedrawMuxBox("muxbox1".to_string()),
1913 Message::RedrawMuxBox("muxbox1".to_string())
1914 );
1915 assert_ne!(
1916 Message::RedrawMuxBox("muxbox1".to_string()),
1917 Message::RedrawMuxBox("muxbox2".to_string())
1918 );
1919 assert_ne!(Message::Exit, Message::Terminate);
1920 }
1921
1922 #[test]
1925 fn test_message_hash_different_types() {
1926 let msg1 = Message::KeyPress("a".to_string());
1927 let msg2 = Message::KeyPress("a".to_string());
1928 let msg3 = Message::KeyPress("b".to_string());
1929
1930 let mut hasher1 = DefaultHasher::new();
1931 let mut hasher2 = DefaultHasher::new();
1932 let mut hasher3 = DefaultHasher::new();
1933
1934 msg1.hash(&mut hasher1);
1935 msg2.hash(&mut hasher2);
1936 msg3.hash(&mut hasher3);
1937
1938 assert_eq!(hasher1.finish(), hasher2.finish());
1939 assert_ne!(hasher1.finish(), hasher3.finish());
1940 }
1941
1942 #[test]
1945 fn test_runnable_state() {
1946 let state1 = RunnableState::Created;
1947 let state2 = state1.clone();
1948 assert_eq!(state1, state2);
1949
1950 let state3 = RunnableState::Running;
1951 assert_ne!(state1, state3);
1952
1953 let state4 = RunnableState::Paused;
1954 let state5 = RunnableState::Terminated;
1955 assert_ne!(state4, state5);
1956 }
1957
1958 #[test]
1961 fn test_runnable_impl_new() {
1962 let app_context = create_test_app_context();
1963 let runnable = RunnableImpl::new(app_context.clone());
1964
1965 assert_eq!(runnable.get_app_context(), &app_context);
1966 assert_eq!(runnable.running_state, RunnableState::Created);
1967 assert!(runnable.app_context_sender.is_none());
1968 assert!(runnable.message_sender.is_none());
1969 assert!(runnable.app_context_receiver.is_none());
1970 assert!(runnable.message_receiver.is_none());
1971 }
1972
1973 #[test]
1976 fn test_runnable_impl_uuid() {
1977 let app_context = create_test_app_context();
1978 let mut runnable = RunnableImpl::new(app_context);
1979
1980 let original_uuid = runnable.get_uuid();
1981 let new_uuid = Uuid::new_v4();
1982 runnable.set_uuid(new_uuid);
1983
1984 assert_eq!(runnable.get_uuid(), new_uuid);
1985 assert_ne!(runnable.get_uuid(), original_uuid);
1986 }
1987
1988 #[test]
1991 fn test_runnable_impl_channels() {
1992 let app_context = create_test_app_context();
1993 let mut runnable = RunnableImpl::new(app_context);
1994
1995 let (app_context_sender, app_context_receiver) = mpsc::channel();
1996 let (message_sender, message_receiver) = mpsc::channel();
1997
1998 runnable.set_app_context_sender(app_context_sender);
1999 runnable.set_message_sender(message_sender);
2000 runnable.set_app_context_receiver(app_context_receiver);
2001 runnable.set_message_receiver(message_receiver);
2002
2003 assert!(runnable.get_app_context_sender().is_some());
2004 assert!(runnable.get_message_sender().is_some());
2005 assert!(runnable.app_context_receiver.is_some());
2006 assert!(runnable.message_receiver.is_some());
2007 }
2008
2009 #[test]
2012 fn test_runnable_impl_update_app_context() {
2013 let app_context = create_test_app_context();
2014 let mut runnable = RunnableImpl::new(app_context);
2015
2016 let mut new_app_context = create_test_app_context();
2017 new_app_context.config.frame_delay = 100;
2018
2019 runnable.update_app_context(new_app_context.clone());
2020 assert_eq!(runnable.get_app_context(), &new_app_context);
2021 }
2022
2023 #[test]
2026 fn test_runnable_impl_receive_updates() {
2027 let app_context = create_test_app_context();
2028 let mut runnable = RunnableImpl::new(app_context);
2029
2030 let (app_context_sender, app_context_receiver) = mpsc::channel();
2031 let (message_sender, message_receiver) = mpsc::channel();
2032
2033 runnable.set_app_context_receiver(app_context_receiver);
2034 runnable.set_message_receiver(message_receiver);
2035
2036 let field_update = create_test_field_update(
2038 EntityType::App,
2039 "test",
2040 "field",
2041 Value::String("value".to_string()),
2042 );
2043 app_context_sender
2044 .send((Uuid::new_v4(), vec![field_update]))
2045 .unwrap();
2046 message_sender
2047 .send((Uuid::new_v4(), Message::RedrawApp))
2048 .unwrap();
2049
2050 let (updated_app_context, messages) = runnable.receive_updates();
2051 assert_eq!(messages.len(), 1);
2052 assert_eq!(messages[0], Message::RedrawApp);
2053 }
2054
2055 #[test]
2058 fn test_runnable_impl_process_messages() {
2059 let app_context = create_test_app_context();
2060 let mut runnable = RunnableImpl::new(app_context.clone());
2061
2062 let messages = vec![Message::Start, Message::Pause, Message::Terminate];
2063
2064 runnable.process(app_context.clone(), vec![Message::Start]);
2065 assert_eq!(runnable.running_state, RunnableState::Running);
2066
2067 runnable.process(app_context.clone(), vec![Message::Pause]);
2068 assert_eq!(runnable.running_state, RunnableState::Paused);
2069
2070 runnable.process(app_context.clone(), vec![Message::Terminate]);
2071 assert_eq!(runnable.running_state, RunnableState::Terminated);
2072 }
2073
2074 #[test]
2077 fn test_runnable_impl_send_message() {
2078 let app_context = create_test_app_context();
2079 let mut runnable = RunnableImpl::new(app_context);
2080
2081 let (message_sender, message_receiver) = mpsc::channel();
2082 runnable.set_message_sender(message_sender);
2083
2084 runnable.send_message(Message::RedrawApp);
2085
2086 let (uuid, received_message) = message_receiver.recv().unwrap();
2087 assert_eq!(received_message, Message::RedrawApp);
2088 assert_eq!(uuid, runnable.get_uuid());
2089 }
2090
2091 #[test]
2094 fn test_thread_manager_new() {
2095 let app_context = create_test_app_context();
2096 let manager = ThreadManager::new(app_context.clone());
2097
2098 assert_eq!(manager.app_context, app_context);
2099 assert!(manager.threads.is_empty());
2100 assert!(manager.app_context_senders.is_empty());
2101 assert!(manager.message_senders.is_empty());
2102 assert!(manager.app_context_receivers.is_empty());
2103 assert!(manager.message_receivers.is_empty());
2104 }
2105
2106 #[test]
2109 fn test_thread_manager_spawn_thread() {
2110 let app_context = create_test_app_context();
2111 let mut manager = ThreadManager::new(app_context.clone());
2112 let runnable = RunnableImpl::new(app_context);
2113
2114 let uuid = manager.spawn_thread(runnable);
2115
2116 assert!(manager.threads.contains_key(&uuid));
2117 assert!(manager.app_context_senders.contains_key(&uuid));
2118 assert!(manager.message_senders.contains_key(&uuid));
2119 assert!(manager.app_context_receivers.contains_key(&uuid));
2120 assert!(manager.message_receivers.contains_key(&uuid));
2121
2122 manager.stop();
2124 manager.join_threads();
2125 }
2126
2127 #[test]
2130 fn test_thread_manager_remove_thread() {
2131 let app_context = create_test_app_context();
2132 let mut manager = ThreadManager::new(app_context.clone());
2133 let runnable = RunnableImpl::new(app_context);
2134
2135 let uuid = manager.spawn_thread(runnable);
2136 assert!(manager.threads.contains_key(&uuid));
2137
2138 manager.remove_thread(uuid);
2139 assert!(!manager.threads.contains_key(&uuid));
2140 assert!(!manager.app_context_senders.contains_key(&uuid));
2141 assert!(!manager.message_senders.contains_key(&uuid));
2142 }
2143
2144 #[test]
2147 fn test_thread_manager_send_message_to_thread() {
2148 let app_context = create_test_app_context();
2149 let mut manager = ThreadManager::new(app_context.clone());
2150 let runnable = RunnableImpl::new(app_context);
2151
2152 let uuid = manager.spawn_thread(runnable);
2153 let message = (Uuid::new_v4(), Message::RedrawApp);
2154
2155 manager.send_message_to_thread(message, uuid);
2156
2157 manager.stop();
2159 manager.join_threads();
2160 }
2161
2162 #[test]
2165 fn test_thread_manager_send_message_to_all_threads() {
2166 let app_context = create_test_app_context();
2167 let mut manager = ThreadManager::new(app_context.clone());
2168 let runnable1 = RunnableImpl::new(app_context.clone());
2169 let runnable2 = RunnableImpl::new(app_context.clone());
2170
2171 let uuid1 = manager.spawn_thread(runnable1);
2172 let uuid2 = manager.spawn_thread(runnable2);
2173
2174 let message = (Uuid::new_v4(), Message::RedrawApp);
2175 manager.send_message_to_all_threads(message);
2176
2177 manager.stop();
2179 manager.join_threads();
2180 }
2181
2182 #[test]
2185 fn test_thread_manager_send_app_context_update_to_thread() {
2186 let app_context = create_test_app_context();
2187 let mut manager = ThreadManager::new(app_context.clone());
2188 let runnable = RunnableImpl::new(app_context);
2189
2190 let uuid = manager.spawn_thread(runnable);
2191 let field_update = create_test_field_update(
2192 EntityType::App,
2193 "test",
2194 "field",
2195 Value::String("value".to_string()),
2196 );
2197
2198 manager.send_app_context_update_to_thread(vec![field_update], uuid);
2199
2200 manager.stop();
2202 manager.join_threads();
2203 }
2204
2205 #[test]
2208 fn test_thread_manager_send_app_context_update_to_all_threads() {
2209 let app_context = create_test_app_context();
2210 let mut manager = ThreadManager::new(app_context.clone());
2211 let runnable1 = RunnableImpl::new(app_context.clone());
2212 let runnable2 = RunnableImpl::new(app_context.clone());
2213
2214 let uuid1 = manager.spawn_thread(runnable1);
2215 let uuid2 = manager.spawn_thread(runnable2);
2216
2217 let field_update = create_test_field_update(
2218 EntityType::App,
2219 "test",
2220 "field",
2221 Value::String("value".to_string()),
2222 );
2223 let sender_uuid = Uuid::new_v4();
2224
2225 manager.send_app_context_update_to_all_threads((sender_uuid, vec![field_update]));
2226
2227 manager.stop();
2229 manager.join_threads();
2230 }
2231
2232 #[test]
2235 fn test_thread_manager_stop() {
2236 let app_context = create_test_app_context();
2237 let mut manager = ThreadManager::new(app_context.clone());
2238 let runnable = RunnableImpl::new(app_context);
2239
2240 let uuid = manager.spawn_thread(runnable);
2241 assert!(manager.threads.contains_key(&uuid));
2242
2243 manager.stop();
2244 manager.join_threads();
2245
2246 assert!(manager.threads.is_empty());
2247 }
2248
2249 #[test]
2252 fn test_thread_manager_pause() {
2253 let app_context = create_test_app_context();
2254 let mut manager = ThreadManager::new(app_context.clone());
2255 let runnable = RunnableImpl::new(app_context);
2256
2257 let uuid = manager.spawn_thread(runnable);
2258 manager.pause();
2259
2260 manager.stop();
2262 manager.join_threads();
2263 }
2264
2265 #[test]
2268 fn test_thread_manager_get_hash() {
2269 let app_context = create_test_app_context();
2270 let manager = ThreadManager::new(app_context);
2271
2272 let test_string = "test";
2273 let hash1 = manager.get_hash(&test_string);
2274 let hash2 = manager.get_hash(&test_string);
2275 assert_eq!(hash1, hash2);
2276
2277 let test_string2 = "different";
2278 let hash3 = manager.get_hash(&test_string2);
2279 assert_ne!(hash1, hash3);
2280 }
2281
2282 #[test]
2285 fn test_thread_manager_join_threads() {
2286 let app_context = create_test_app_context();
2287 let mut manager = ThreadManager::new(app_context.clone());
2288 let runnable = RunnableImpl::new(app_context);
2289
2290 let uuid = manager.spawn_thread(runnable);
2291 assert!(manager.threads.contains_key(&uuid));
2292
2293 manager.stop();
2294 manager.join_threads();
2295
2296 assert!(manager.threads.is_empty());
2297 }
2298
2299 #[test]
2302 fn test_run_script_in_thread() {
2303 let app_context = create_test_app_context();
2304 let mut manager = ThreadManager::new(app_context.clone());
2305
2306 let muxbox_id = "test_muxbox".to_string();
2307 let choice_id = "test_choice".to_string();
2308
2309 let uuid = run_script_in_thread(app_context, &mut manager, muxbox_id, choice_id);
2310
2311 assert!(manager.threads.contains_key(&uuid));
2312 assert!(manager.app_context_senders.contains_key(&uuid));
2313 assert!(manager.message_senders.contains_key(&uuid));
2314
2315 manager.stop();
2317 manager.join_threads();
2318 }
2319
2320 #[test]
2325 fn test_message_muxbox_script_update() {
2326 let muxbox_id = "test_muxbox".to_string();
2327 let script = vec!["echo 'test'".to_string(), "ls".to_string()];
2328
2329 let message = Message::MuxBoxScriptUpdate(muxbox_id.clone(), script.clone());
2330
2331 match message {
2332 Message::MuxBoxScriptUpdate(id, script_content) => {
2333 assert_eq!(id, muxbox_id);
2334 assert_eq!(script_content, script);
2335 }
2336 _ => panic!("Expected MuxBoxScriptUpdate message"),
2337 }
2338 }
2339
2340 #[test]
2343 fn test_message_replace_muxbox() {
2344 let muxbox_id = "test_muxbox".to_string();
2345 let muxbox = create_test_muxbox("new_muxbox");
2346
2347 let message = Message::ReplaceMuxBox(muxbox_id.clone(), muxbox.clone());
2348
2349 match message {
2350 Message::ReplaceMuxBox(id, new_muxbox) => {
2351 assert_eq!(id, muxbox_id);
2352 assert_eq!(new_muxbox.id, muxbox.id);
2353 }
2354 _ => panic!("Expected ReplaceMuxBox message"),
2355 }
2356 }
2357
2358 #[test]
2361 fn test_message_switch_active_layout() {
2362 let layout_id = "test_layout".to_string();
2363 let message = Message::SwitchActiveLayout(layout_id.clone());
2364
2365 match message {
2366 Message::SwitchActiveLayout(id) => {
2367 assert_eq!(id, layout_id);
2368 }
2369 _ => panic!("Expected SwitchActiveLayout message"),
2370 }
2371 }
2372
2373 #[test]
2376 fn test_message_key_press() {
2377 let key = "ctrl+c".to_string();
2378 let message = Message::KeyPress(key.clone());
2379
2380 match message {
2381 Message::KeyPress(pressed_key) => {
2382 assert_eq!(pressed_key, key);
2383 }
2384 _ => panic!("Expected KeyPress message"),
2385 }
2386 }
2387
2388 #[test]
2391 fn test_message_external_message() {
2392 let external_msg = "external command".to_string();
2393 let message = Message::ExternalMessage(external_msg.clone());
2394
2395 match message {
2396 Message::ExternalMessage(msg) => {
2397 assert_eq!(msg, external_msg);
2398 }
2399 _ => panic!("Expected ExternalMessage message"),
2400 }
2401 }
2402
2403 #[test]
2406 fn test_message_add_box() {
2407 let box_id = "test_box".to_string();
2408 let muxbox = create_test_muxbox("new_box");
2409
2410 let message = Message::AddBox(box_id.clone(), muxbox.clone());
2411
2412 match message {
2413 Message::AddBox(id, new_muxbox) => {
2414 assert_eq!(id, box_id);
2415 assert_eq!(new_muxbox.id, muxbox.id);
2416 }
2417 _ => panic!("Expected AddBox message"),
2418 }
2419 }
2420
2421 #[test]
2424 fn test_message_remove_box() {
2425 let box_id = "test_box".to_string();
2426 let message = Message::RemoveBox(box_id.clone());
2427
2428 match message {
2429 Message::RemoveBox(id) => {
2430 assert_eq!(id, box_id);
2431 }
2432 _ => panic!("Expected RemoveBox message"),
2433 }
2434 }
2435
2436 #[test]
2439 fn test_simple_messages() {
2440 let exit_msg = Message::Exit;
2441 let terminate_msg = Message::Terminate;
2442 let pause_msg = Message::Pause;
2443 let start_msg = Message::Start;
2444 let resize_msg = Message::Resize;
2445 let redraw_app_msg = Message::RedrawApp;
2446
2447 assert_eq!(exit_msg, Message::Exit);
2449 assert_eq!(terminate_msg, Message::Terminate);
2450 assert_eq!(pause_msg, Message::Pause);
2451 assert_eq!(start_msg, Message::Start);
2452 assert_eq!(resize_msg, Message::Resize);
2453 assert_eq!(redraw_app_msg, Message::RedrawApp);
2454
2455 assert_ne!(exit_msg, terminate_msg);
2457 assert_ne!(pause_msg, start_msg);
2458 assert_ne!(resize_msg, redraw_app_msg);
2459 }
2460
2461 #[test]
2464 fn test_scroll_messages() {
2465 let scroll_down = Message::ScrollMuxBoxDown();
2466 let scroll_up = Message::ScrollMuxBoxUp();
2467 let scroll_left = Message::ScrollMuxBoxLeft();
2468 let scroll_right = Message::ScrollMuxBoxRight();
2469 let scroll_page_up = Message::ScrollMuxBoxPageUp();
2470 let scroll_page_down = Message::ScrollMuxBoxPageDown();
2471
2472 assert_eq!(scroll_down, Message::ScrollMuxBoxDown());
2473 assert_eq!(scroll_up, Message::ScrollMuxBoxUp());
2474 assert_eq!(scroll_left, Message::ScrollMuxBoxLeft());
2475 assert_eq!(scroll_right, Message::ScrollMuxBoxRight());
2476 assert_eq!(scroll_page_up, Message::ScrollMuxBoxPageUp());
2477 assert_eq!(scroll_page_down, Message::ScrollMuxBoxPageDown());
2478
2479 assert_ne!(scroll_down, scroll_up);
2480 assert_ne!(scroll_left, scroll_right);
2481 }
2482
2483 #[test]
2486 fn test_navigation_messages() {
2487 let next_muxbox = Message::NextMuxBox();
2488 let previous_muxbox = Message::PreviousMuxBox();
2489
2490 assert_eq!(next_muxbox, Message::NextMuxBox());
2491 assert_eq!(previous_muxbox, Message::PreviousMuxBox());
2492 assert_ne!(next_muxbox, previous_muxbox);
2493 }
2494
2495 #[test]
2498 fn test_box_refresh_messages() {
2499 let box_id = "test_box".to_string();
2500 let start_refresh = Message::StartBoxRefresh(box_id.clone());
2501 let stop_refresh = Message::StopBoxRefresh(box_id.clone());
2502 let event_refresh = Message::MuxBoxEventRefresh(box_id.clone());
2503
2504 match start_refresh {
2505 Message::StartBoxRefresh(id) => assert_eq!(id, box_id),
2506 _ => panic!("Expected StartBoxRefresh"),
2507 }
2508
2509 match stop_refresh {
2510 Message::StopBoxRefresh(id) => assert_eq!(id, box_id),
2511 _ => panic!("Expected StopBoxRefresh"),
2512 }
2513
2514 match event_refresh {
2515 Message::MuxBoxEventRefresh(id) => assert_eq!(id, box_id),
2516 _ => panic!("Expected MuxBoxEventRefresh"),
2517 }
2518 }
2519
2520 #[test]
2523 fn test_redraw_muxbox_message() {
2524 let muxbox_id = "test_muxbox".to_string();
2525 let redraw_msg = Message::RedrawMuxBox(muxbox_id.clone());
2526
2527 match redraw_msg {
2528 Message::RedrawMuxBox(id) => assert_eq!(id, muxbox_id),
2529 _ => panic!("Expected RedrawMuxBox message"),
2530 }
2531 }
2532}