1use crate::{
2 ClientError, ClientOptions, ClientState, ConnectionState, DiffReviewEvent, DiffScope,
3 ReconnectPolicy, RemoteError, RepositoryAction, platform,
4 protocol::{
5 client::{ClientCommand, LocalClientTransport},
6 server::ServerEvent,
7 shared::{Event, LIVE_PROTOCOL_VERSION, RemoteErrorCode},
8 },
9 transport::{ClientMessageTransport, Decoded, Transport},
10};
11#[cfg(feature = "websocket")]
12use crate::{ConnectionHeader, transport::Reconnecting};
13use async_channel::{Receiver, Sender, unbounded};
14use futures_util::FutureExt;
15use std::{sync::Arc, time::Duration};
16use tokio::sync::{oneshot, watch};
17
18const IDLE_TIMEOUT: Duration = Duration::from_secs(45);
19
20type Reply = oneshot::Sender<Result<(), ClientError>>;
21type Ready = oneshot::Receiver<Result<(), ClientError>>;
22
23#[derive(Clone)]
24pub struct DiffClient {
25 commands: Sender<Command>,
26 state: watch::Receiver<Arc<ClientState>>,
27}
28
29pub struct ClientSubscription {
30 state: watch::Receiver<Arc<ClientState>>,
31}
32
33impl ClientSubscription {
34 #[must_use]
35 pub fn latest(&self) -> Arc<ClientState> {
36 self.state.borrow().clone()
37 }
38
39 pub async fn changed(&mut self) -> Result<Arc<ClientState>, ClientError> {
40 self.state
41 .changed()
42 .await
43 .map_err(|_| ClientError::Disconnected)?;
44 Ok(self.state.borrow_and_update().clone())
45 }
46
47 pub async fn wait_until(
54 &mut self,
55 timeout: Duration,
56 mut predicate: impl FnMut(&ClientState) -> bool,
57 ) -> Result<Arc<ClientState>, ClientError> {
58 let deadline = platform::sleep(timeout).fuse();
59 futures_util::pin_mut!(deadline);
60 loop {
61 let state = self.latest();
62 if predicate(&state) {
63 return Ok(state);
64 }
65 let changed = self.changed().fuse();
66 futures_util::pin_mut!(changed);
67 futures_util::select! {
68 state = changed => { state?; }
69 () = deadline => return Err(ClientError::Disconnected),
70 }
71 }
72 }
73}
74
75impl DiffClient {
76 #[cfg(feature = "websocket")]
77 pub async fn connect(url: &str, options: ClientOptions) -> Result<Self, ClientError> {
78 Self::connect_with_headers(url, options, Vec::new()).await
79 }
80
81 #[cfg(feature = "websocket")]
82 pub async fn connect_with_headers(
83 url: &str,
84 options: ClientOptions,
85 headers: Vec<ConnectionHeader>,
86 ) -> Result<Self, ClientError> {
87 let transport = Reconnecting::connect(url, headers).await?;
88 Self::start(transport, options).await
89 }
90
91 pub async fn from_transport(
92 transport: LocalClientTransport,
93 options: ClientOptions,
94 ) -> Result<Self, ClientError> {
95 Self::start(transport, options).await
96 }
97
98 pub async fn from_messages(
99 transport: impl ClientMessageTransport,
100 options: ClientOptions,
101 ) -> Result<Self, ClientError> {
102 Self::start(Decoded::new(transport), options).await
103 }
104
105 #[must_use]
106 pub fn spawn(transport: impl ClientMessageTransport, options: ClientOptions) -> Self {
107 Self::start_worker(Decoded::new(transport), options).0
108 }
109
110 async fn start(transport: impl Transport, options: ClientOptions) -> Result<Self, ClientError> {
111 let (client, ready) = Self::start_worker(transport, options);
112 ready.await.map_err(|_| ClientError::Disconnected)??;
113 Ok(client)
114 }
115
116 fn start_worker(transport: impl Transport, options: ClientOptions) -> (Self, Ready) {
117 let (commands, rx) = unbounded();
118 let (state_tx, state) = watch::channel(Arc::new(ClientState::default()));
119 let (ready_tx, ready_rx) = oneshot::channel();
120 platform::spawn(async move {
121 Worker {
122 commands: rx,
123 state_tx,
124 options,
125 client: ClientState::default(),
126 handshake: Handshake::default(),
127 pending: None,
128 closed: false,
129 }
130 .run(transport, ready_tx)
131 .await;
132 });
133 (Self { commands, state }, ready_rx)
134 }
135
136 #[must_use]
137 pub fn state(&self) -> Arc<ClientState> {
138 self.state.borrow().clone()
139 }
140
141 #[must_use]
142 pub fn subscribe(&self) -> ClientSubscription {
143 ClientSubscription {
144 state: self.state.clone(),
145 }
146 }
147
148 pub async fn set_scope(&self, scope: DiffScope) -> Result<(), ClientError> {
149 self.request(ClientCommand::SetScope(scope)).await
150 }
151
152 pub async fn apply(&self, action: RepositoryAction) -> Result<(), ClientError> {
153 self.request(ClientCommand::Apply(action)).await
154 }
155
156 pub async fn refresh(&self) -> Result<(), ClientError> {
157 self.request(ClientCommand::Refresh).await
158 }
159
160 pub async fn handle(&self, event: DiffReviewEvent) -> Result<(), ClientError> {
161 match self.dispatch(event)? {
162 Some(reply) => reply.await.map_err(|_| ClientError::Disconnected)?,
163 None => Ok(()),
164 }
165 }
166
167 pub fn dispatch(
168 &self,
169 event: DiffReviewEvent,
170 ) -> Result<Option<oneshot::Receiver<Result<(), ClientError>>>, ClientError> {
171 let request = match event {
172 DiffReviewEvent::RepositoryAction(action) => ClientCommand::Apply(action),
173 DiffReviewEvent::SetScope(scope) => ClientCommand::SetScope(scope),
174 DiffReviewEvent::Refresh => ClientCommand::Refresh,
175 DiffReviewEvent::SubmitReview(submission) => ClientCommand::Submit(submission),
176 DiffReviewEvent::Cancel => ClientCommand::Cancel,
177 DiffReviewEvent::CopyFormattedReview(_) => return Ok(None),
178 };
179 let (reply, receiver) = oneshot::channel();
180 self.commands
181 .try_send(Command::Request(request, reply))
182 .map_err(|_| ClientError::Disconnected)?;
183 Ok(Some(receiver))
184 }
185
186 pub async fn close(self) -> Result<(), ClientError> {
187 self.command(Command::Close).await
188 }
189
190 async fn request(&self, request: ClientCommand) -> Result<(), ClientError> {
191 self.command(|reply| Command::Request(request, reply)).await
192 }
193
194 async fn command(&self, build: impl FnOnce(Reply) -> Command) -> Result<(), ClientError> {
195 let (reply, rx) = oneshot::channel();
196 self.commands
197 .send(build(reply))
198 .await
199 .map_err(|_| ClientError::Disconnected)?;
200 rx.await.map_err(|_| ClientError::Disconnected)?
201 }
202}
203
204enum Command {
205 Request(ClientCommand, Reply),
206 Close(Reply),
207}
208
209enum ConnectionInput {
210 Command(Option<Command>),
211 Event(Result<ServerEvent, ClientError>),
212 Idle,
213}
214
215struct Pending {
216 reply: Reply,
217 outcome_unknown_on_disconnect: bool,
218}
219
220struct Worker {
221 commands: Receiver<Command>,
222 state_tx: watch::Sender<Arc<ClientState>>,
223 options: ClientOptions,
224 client: ClientState,
225 handshake: Handshake,
226 pending: Option<Pending>,
227 closed: bool,
228}
229
230#[derive(Default)]
231struct Handshake {
232 initialized: bool,
233}
234
235impl Handshake {
236 fn reset(&mut self) {
237 self.initialized = false;
238 }
239
240 fn check(&mut self, event: &ServerEvent) -> Result<(), ClientError> {
241 match event {
242 Event::Initialize {
243 protocol_version, ..
244 } => {
245 if *protocol_version != LIVE_PROTOCOL_VERSION {
246 return Err(ClientError::Remote(RemoteError::new(
247 RemoteErrorCode::UnsupportedVersion,
248 "unsupported live protocol version",
249 )));
250 }
251 if std::mem::replace(&mut self.initialized, true) {
252 return Err(ClientError::Protocol("repeated initialization".to_owned()));
253 }
254 Ok(())
255 }
256 Event::Error(_) => Ok(()),
257 _ if !self.initialized => {
258 Err(ClientError::Protocol("expected initialization".to_owned()))
259 }
260 _ => Ok(()),
261 }
262 }
263}
264
265impl Worker {
266 fn publish(&self) {
267 self.state_tx.send_replace(Arc::new(self.client.clone()));
268 }
269
270 async fn run<T: Transport>(mut self, mut transport: T, ready: Reply) {
271 let mut ready = Some(ready);
272 loop {
273 let result = self.connection(&mut transport, &mut ready).await;
274 transport.close().await;
275 self.abandon_request();
276 let retry = !self.closed
277 && ready.is_none()
278 && transport.reconnects()
279 && matches!(self.options.reconnect, ReconnectPolicy::Retry)
280 && !is_terminal(&result);
281 if let Some(ready) = ready.take() {
282 let _ = ready.send(result.clone());
283 }
284 if !retry {
285 self.client.set_connection(ConnectionState::Failed(
286 result.err().unwrap_or(ClientError::Disconnected),
287 ));
288 self.publish();
289 return;
290 }
291 self.client.set_connection(ConnectionState::Connecting);
292 self.publish();
293 if !self.reconnect(&mut transport).await {
294 break;
295 }
296 }
297 self.client
298 .set_connection(ConnectionState::Failed(ClientError::Disconnected));
299 self.publish();
300 }
301
302 async fn connection<T: Transport>(
303 &mut self,
304 transport: &mut T,
305 ready: &mut Option<Reply>,
306 ) -> Result<(), ClientError> {
307 self.handshake.reset();
308 transport
309 .send(ClientCommand::Initialize {
310 protocol_version: LIVE_PROTOCOL_VERSION,
311 scope: self.options.scope,
312 })
313 .await
314 .map_err(|_| ClientError::Disconnected)?;
315 let idle = platform::sleep(IDLE_TIMEOUT).fuse();
316 futures_util::pin_mut!(idle);
317 loop {
318 let input = {
319 let command = async {
320 if self.pending.is_none() {
321 self.commands.recv().await.ok()
322 } else {
323 futures_util::future::pending().await
324 }
325 }
326 .fuse();
327 let event = transport.recv().fuse();
328 futures_util::pin_mut!(command, event);
329 futures_util::select! {
330 command = command => ConnectionInput::Command(command),
331 event = event => ConnectionInput::Event(event),
332 () = idle => ConnectionInput::Idle,
333 }
334 };
335 match input {
336 ConnectionInput::Command(None) => {
337 self.closed = true;
338 return Ok(());
339 }
340 ConnectionInput::Command(Some(Command::Close(reply))) => {
341 self.closed = true;
342 let _ = reply.send(Ok(()));
343 return Ok(());
344 }
345 ConnectionInput::Command(Some(Command::Request(request, reply))) => {
346 self.request(transport, request, reply).await?;
347 }
348 ConnectionInput::Event(event) => {
349 self.event(&event?)?;
350 if self.handshake.initialized
351 && let Some(ready) = ready.take()
352 {
353 let _ = ready.send(Ok(()));
354 }
355 }
356 ConnectionInput::Idle => return Err(ClientError::Disconnected),
357 }
358 idle.set(platform::sleep(IDLE_TIMEOUT).fuse());
359 }
360 }
361
362 fn event(&mut self, event: &ServerEvent) -> Result<(), ClientError> {
363 self.handshake.check(event)?;
364 if let Event::RequestResult(result) = event {
365 if let Some(pending) = self.pending.take() {
366 let _ = pending
367 .reply
368 .send(result.clone().map_err(ClientError::Remote));
369 }
370 return Ok(());
371 }
372 let next = self.client.clone().apply(event);
373 if next != self.client {
374 self.client = next;
375 self.publish();
376 }
377 if let Event::Error(error) = event {
378 return Err(ClientError::Remote(error.clone()));
379 }
380 Ok(())
381 }
382
383 async fn request<T: Transport>(
384 &mut self,
385 transport: &mut T,
386 request: ClientCommand,
387 reply: Reply,
388 ) -> Result<(), ClientError> {
389 if !self.handshake.initialized {
390 let _ = reply.send(Err(ClientError::Disconnected));
391 return Ok(());
392 }
393 let outcome_unknown_on_disconnect = matches!(
394 request,
395 ClientCommand::Apply(_) | ClientCommand::Submit(_) | ClientCommand::Cancel
396 );
397 if transport.send(request).await.is_err() {
398 let _ = reply.send(Err(ClientError::Disconnected));
399 return Err(ClientError::Disconnected);
400 }
401 self.pending = Some(Pending {
402 reply,
403 outcome_unknown_on_disconnect,
404 });
405 Ok(())
406 }
407
408 fn abandon_request(&mut self) {
409 if let Some(pending) = self.pending.take() {
410 let _ = pending
411 .reply
412 .send(Err(if pending.outcome_unknown_on_disconnect {
413 ClientError::OutcomeUnknown
414 } else {
415 ClientError::Disconnected
416 }));
417 }
418 }
419
420 async fn reconnect<T: Transport>(&mut self, transport: &mut T) -> bool {
421 let mut delay = 250;
422 loop {
423 let wait = delay;
424 let connecting = async {
425 platform::sleep(platform::reconnect_delay(wait)).await;
426 transport.reconnect().await
427 }
428 .fuse();
429 let command = self.commands.recv().fuse();
430 futures_util::pin_mut!(connecting, command);
431 futures_util::select! {
432 next = connecting => match next {
433 Ok(()) => return true,
434 Err(_) => delay = (delay * 2).min(5000),
435 },
436 command = command => match command {
437 Ok(Command::Request(_, reply)) => { let _ = reply.send(Err(ClientError::Disconnected)); }
438 Ok(Command::Close(reply)) => { self.closed = true; let _ = reply.send(Ok(())); return false; }
439 Err(_) => { self.closed = true; return false; }
440 },
441 }
442 }
443 }
444}
445
446fn is_terminal(result: &Result<(), ClientError>) -> bool {
447 matches!(
448 result,
449 Err(ClientError::Protocol(_)
450 | ClientError::Remote(RemoteError {
451 code: RemoteErrorCode::UnsupportedVersion | RemoteErrorCode::Protocol,
452 ..
453 }))
454 )
455}