1use std::{io, thread::sleep, time::Duration};
5
6use clap::{builder::TypedValueParser, Parser, ValueEnum};
7use tokio::sync::mpsc;
8use tower::{Service, ServiceExt};
9use tracing::level_filters::LevelFilter;
10
11use cuprate_consensus_context::{
12 BlockChainContextRequest, BlockChainContextResponse, BlockchainContextService,
13};
14use cuprate_helper::time::secs_to_hms;
15
16use crate::{
17 constants::PANIC_CRITICAL_SERVICE_ERROR,
18 logging::{self, CupratedTracingFilter},
19 statics,
20};
21
22#[derive(Debug, Parser)]
24#[command(
25 multicall = true,
26 subcommand_required = true,
27 rename_all = "snake_case",
28 help_template = "{all-args}",
29 arg_required_else_help = true,
30 disable_help_flag = true
31)]
32pub enum Command {
33 #[command(arg_required_else_help = true)]
35 SetLog {
36 #[arg(
38 short, long,
39 value_parser = clap::builder::PossibleValuesParser::new(["off", "trace", "debug", "info", "warn", "error"])
40 .map(|s| s.parse::<LevelFilter>().unwrap()),
41 )]
42 level: Option<LevelFilter>,
43 #[arg(value_enum, default_value_t)]
45 output_target: OutputTarget,
46 },
47
48 Status,
50
51 FastSyncStopHeight,
53}
54
55#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
57pub enum OutputTarget {
58 #[default]
60 Stdout,
61 File,
63}
64
65pub fn command_listener(incoming_commands: mpsc::Sender<Command>) -> ! {
67 let mut stdin = io::stdin();
68 let mut line = String::new();
69
70 loop {
71 line.clear();
72
73 if let Err(e) = stdin.read_line(&mut line) {
74 eprintln!("Failed to read from stdin: {e}");
75 sleep(Duration::from_secs(1));
76 continue;
77 }
78
79 match Command::try_parse_from(line.split_whitespace()) {
80 Ok(command) => drop(
81 incoming_commands
82 .blocking_send(command)
83 .inspect_err(|err| eprintln!("Failed to send command: {err}")),
84 ),
85 Err(err) => err.print().unwrap(),
86 }
87 }
88}
89
90pub async fn io_loop(
92 mut incoming_commands: mpsc::Receiver<Command>,
93 mut context_service: BlockchainContextService,
94) {
95 loop {
96 let Some(command) = incoming_commands.recv().await else {
97 tracing::warn!("Shutting down io_loop command channel closed.");
98 return;
99 };
100
101 match command {
102 Command::SetLog {
103 level,
104 output_target,
105 } => {
106 let modify_output = |filter: &mut CupratedTracingFilter| {
107 if let Some(level) = level {
108 filter.level = level;
109 }
110 println!("NEW LOG FILTER: {filter}");
111 };
112
113 match output_target {
114 OutputTarget::File => logging::modify_file_output(modify_output),
115 OutputTarget::Stdout => logging::modify_stdout_output(modify_output),
116 }
117 }
118 Command::Status => {
119 let context = context_service.blockchain_context();
120
121 let uptime = statics::START_INSTANT.elapsed().unwrap_or_default();
122
123 let (h, m, s) = secs_to_hms(uptime.as_secs());
124 let height = context.chain_height;
125 let top_hash = hex::encode(context.top_hash);
126
127 println!("STATUS:\n uptime: {h}h {m}m {s}s,\n height: {height},\n top_hash: {top_hash}");
128 }
129 Command::FastSyncStopHeight => {
130 let stop_height = cuprate_fast_sync::fast_sync_stop_height();
131
132 println!("{stop_height}");
133 }
134 }
135 }
136}