1use crate::debug_panic_or_return_log;
2use std::collections::HashMap;
3use std::io;
4use std::io::Write;
5use crate::r_log;
6use crate::networking::announce_shutdown;
7use common::cli_actions::{CliAction, CliActionResponse};
8use common::cli_actions::CliActionResponse::{Ack, FixtureError, FixtureTypeInfo, UnsupportedCommand};
9use common::logging::LogLevel;
10use common::logging::LogLevel::{Info, UserError, UserSuccess, Warning};
11use common::fixture::{ChannelIndex, ChannelValue, ChannelParameter, PropertyType, FixtureType, ColorPropertyType};
12use common::fixture::ChannelError::{ChannelAlreadyInUse, ChannelOutOfRange, UniverseOutOfRange};
13use common::fixture::FixtureError::{ChannelError, DmxStateDesync, FixtureNameAlreadyInUse, FixtureTypeNameAlreadyInUse, InvalidFixture, InvalidFixtureType, InvalidPropertyType, MissingProperty, MultipleColorOutputTypes};
14use common::r_debug_log;
15use crate::fixture;
16
17pub(super) fn execute_cli_action(is_kernel: bool, cli_action: &CliAction) -> (LogLevel, String) {
24 match cli_action {
25 CliAction::Help => {
26 const HELP_TEXT: &str = include_str!("../../../common/help.txt");
27 (UserSuccess, HELP_TEXT.to_string())
28 },
29
30 CliAction::FixtureNew { name, channels } => {
31 new_fixture_type(name.clone(), channels.clone())
32 },
33
34 CliAction::FixtureAdd { name, fixture_type_name, universe, channel } => {
35 new_fixture(name.clone(), fixture_type_name.clone(), *universe, *channel)
36 },
37
38 CliAction::FixtureMove {fixture_name, new_universe, new_channel} => {
39 move_fixture(fixture_name.clone(), *new_universe, *new_channel)
40 }
41
42 CliAction::FixtureRemove { fixture_name } => {
43 remove_fixture(fixture_name.clone())
44 }
45
46 CliAction::FixtureSet {name, property_type, value} => {
47 set_property_value(name.clone(), property_type.clone(), *value)
48 },
49
50 CliAction::FixtureGetType { fixture_name } => {
51 get_fixture_type(fixture_name.clone())
52 },
53
54 CliAction::Exit {save_changes} => {
55 if !is_kernel {
56 (UserError, "Only kernel can exit a session".to_string())
57 } else {
58 shutdown_kernel(save_changes);
59 (Info, "Shutdown aborted".to_string())
60 }
62
63 },
64
65 CliAction::OtherCommands {command} => {
66 execute_debug_command(command.clone())
67 },
68
69 }
71}
72
73fn execute_debug_command(line: String) -> (LogLevel, String) {
80 let mut line_iter = line.split_ascii_whitespace();
81 let arg_count = line_iter.clone().count().saturating_sub(1);
83 match line_iter.next() {
84
85 Some("create_debug") if cfg!(all(debug_assertions, not(test))) => {
86 let fixture_type_name = "rgb".to_string();
87 let universe = 0;
88
89 let mut channels = HashMap::new();
90 channels.insert(
91 PropertyType::Color(ColorPropertyType::Red), ChannelParameter::new(0, universe)
92 );
93 channels.insert(
94 PropertyType::Color(ColorPropertyType::Green), ChannelParameter::new(1, universe)
95 );
96 channels.insert(
97 PropertyType::Color(ColorPropertyType::Blue), ChannelParameter::new(2, universe)
98 );
99
100 let new_command = CliAction::FixtureNew {
101 name: fixture_type_name.clone(),
102 channels,
103 };
104
105 if let (UserError,error) = execute_cli_action(false,&new_command) {
106 return (UserError,error.to_string());
107 }
108 for i in 0..50 {
109 let name = i.to_string();
110 let start_channel = i * 3;
111
112 let add_command = CliAction::FixtureAdd {
113 name,
114 fixture_type_name: fixture_type_name.clone(),
115 universe,
116 channel: start_channel
117 };
118
119 match execute_cli_action(false, &add_command) {
120 (UserSuccess, _) => continue,
121 x => return x
122 }
123 }
124 (UserSuccess,"Created the debug-fixtures".to_string())
125 }
126
127 Some("set_all") if arg_count == 2 && cfg!(all(debug_assertions, not(test))) => {
128 let property_name = line_iter.next().unwrap().to_string();
129 let value = line_iter.next().unwrap().to_string();
130
131 let property_type = match PropertyType::from_str(&*property_name) {
132 Ok(property_type) => property_type,
133 Err(InvalidPropertyType(property_type)) => {
134 return (UserError,format!("Error: \"{property_type}\" is not a valid PropertyType"))
135 }
136 Err(_) => unreachable!() };
138
139 let value = match crate::cli::command_parsing::parse_cli_value(&*value) {
140 Ok(value) => value,
141 Err(e) => return (UserError,e.to_string()),
142 };
143
144 for i in 0..50 {
145 let name = i.to_string();
146 let set_command = CliAction::FixtureSet {
147 name,
148 property_type: property_type.clone(),
149 value,
150 };
151
152 match execute_cli_action(false, &set_command) {
153 (UserSuccess, _) => continue,
154 x => return x
155 }
156 }
157
158 (UserSuccess,format!("Set {} to {} in all debug-fixtures", property_type, value))
159 }
160
161 Some("break") if cfg!(all(debug_assertions, not(test))) => {
162 (Info,"Add a breakpoint at this point in the code to check the datastructures".to_string())
163 }
164
165 Some(command) => {
166 (UserError,format!("Unknown command \"{command}\". Please enter help, to get a list of commands."))
167 }
168
169 None => (UserError,"Unknown command. Please enter help, to get a list of commands.".to_string()),
170 }
171}
172
173pub(crate) fn execute_implicit_cli_action(cli_action: &CliAction) -> CliActionResponse {
179 match cli_action {
180 CliAction::Help => {
181 r_debug_log!(Warning, "A Client has sent an implicit Help-Command. Please dont let him do that.");
182 UnsupportedCommand
183 }
184
185 CliAction::FixtureNew { name, channels } => {
186 if let Err(e) = FixtureType::new(name.clone(), channels.clone()) {
187 r_debug_log!(Warning, "Implicit FixtureNew threw an (User-)Error: {:?}", e);
188 FixtureError(e)
189 } else {
190 Ack
191 }
192 }
193
194 CliAction::FixtureAdd { name, fixture_type_name, universe, channel } => {
195 if let Err(e) = fixture::new_fixture(name.clone(), fixture_type_name.clone(), *channel, *universe) {
196 r_debug_log!(Warning, "Implicit FixtureAdd threw an (User-)Error: {:?}", e);
197 FixtureError(e)
198 } else {
199 Ack
200 }
201 }
202
203 CliAction::FixtureMove { fixture_name, new_universe, new_channel } => {
204 if let Err(e) = fixture::move_fixture(fixture_name.clone(), *new_channel, *new_universe) {
205 r_debug_log!(Warning, "Implicit FixtureMove threw an (User-)Error: {:?}", e);
206 FixtureError(e)
207 } else {
208 Ack
209 }
210 }
211
212 CliAction::FixtureRemove { fixture_name } => {
213 if let Err(e) = fixture::remove_fixture(fixture_name.clone()) {
214 r_debug_log!(Warning, "Implicit FixtureRemove threw an (User-)Error: {:?}", e);
215 FixtureError(e)
216 } else {
217 Ack
218 }
219 }
220
221 CliAction::FixtureSet {name, property_type, value} => {
222 if let Err(e) = fixture::set_property(name.clone(), property_type.clone(), *value) {
223 r_debug_log!(Warning, "Implicit FixtureSet threw an (User-)Error: {:?}", e);
224 FixtureError(e)
225 } else {
226 Ack
227 }
228 }
229
230 CliAction::FixtureGetType { fixture_name } => {
231 match fixture::get_fixture_type(fixture_name.clone()) {
232 Ok(fixture_type) => FixtureTypeInfo(fixture_type),
233 Err(e) => {
234 r_debug_log!(Warning, "Implicit FixtureGetType threw an (User-)Error: {:?}", e);
235 FixtureError(e)
236 },
237 }
238 }
239
240 CliAction::Exit {..} => {
241 r_debug_log!(Warning, "{}", "A client has sent an implicit Exit-Command. Only kernel can exit a session");
242 UnsupportedCommand
243 }
244
245 CliAction::OtherCommands { command } => {
246 r_debug_log!(Warning, "A client has sent an implicit Debug-Command : {}. Please dont him do that.", command);
247 UnsupportedCommand
248 }
249 }
250}
251
252fn new_fixture_type(name: String, channels: HashMap<PropertyType, ChannelParameter>) -> (LogLevel, String) {
259 match FixtureType::new(name.clone(), channels) {
260
261 Err(ChannelError(ChannelAlreadyInUse(channel_type))) => {
262 (UserError,format!("Error: The channel {channel_type} overlaps with another channel."))
263 }
264
265 Err(ChannelError(ChannelOutOfRange)) => {
266 (UserError,"Error: A Channel is higher than the size of the Universe. This is not yet supported".into())
267 }
268
269 Err(FixtureTypeNameAlreadyInUse(name)) => {
270 (UserError, format!("Error: The Fixture type name {name} is already used."))
271 }
272
273 Err(InvalidPropertyType(property_type)) => {
274 (UserError,format!("Error: \"{property_type}\" is not a valid PropertyType"))
275 }
276
277 Err(MultipleColorOutputTypes(error_message)) => {
278 (UserError,error_message)
279 }
280
281 Err(_) => {
282 debug_panic_or_return_log!("new_fixture_type() threw an Error it shouldn't")
283 }
284
285 Ok(()) => {
286 (UserSuccess,format!("{} created successfully", name))
287 }
288 }
289}
290
291fn new_fixture(name: String, fixture_type_name: String, universe: usize, channel: ChannelIndex,)
300 -> (LogLevel, String) {
301 match fixture::new_fixture(name.clone(), fixture_type_name, channel, universe) {
302
303 Err(FixtureNameAlreadyInUse(name)) => {
304 (UserError,format!("Error: The Fixture name {name} is already used."))
305 }
306
307 Err(InvalidFixtureType(fixture_type_name)) => {
308 (UserError,format!("Error: There is no fixture-type named \"{fixture_type_name}\"."))
309 }
310
311 Err(ChannelError(ChannelAlreadyInUse(overlapping_fixture))) => {
312 (UserError,format!(
313 "Error: At least one Channel of this fixture is overlapping with {}. Fixture has not been created.",
314 overlapping_fixture
315 ))
316 }
317
318 Err(ChannelError(ChannelOutOfRange)) => {
319 (UserError,"Error: fixture overflows out of this remaining universe".to_string())
320 }
321
322 Err(ChannelError(UniverseOutOfRange)) => {
323 debug_panic_or_return_log!(
324 "Fatal Error: Fixture created in Universe that does not exist. Normally, the programm should \
325 automatically create an universe, but somehow, this hasn't happened"
326 )
327 }
328
329 Err(DmxStateDesync) => {
330 debug_panic_or_return_log!(
331 "Fatal Error: Dmx-State has desynced from real Fixture-Positions"
332 )
333 }
334
335 Err(_) => {
336 debug_panic_or_return_log!("new_fixture_type() threw an Error it shouldn't")
337 }
338
339 Ok(_) => {
340 (UserSuccess,format!("{} created successfully", name))
341 }
342 }
343}
344
345fn move_fixture(fixture_name: String, new_universe: usize, new_channel: ChannelIndex) -> (LogLevel, String) {
353 match fixture::move_fixture(fixture_name.clone(), new_channel, new_universe) {
354
355 Err(InvalidFixture(fixture_name)) => {
356 (UserError,format!("Error: There is no fixture named \"{fixture_name}\"."))
357 }
358
359 Err(ChannelError(ChannelAlreadyInUse(overlapping_fixture))) => {
360 (UserError,format!(
361 "Error: At least one Channel of this fixture is overlapping with {}. Fixture has not been created.",
362 overlapping_fixture
363 ))
364 }
365
366 Err(ChannelError(ChannelOutOfRange)) => {
367 (UserError,"Error: fixture overflows out of this remaining universe".to_string())
368 }
369
370 Err(ChannelError(UniverseOutOfRange)) => {
371 debug_panic_or_return_log!(
372 "Fatal Error: Fixture created in Universe that does not exist. Normally, the programm should \
373 automatically create an universe, but somehow, this hasn't happened"
374 )
375 }
376
377 Err(DmxStateDesync) => {
378 debug_panic_or_return_log!(
379 "Fatal Error: Dmx-State has desynced from real Fixture-Positions"
380 )
381 }
382
383 Err(_) => {
384 debug_panic_or_return_log!("new_fixture_type() threw an Error it shouldn't")
385 }
386
387 Ok(_) => {
388 (UserSuccess,format!("{} moved successfully", fixture_name))
389 }
390 }
391}
392
393fn remove_fixture(fixture_name: String) -> (LogLevel, String) {
399 match fixture::remove_fixture(fixture_name.clone()) {
400
401 Err(InvalidFixture(fixture_name)) => {
402 (UserError,format!("Error: There is no fixture named \"{fixture_name}\""))
403 }
404
405 Err(ChannelError(UniverseOutOfRange)) => {
406 debug_panic_or_return_log!(
407 "Fatal Error: Fixture {} is in a non-existent Universe", fixture_name
408 )
409 }
410
411 Err(DmxStateDesync) => {
412 debug_panic_or_return_log!(
413 "Fatal Error: Dmx-State has desynced from real Fixture-Positions"
414 )
415 }
416
417 Err(_) => {
418 debug_panic_or_return_log!("new_fixture_type() threw an Error it shouldn't")
419 }
420
421 Ok(_) => {
422 (UserSuccess,format!("{} removed successfully", fixture_name))
423 }
424 }
425}
426
427fn set_property_value(fixture_name: String, property_type: PropertyType, value: ChannelValue) -> (LogLevel, String) {
435 match fixture::set_property(fixture_name.clone(), property_type.clone(), value) {
436 Err(InvalidFixture(name)) => {
437 (UserError,format!("Error: \"{name}\" is not a valid Fixture"))
438 }
439
440 Err(MissingProperty(_)) => {
441 (UserError,format!("Error: \"{fixture_name}\" has no property \"{property_type}\""))
442 }
443
444 Err(_) => {
445 debug_panic_or_return_log!("new_fixture_type() threw an Error it shouldn't")
446 }
447
448 Ok(_) => {
449 (UserSuccess,format!("Value {property_type} of {fixture_name} changed successfully to {value}"))
450 }
451 }
452}
453
454fn get_fixture_type(fixture_name: String) -> (LogLevel, String) {
460 match fixture::get_fixture_type(fixture_name.clone()) {
461
462 Err(InvalidFixture(fixture)) => {
463 (UserError,format!("Error: \"{fixture}\" is not a valid Fixture"))
464 }
465
466 Err(_) => {
467 debug_panic_or_return_log!("get_fixture_type_from_string() threw an Error it shouldn't")
468 }
469
470 Ok(fixture_type) => {
471 (Info,format!("\"{fixture_name}\" is a fixture of the type \"{fixture_type}\""))
472 }
473 }
474}
475
476
477fn shutdown_kernel(save_changes: &Option<bool>) {
483 r_log!(Info,"Shutting down Kernel");
484
485 let (should_exit, save_changes) = match save_changes {
486 Some(save_changes) => (true, *save_changes),
487 None => {
488 let stdout = io::stdout();
489 let mut handle = stdout.lock();
490
491 write!(handle, "\r\x1B[2K").unwrap();
492 write!(handle, "\x1b[33m[System] Do you want to save before exiting?\n\
493 (1) Save and exit (Warning: Not yet implemented. Its impossible to save right now)\n\
494 (2) Discard and exit\n\
495 (0) Cancel\n>\
496 \x1b[0m").unwrap();
497
498 handle.flush().unwrap();
499
500 let mut exit_choice = String::new();
501 io::stdin().read_line(&mut exit_choice).unwrap();
502 match exit_choice.trim() {
503 "1" => (true, true),
504 "2" => (true, false),
505 _ => {
506 writeln!(handle, "\x1b[32m[System] Exit canceled. Resuming kernel...\x1b[0m").unwrap();
507 (false, false)
508 }
509 }
510 }
511 };
512
513 if should_exit {
514 if save_changes {
515 r_log!(Warning, "Couldnt save changes ... not yet implemented");
516 } else {
517 r_log!(Warning, "Exiting without saving changes");
518 }
519
520 announce_shutdown();
521
522 std::thread::sleep(std::time::Duration::from_millis(500));
524
525 std::process::exit(0);
526 }
527}
528
529#[macro_export]
531macro_rules! debug_panic_or_return_log {
532 ($($arg:tt)*) => {{
533 #[cfg(all(debug_assertions, not(test)))]
534 panic!("{}", format!($($arg)*));
535
536 #[cfg(not(all(debug_assertions, not(test))))]
537 (LogLevel::Error, format!($($arg)*))
538 }};
539}