1use anyhow::Result;
2use clap::Parser;
3use console::style;
4use std::net::SocketAddr;
5use std::str::FromStr;
6use std::time::Duration;
7use tokio::time;
8use tracing::{debug, info};
9
10use crate::client::ManagementResponse;
11use crate::utils::event_display::{display_events, display_single_event, EventDisplayOptions};
12use crate::{error::CliError, output::formatters::EventSubscription, CommandContext};
13use theater::id::TheaterId;
14
15#[derive(Debug, Parser)]
16pub struct SubscribeArgs {
17 #[arg(required = true)]
19 pub actor_id: String,
20
21 #[arg(short, long)]
23 pub address: Option<SocketAddr>,
24
25 #[arg(short, long)]
27 pub event_type: Option<String>,
28
29 #[arg(short, long)]
31 pub detailed: bool,
32
33 #[arg(short, long, default_value = "0")]
35 pub limit: usize,
36
37 #[arg(short, long, default_value = "0")]
39 pub timeout: u64,
40
41 #[arg(short, long, default_value = "compact")]
43 pub format: String,
44
45 #[arg(short = 'H', long)]
47 pub history: bool,
48
49 #[arg(long, default_value = "0")]
51 pub history_limit: usize,
52}
53
54pub async fn execute_async(args: &SubscribeArgs, ctx: &CommandContext) -> Result<(), CliError> {
56 let actor_id_str = if args.actor_id == "-" {
58 let mut input = String::new();
59 std::io::stdin().read_line(&mut input).map_err(|e| {
60 CliError::invalid_input("actor_id", "-", format!("Failed to read from stdin: {}", e))
61 })?;
62 input.trim().to_string()
63 } else {
64 args.actor_id.clone()
65 };
66
67 debug!("Subscribing to events for actor: {}", actor_id_str);
68
69 let actor_id = TheaterId::from_str(&actor_id_str)
71 .map_err(|_| CliError::invalid_actor_id(&actor_id_str))?;
72
73 let address = ctx.server_address(args.address);
75 debug!("Connecting to server at: {}", address);
76
77 let client = ctx.create_client();
79 client
80 .connect()
81 .await
82 .map_err(|e| CliError::connection_failed(address, e))?;
83
84 let display_options = EventDisplayOptions {
86 format: args.format.clone(),
87 detailed: args.detailed,
88 json: ctx.json,
89 };
90
91 let mut events_count = 0;
93 let mut subscription_info = EventSubscription {
94 actor_id: actor_id.clone(),
95 address: address.to_string(),
96 event_type_filter: args.event_type.clone(),
97 limit: args.limit,
98 timeout: args.timeout,
99 format: args.format.clone(),
100 show_history: args.history,
101 history_limit: args.history_limit,
102 detailed: args.detailed,
103 events_received: 0,
104 subscription_id: None,
105 is_active: false,
106 };
107
108 if !ctx.json {
110 ctx.output.output(&subscription_info, None)?;
111 }
112
113 if args.history {
115 let mut events = client
116 .get_actor_events(&actor_id.to_string())
117 .await
118 .map_err(|e| {
119 CliError::actor_not_found(format!(
120 "Failed to get events for actor {}: {}",
121 actor_id, e
122 ))
123 })?;
124
125 if let Some(filter) = &args.event_type {
127 events.retain(|e| e.event_type.contains(filter));
128 }
129
130 if args.history_limit > 0 && events.len() > args.history_limit {
132 let skip_count = events.len() - args.history_limit;
133 events = events.into_iter().skip(skip_count).collect();
134 }
135
136 if !events.is_empty() {
138 events_count = display_events(&events, Some(&actor_id), &display_options, 0)
139 .map_err(|e| CliError::invalid_input("event_display", "events", e.to_string()))?;
140 }
141 }
142
143 let event_stream = client
145 .subscribe_to_events(&actor_id.to_string())
146 .await
147 .map_err(|e| {
148 CliError::actor_not_found(format!("Failed to subscribe to actor {}: {}", actor_id, e))
149 })?;
150
151 let subscription_id = event_stream.subscription_id();
152 subscription_info.subscription_id = Some(subscription_id.to_string());
153 subscription_info.is_active = true;
154
155 info!(
156 "Subscribed to actor events with subscription ID: {}",
157 subscription_id
158 );
159
160 if !args.history || events_count == 0 {
162 if display_options.format == "compact" && !display_options.json {
163 println!(
164 "{:<12} {:<12} {:<25} {}",
165 "HASH", "PARENT", "EVENT TYPE", "DESCRIPTION"
166 );
167 println!("{}", style("─".repeat(100)).dim());
168 }
169
170 if !args.history && !ctx.json {
171 println!("{} Waiting for events...\n", style("⏳").yellow().bold());
172 }
173 }
174
175 let mut last_event_time = std::time::Instant::now();
176
177 loop {
179 if args.timeout > 0 {
181 let timeout_duration = Duration::from_secs(args.timeout);
182 if last_event_time.elapsed() > timeout_duration {
183 if !ctx.json {
184 println!(
185 "\n{} No events received for {} seconds, exiting.",
186 style("⏱").yellow().bold(),
187 args.timeout
188 );
189 }
190 break;
191 }
192 }
193
194 let response = match time::timeout(Duration::from_secs(1), client.next_response()).await {
196 Ok(result) => match result {
197 Ok(response) => response,
198 Err(e) => {
199 if !e.to_string().contains("Connection closed") {
200 return Err(CliError::connection_failed(address, e));
201 }
202 continue;
203 }
204 },
205 Err(_) => continue, };
207
208 match response {
210 ManagementResponse::ActorEvent { event } => {
211 if let Some(filter) = &args.event_type {
213 if !event.event_type.contains(filter) {
214 continue;
215 }
216 }
217
218 last_event_time = std::time::Instant::now();
219 events_count += 1;
220 subscription_info.events_received = events_count;
221
222 display_single_event(
224 &event,
225 if display_options.json {
226 "json"
227 } else {
228 &display_options.format
229 },
230 )
231 .map_err(|e| CliError::invalid_input("event_display", "event", e.to_string()))?;
232
233 if args.limit > 0 && events_count >= args.limit {
235 if !ctx.json {
236 println!(
237 "\n{} Reached event limit ({}), exiting.",
238 style("ℹ").blue().bold(),
239 args.limit
240 );
241 }
242 break;
243 }
244 }
245 ManagementResponse::ActorError { error } => {
246 if ctx.json {
247 let output = serde_json::json!({
248 "actor_id": actor_id.to_string(),
249 "error": error,
250 });
251 println!(
252 "{}",
253 serde_json::to_string_pretty(&output).map_err(|e| {
254 CliError::invalid_input("json_output", "error", e.to_string())
255 })?
256 );
257 } else {
258 println!("{} Actor error: {}", style("ERROR").bold().red(), error);
259 }
260 }
261 ManagementResponse::Error { error } => {
262 return Err(CliError::actor_not_found(format!(
263 "Server error: {:?}",
264 error
265 )));
266 }
267 _ => {
268 debug!("Received unexpected response: {:?}", response);
269 }
270 }
271 }
272
273 if let Err(e) = client
275 .unsubscribe_from_actor(&actor_id.to_string(), subscription_id)
276 .await
277 {
278 debug!("Failed to unsubscribe: {}", e);
279 }
280
281 subscription_info.is_active = false;
282
283 if !ctx.json && !args.history {
285 subscription_info.events_received = events_count;
286 }
288
289 Ok(())
290}