Implement call cancellation

This commit is contained in:
Antonio Scandurra 2022-10-06 15:12:27 +02:00
parent 2e84fc6737
commit 4cb306fbf3
6 changed files with 176 additions and 7 deletions

View File

@ -52,7 +52,7 @@ impl ActiveCall {
incoming_call: watch::channel(), incoming_call: watch::channel(),
_subscriptions: vec![ _subscriptions: vec![
client.add_request_handler(cx.handle(), Self::handle_incoming_call), client.add_request_handler(cx.handle(), Self::handle_incoming_call),
client.add_message_handler(cx.handle(), Self::handle_cancel_call), client.add_message_handler(cx.handle(), Self::handle_call_canceled),
], ],
client, client,
user_store, user_store,
@ -87,9 +87,9 @@ impl ActiveCall {
Ok(proto::Ack {}) Ok(proto::Ack {})
} }
async fn handle_cancel_call( async fn handle_call_canceled(
this: ModelHandle<Self>, this: ModelHandle<Self>,
_: TypedEnvelope<proto::CancelCall>, _: TypedEnvelope<proto::CallCanceled>,
_: Arc<Client>, _: Arc<Client>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<()> { ) -> Result<()> {
@ -140,6 +140,20 @@ impl ActiveCall {
}) })
} }
pub fn cancel_invite(
&mut self,
recipient_user_id: u64,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
let client = self.client.clone();
cx.foreground().spawn(async move {
client
.request(proto::CancelCall { recipient_user_id })
.await?;
anyhow::Ok(())
})
}
pub fn incoming(&self) -> watch::Receiver<Option<IncomingCall>> { pub fn incoming(&self) -> watch::Receiver<Option<IncomingCall>> {
self.incoming_call.1.clone() self.incoming_call.1.clone()
} }

View File

