mi6_cli/commands/
watch.rs1use std::io::{self, BufReader, Read, Write};
2use std::time::Duration;
3
4use anyhow::{Context, Result};
5
6use mi6_core::{EventQuery, Order, Storage};
7use termion::async_stdin;
8use termion::raw::IntoRawMode;
9use termion::terminal_size;
10
11use crate::display::{calculate_details_width, print_event_row, print_table_header};
12
13const DEFAULT_POLL_MS: u64 = 500;
15
16const DEFAULT_TERMINAL_HEIGHT: u16 = 20;
18
19#[derive(Debug, Clone)]
24pub struct WatchOptions {
25 pub session: Option<String>,
27 pub event_type: Option<String>,
29 pub permission_mode: Option<String>,
31 pub framework: Option<String>,
33 pub poll_ms: u64,
35}
36
37impl Default for WatchOptions {
38 fn default() -> Self {
39 Self {
40 session: None,
41 event_type: None,
42 permission_mode: None,
43 framework: None,
44 poll_ms: DEFAULT_POLL_MS,
45 }
46 }
47}
48
49impl WatchOptions {
50 pub fn new() -> Self {
52 Self::default()
53 }
54
55 pub fn session(mut self, session: impl Into<Option<String>>) -> Self {
57 self.session = session.into();
58 self
59 }
60
61 pub fn event_type(mut self, event_type: impl Into<Option<String>>) -> Self {
63 self.event_type = event_type.into();
64 self
65 }
66
67 pub fn permission_mode(mut self, permission_mode: impl Into<Option<String>>) -> Self {
69 self.permission_mode = permission_mode.into();
70 self
71 }
72
73 pub fn framework(mut self, framework: impl Into<Option<String>>) -> Self {
75 self.framework = framework.into();
76 self
77 }
78
79 pub fn poll_ms(mut self, poll_ms: u64) -> Self {
81 self.poll_ms = poll_ms;
82 self
83 }
84}
85
86const MAX_INITIAL_LINES: usize = 50;
88
89pub fn run_watch<S: Storage>(storage: &S, options: WatchOptions) -> Result<()> {
91 let show_app = true;
93 let details_width = calculate_details_width(show_app);
94
95 print_table_header(show_app, details_width);
97 io::stdout().flush().context("failed to flush stdout")?;
98
99 let term_height = terminal_size()
101 .map(|(_, h)| h)
102 .unwrap_or(DEFAULT_TERMINAL_HEIGHT) as usize;
103 let initial_lines = term_height.saturating_sub(5).min(MAX_INITIAL_LINES);
104 let mut last_event_id = 0i64;
105
106 if initial_lines > 0 {
107 let mut initial_query = EventQuery::new()
109 .with_session(options.session.clone())
110 .with_event_type(options.event_type.clone())
111 .with_permission_mode(options.permission_mode.clone())
112 .with_direction(Order::Desc)
113 .with_limit(initial_lines);
114 if let Some(ref framework) = options.framework {
115 initial_query = initial_query.with_framework(framework);
116 }
117
118 let mut initial_events = storage
119 .query(&initial_query)
120 .context("failed to query initial events")?;
121
122 for event in &initial_events {
124 if let Some(id) = event.id {
125 last_event_id = last_event_id.max(id);
126 }
127 }
128
129 initial_events.reverse();
131
132 for event in &initial_events {
133 print_event_row(event, show_app, details_width, false);
134 }
135 io::stdout().flush().context("failed to flush stdout")?;
136 }
137
138 let _raw = io::stdout()
140 .into_raw_mode()
141 .context("failed to enable raw mode")?;
142
143 let mut stdin = BufReader::new(async_stdin()).bytes();
145
146 loop {
147 if let Some(Ok(b)) = stdin.next() {
149 if b == b'q' || b == 0x03 {
151 return Ok(());
152 }
153 }
154
155 let mut watch_query = EventQuery::new()
158 .order_by_id()
159 .with_session(options.session.clone())
160 .with_event_type(options.event_type.clone())
161 .with_permission_mode(options.permission_mode.clone())
162 .with_after_id(last_event_id)
163 .with_limit(100);
164 if let Some(ref framework) = options.framework {
165 watch_query = watch_query.with_framework(framework);
166 }
167
168 let new_events = storage
169 .query(&watch_query)
170 .context("failed to query events")?;
171
172 for event in &new_events {
174 if let Some(id) = event.id {
175 last_event_id = last_event_id.max(id);
176 }
177 print_event_row(event, show_app, details_width, true);
178 io::stdout().flush().context("failed to flush stdout")?;
179 }
180
181 std::thread::sleep(Duration::from_millis(options.poll_ms));
183 }
184}