theater_cli/client/
connection.rs1
2use bytes::Bytes;
3use futures::sink::SinkExt;
4use futures::stream::StreamExt;
5use std::net::SocketAddr;
6use tokio::net::TcpStream;
7use tokio::time::timeout;
8use tokio_util::codec::Framed;
9use tracing::{debug, error, info, warn};
10
11use theater_server::FragmentingCodec;
12
13use crate::config::Config;
14use crate::error::{CliError, CliResult};
15
16pub use theater_server::{ManagementCommand, ManagementResponse};
17
18#[derive(Debug)]
20pub struct Connection {
21 address: SocketAddr,
22 config: Config,
23 framed: Option<Framed<TcpStream, FragmentingCodec>>,
24 last_error: Option<String>,
25}
26
27impl Connection {
28 pub fn new(address: SocketAddr, config: Config) -> Self {
29 Self {
30 address,
31 config,
32 framed: None,
33 last_error: None,
34 }
35 }
36
37 pub async fn ensure_connected(&mut self) -> CliResult<()> {
39 if self.framed.is_none() {
40 self.connect().await?;
41 }
42
43 if !self.test_connection().await {
46 info!("Connection test failed, attempting to reconnect");
47 self.framed = None;
48 self.connect().await?;
49 }
50
51 Ok(())
52 }
53
54 async fn connect(&mut self) -> CliResult<()> {
56 info!("Connecting to Theater server at {}", self.address);
57
58 let connect_future = TcpStream::connect(self.address);
59 let socket = timeout(self.config.server.timeout, connect_future)
60 .await
61 .map_err(|_| CliError::ConnectionTimeout {
62 timeout: self.config.server.timeout.as_secs(),
63 })?
64 .map_err(|e| CliError::connection_failed(self.address, e))?;
65
66 let codec = FragmentingCodec::new();
68 self.framed = Some(Framed::new(socket, codec));
69 self.last_error = None;
70
71 info!("Successfully connected to Theater server with fragmentation support");
72 Ok(())
73 }
74
75 async fn test_connection(&mut self) -> bool {
77 if let Some(ref mut framed) = self.framed {
78 match framed.get_ref().peer_addr() {
81 Ok(_) => true,
82 Err(_) => false,
83 }
84 } else {
85 false
86 }
87 }
88
89 pub async fn send_command(
91 &mut self,
92 command: ManagementCommand,
93 ) -> CliResult<ManagementResponse> {
94 self.ensure_connected().await?;
95
96 let framed = self.framed.as_mut().unwrap();
97
98 debug!("Sending command: {:?}", command);
100 let command_bytes = serde_json::to_vec(&command).map_err(CliError::Serialization)?;
101
102 let send_future = framed.send(Bytes::from(command_bytes));
104 timeout(self.config.server.timeout, send_future)
105 .await
106 .map_err(|_| CliError::ConnectionTimeout {
107 timeout: self.config.server.timeout.as_secs(),
108 })?
109 .map_err(|e| {
110 error!("Failed to send command: {}", e);
111 CliError::ConnectionLost
112 })?;
113
114 debug!("Command sent, waiting for response");
115
116 let receive_future = framed.next();
118 let response_bytes = timeout(self.config.server.timeout, receive_future)
119 .await
120 .map_err(|_| CliError::ConnectionTimeout {
121 timeout: self.config.server.timeout.as_secs(),
122 })?;
123
124 match response_bytes {
125 Some(Ok(bytes)) => {
126 let response: ManagementResponse =
127 serde_json::from_slice(&bytes).map_err(|e| CliError::ProtocolError {
128 reason: format!("Failed to deserialize response: {}", e),
129 })?;
130 debug!("Received response: {:?}", response);
131 Ok(response)
132 }
133 Some(Err(e)) => {
134 error!("Error receiving response: {}", e);
135 self.framed = None; Err(CliError::ConnectionLost)
137 }
138 None => {
139 warn!("Connection closed by server");
140 self.framed = None;
141 Err(CliError::ConnectionLost)
142 }
143 }
144 }
145
146 pub async fn send_command_no_response(&mut self, command: ManagementCommand) -> CliResult<()> {
148 self.ensure_connected().await?;
149
150 let framed = self.framed.as_mut().unwrap();
151
152 debug!("Sending command (no response expected): {:?}", command);
154 let command_bytes = serde_json::to_vec(&command).map_err(CliError::Serialization)?;
155
156 let send_future = framed.send(Bytes::from(command_bytes));
158 timeout(self.config.server.timeout, send_future)
159 .await
160 .map_err(|_| CliError::ConnectionTimeout {
161 timeout: self.config.server.timeout.as_secs(),
162 })?
163 .map_err(|e| {
164 error!("Failed to send command: {}", e);
165 CliError::ConnectionLost
166 })?;
167
168 debug!("Command sent (no response expected)");
169 Ok(())
170 }
171
172 pub async fn next_response(&mut self) -> CliResult<Option<ManagementResponse>> {
174 if let Some(ref mut framed) = self.framed {
175 let receive_future = framed.next();
176 let response_bytes = timeout(self.config.server.timeout, receive_future)
177 .await
178 .map_err(|_| CliError::ConnectionTimeout {
179 timeout: self.config.server.timeout.as_secs(),
180 })?;
181
182 match response_bytes {
183 Some(Ok(bytes)) => {
184 let response: ManagementResponse =
185 serde_json::from_slice(&bytes).map_err(|e| CliError::ProtocolError {
186 reason: format!("Failed to deserialize response: {}", e),
187 })?;
188 debug!("Received streaming response: {:?}", response);
189 Ok(Some(response))
190 }
191 Some(Err(e)) => {
192 error!("Error receiving streaming response: {}", e);
193 self.framed = None;
194 Err(CliError::ConnectionLost)
195 }
196 None => {
197 debug!("Stream ended");
198 Ok(None)
199 }
200 }
201 } else {
202 Err(CliError::ConnectionLost)
203 }
204 }
205
206 pub fn is_connected(&self) -> bool {
208 self.framed.is_some()
209 }
210
211 pub fn address(&self) -> SocketAddr {
213 self.address
214 }
215
216 pub fn last_error(&self) -> Option<&str> {
218 self.last_error.as_deref()
219 }
220
221 pub async fn close(&mut self) {
223 if let Some(mut framed) = self.framed.take() {
224 let _ = framed.close().await;
225 info!("Connection closed");
226 }
227 }
228}
229
230impl Drop for Connection {
231 fn drop(&mut self) {
232 if self.framed.is_some() {
233 debug!("Connection dropped without explicit close");
234 }
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241
242 #[test]
243 fn test_connection_creation() {
244 let config = Config::default();
245 let addr = "127.0.0.1:9000".parse().unwrap();
246 let conn = Connection::new(addr, config);
247
248 assert_eq!(conn.address(), addr);
249 assert!(!conn.is_connected());
250 assert!(conn.last_error().is_none());
251 }
252}