@ -401,6 +401,88 @@ async fn test_leaving_room_on_disconnection(
); );
} }
#[gpui::test(iterations = 10)]
async fn test_calls_on_multiple_connections(
deterministic: Arc<Deterministic>,
cx_a: &mut TestAppContext,
cx_b1: &mut TestAppContext,
cx_b2: &mut TestAppContext,
) {
deterministic.forbid_parking();
let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
let client_a = server.create_client(cx_a, "user_a").await;
let client_b1 = server.create_client(cx_b1, "user_b").await;
let _client_b2 = server.create_client(cx_b2, "user_b").await;
server
.make_contacts(&mut [(&client_a, cx_a), (&client_b1, cx_b1)])
.await;
let active_call_a = cx_a.read(ActiveCall::global);
let active_call_b1 = cx_b1.read(ActiveCall::global);
let active_call_b2 = cx_b2.read(ActiveCall::global);
let mut incoming_call_b1 = active_call_b1.read_with(cx_b1, |call, _| call.incoming());
let mut incoming_call_b2 = active_call_b2.read_with(cx_b2, |call, _| call.incoming());
assert!(incoming_call_b1.next().await.unwrap().is_none());
assert!(incoming_call_b2.next().await.unwrap().is_none());
// Call user B from client A, ensuring both clients for user B ring.
active_call_a
.update(cx_a, |call, cx| {
call.invite(client_b1.user_id().unwrap(), None, cx)
})
.await
.unwrap();
assert!(incoming_call_b1.next().await.unwrap().is_some());
assert!(incoming_call_b2.next().await.unwrap().is_some());
// User B declines the call on one of the two connections, causing both connections
// to stop ringing.
active_call_b2.update(cx_b2, |call, _| call.decline_incoming().unwrap());
assert!(incoming_call_b1.next().await.unwrap().is_none());
assert!(incoming_call_b2.next().await.unwrap().is_none());
// Call user B again from client A.
active_call_a
.update(cx_a, |call, cx| {
call.invite(client_b1.user_id().unwrap(), None, cx)
})
.await
.unwrap();
assert!(incoming_call_b1.next().await.unwrap().is_some());
assert!(incoming_call_b2.next().await.unwrap().is_some());
// User B accepts the call on one of the two connections, causing both connections
// to stop ringing.
active_call_b2
.update(cx_b2, |call, cx| call.accept_incoming(cx))
.await
.unwrap();
assert!(incoming_call_b1.next().await.unwrap().is_none());
assert!(incoming_call_b2.next().await.unwrap().is_none());
// User B hangs up, and user A calls them again.
active_call_b2.update(cx_b2, |call, cx| call.hang_up(cx).unwrap());
deterministic.run_until_parked();
active_call_a
.update(cx_a, |call, cx| {
call.invite(client_b1.user_id().unwrap(), None, cx)
})
.await
.unwrap();
assert!(incoming_call_b1.next().await.unwrap().is_some());
assert!(incoming_call_b2.next().await.unwrap().is_some());
// User A cancels the call, causing both connections to stop ringing.
active_call_a
.update(cx_a, |call, cx| {
call.cancel_invite(client_b1.user_id().unwrap(), cx)
})
.await
.unwrap();
assert!(incoming_call_b1.next().await.unwrap().is_none());
assert!(incoming_call_b2.next().await.unwrap().is_none());
}
#[gpui::test(iterations = 10)] #[gpui::test(iterations = 10)]
async fn test_share_project( async fn test_share_project(
deterministic: Arc<Deterministic>, deterministic: Arc<Deterministic>,

View File

@ -150,6 +150,7 @@ impl Server {
.add_request_handler(Server::join_room) .add_request_handler(Server::join_room)
.add_message_handler(Server::leave_room) .add_message_handler(Server::leave_room)
.add_request_handler(Server::call) .add_request_handler(Server::call)
.add_request_handler(Server::cancel_call)
.add_message_handler(Server::decline_call) .add_message_handler(Server::decline_call)
.add_request_handler(Server::update_participant_location) .add_request_handler(Server::update_participant_location)
.add_request_handler(Server::share_project) .add_request_handler(Server::share_project)
@ -599,7 +600,7 @@ impl Server {
let (room, recipient_connection_ids) = store.join_room(room_id, request.sender_id)?; let (room, recipient_connection_ids) = store.join_room(room_id, request.sender_id)?;
for recipient_id in recipient_connection_ids { for recipient_id in recipient_connection_ids {
self.peer self.peer
.send(recipient_id, proto::CancelCall {}) .send(recipient_id, proto::CallCanceled {})
.trace_err(); .trace_err();
} }
response.send(proto::JoinRoomResponse { response.send(proto::JoinRoomResponse {
@ -715,6 +716,26 @@ impl Server {
Err(anyhow!("failed to ring call recipient"))? Err(anyhow!("failed to ring call recipient"))?
} }
async fn cancel_call(
self: Arc<Server>,
request: TypedEnvelope<proto::CancelCall>,
response: Response<proto::CancelCall>,
) -> Result<()> {
let mut store = self.store().await;
let (room, recipient_connection_ids) = store.cancel_call(
UserId::from_proto(request.payload.recipient_user_id),
request.sender_id,
)?;
for recipient_id in recipient_connection_ids {
self.peer
.send(recipient_id, proto::CallCanceled {})
.trace_err();
}
self.room_updated(room);
response.send(proto::Ack {})?;
Ok(())
}
async fn decline_call( async fn decline_call(
self: Arc<Server>, self: Arc<Server>,
message: TypedEnvelope<proto::DeclineCall>, message: TypedEnvelope<proto::DeclineCall>,
@ -723,7 +744,7 @@ impl Server {
let (room, recipient_connection_ids) = store.call_declined(message.sender_id)?; let (room, recipient_connection_ids) = store.call_declined(message.sender_id)?;
for recipient_id in recipient_connection_ids { for recipient_id in recipient_connection_ids {
self.peer self.peer
.send(recipient_id, proto::CancelCall {}) .send(recipient_id, proto::CallCanceled {})
.trace_err(); .trace_err();
} }
self.room_updated(room); self.room_updated(room);

View File

@ -585,6 +585,51 @@ impl Store {
Ok(room) Ok(room)
} }
pub fn cancel_call(
&mut self,
recipient_user_id: UserId,
canceller_connection_id: ConnectionId,
) -> Result<(&proto::Room, HashSet<ConnectionId>)> {
let canceller_user_id = self.user_id_for_connection(canceller_connection_id)?;
let canceller = self
.connected_users
.get(&canceller_user_id)
.ok_or_else(|| anyhow!("no such connection"))?;
let recipient = self
.connected_users
.get(&recipient_user_id)
.ok_or_else(|| anyhow!("no such connection"))?;
let canceller_active_call = canceller
.active_call
.as_ref()
.ok_or_else(|| anyhow!("no active call"))?;
let recipient_active_call = recipient
.active_call
.as_ref()
.ok_or_else(|| anyhow!("no active call for recipient"))?;
anyhow::ensure!(
canceller_active_call.room_id == recipient_active_call.room_id,
"users are on different calls"
);
anyhow::ensure!(
recipient_active_call.connection_id.is_none(),
"recipient has already answered"
);
let room_id = recipient_active_call.room_id;
let room = self
.rooms
.get_mut(&room_id)
.ok_or_else(|| anyhow!("no such room"))?;
room.pending_user_ids
.retain(|user_id| UserId::from_proto(*user_id) != recipient_user_id);
let recipient = self.connected_users.get_mut(&recipient_user_id).unwrap();
recipient.active_call.take();
Ok((room, recipient.connection_ids.clone()))
}
pub fn call_declined( pub fn call_declined(
&mut self, &mut self,
recipient_connection_id: ConnectionId, recipient_connection_id: ConnectionId,

View File

@ -18,7 +18,8 @@ message Envelope {
LeaveRoom leave_room = 1002; LeaveRoom leave_room = 1002;
Call call = 12; Call call = 12;
IncomingCall incoming_call = 1000; IncomingCall incoming_call = 1000;
CancelCall cancel_call = 1001; CallCanceled call_canceled = 1001;
CancelCall cancel_call = 1004;
DeclineCall decline_call = 13; DeclineCall decline_call = 13;
UpdateParticipantLocation update_participant_location = 1003; UpdateParticipantLocation update_participant_location = 1003;
RoomUpdated room_updated = 14; RoomUpdated room_updated = 14;
@ -189,7 +190,11 @@ message IncomingCall {
optional uint64 initial_project_id = 4; optional uint64 initial_project_id = 4;
} }
message CancelCall {} message CallCanceled {}
message CancelCall {
uint64 recipient_user_id = 1;
}
message DeclineCall {} message DeclineCall {}

View File

@ -84,6 +84,7 @@ messages!(
(BufferReloaded, Foreground), (BufferReloaded, Foreground),
(BufferSaved, Foreground), (BufferSaved, Foreground),
(Call, Foreground), (Call, Foreground),
(CallCanceled, Foreground),
(CancelCall, Foreground), (CancelCall, Foreground),
(ChannelMessageSent, Foreground), (ChannelMessageSent, Foreground),
(CopyProjectEntry, Foreground), (CopyProjectEntry, Foreground),
@ -183,6 +184,7 @@ request_messages!(
ApplyCompletionAdditionalEditsResponse ApplyCompletionAdditionalEditsResponse
), ),
(Call, Ack), (Call, Ack),
(CancelCall, Ack),
(CopyProjectEntry, ProjectEntryResponse), (CopyProjectEntry, ProjectEntryResponse),
(CreateProjectEntry, ProjectEntryResponse), (CreateProjectEntry, ProjectEntryResponse),
(CreateRoom, CreateRoomResponse), (CreateRoom, CreateRoomResponse),