From df7beb4afd1f3fed03ee9f1a921f1cd1f90ee4e6 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 6 Jun 2023 17:10:20 +0200 Subject: [PATCH] ui: Add Timeline::retry_send --- .../src/timeline/event_item/content.rs | 11 ++++ crates/matrix-sdk-ui/src/timeline/inner.rs | 24 +++++++ crates/matrix-sdk-ui/src/timeline/mod.rs | 65 +++++++++++++++++++ 3 files changed, 100 insertions(+) diff --git a/crates/matrix-sdk-ui/src/timeline/event_item/content.rs b/crates/matrix-sdk-ui/src/timeline/event_item/content.rs index 1ded64ab4..d8bffe40d 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_item/content.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/content.rs @@ -18,11 +18,13 @@ use imbl::{vector, Vector}; use indexmap::IndexMap; use matrix_sdk::{deserialized_responses::TimelineEvent, Result}; use ruma::{ + assign, events::{ policy::rule::{ room::PolicyRuleRoomEventContent, server::PolicyRuleServerEventContent, user::PolicyRuleUserEventContent, }, + relation::InReplyTo, room::{ aliases::RoomAliasesEventContent, avatar::RoomAvatarEventContent, @@ -232,6 +234,15 @@ impl Message { } } +impl From for RoomMessageEventContent { + fn from(msg: Message) -> Self { + let relates_to = msg.in_reply_to.map(|details| message::Relation::Reply { + in_reply_to: InReplyTo::new(details.event_id), + }); + assign!(Self::new(msg.msgtype), { relates_to }) + } +} + #[cfg(not(tarpaulin_include))] impl fmt::Debug for Message { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/crates/matrix-sdk-ui/src/timeline/inner.rs b/crates/matrix-sdk-ui/src/timeline/inner.rs index cbbd6e321..3fa6cd00f 100644 --- a/crates/matrix-sdk-ui/src/timeline/inner.rs +++ b/crates/matrix-sdk-ui/src/timeline/inner.rs @@ -321,6 +321,30 @@ impl TimelineInner

{ state.items.set(idx, Arc::new(new_item)); } + pub(super) async fn prepare_retry( + &self, + txn_id: &TransactionId, + ) -> Option { + let mut state = self.state.lock().await; + + let (idx, item) = rfind_event_item(&state.items, |it| it.transaction_id() == Some(txn_id))?; + let local_item = item.as_local()?; + + if !matches!(&local_item.send_state, EventSendState::SendingFailed { .. }) { + debug!("Attempted to retry sending of an item that is not in failed state"); + return None; + } + + let new_item = TimelineItem::Event( + item.with_kind(local_item.with_send_state(EventSendState::NotSentYet)), + ); + + let content = item.content.clone(); + state.items.set(idx, Arc::new(new_item)); + + Some(content) + } + /// Handle a back-paginated event. /// /// Returns the number of timeline updates that were made. diff --git a/crates/matrix-sdk-ui/src/timeline/mod.rs b/crates/matrix-sdk-ui/src/timeline/mod.rs index d1a41f997..768c40f9e 100644 --- a/crates/matrix-sdk-ui/src/timeline/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/mod.rs @@ -334,6 +334,67 @@ impl Timeline { Ok(()) } + /// Retry sending a message that previously failed to send. + /// + /// # Arguments + /// + /// * `txn_id` - The transaction ID of a local echo timeline item that has a + /// `send_state()` of `SendState::FailedToSend { .. }` + pub async fn retry_send(&self, txn_id: &TransactionId) -> Result<(), Error> { + macro_rules! error_return { + ($msg:literal) => {{ + error!($msg); + return Ok(()); + }}; + } + + let item = self.inner.prepare_retry(txn_id).await.ok_or(Error::RetryEventNotInTimeline)?; + let content = match item { + TimelineItemContent::Message(msg) => { + AnyMessageLikeEventContent::RoomMessage(msg.into()) + } + TimelineItemContent::RedactedMessage => { + error_return!("Invalid state: attempting to retry a redacted message"); + } + TimelineItemContent::Sticker(sticker) => { + AnyMessageLikeEventContent::Sticker(sticker.content) + } + TimelineItemContent::UnableToDecrypt(_) => { + error_return!("Invalid state: attempting to retry a UTD item"); + } + TimelineItemContent::MembershipChange(_) + | TimelineItemContent::ProfileChange(_) + | TimelineItemContent::OtherState(_) => { + error_return!("Retrying state events is not currently supported"); + } + TimelineItemContent::FailedToParseMessageLike { .. } + | TimelineItemContent::FailedToParseState { .. } => { + error_return!("Invalid state: attempting to retry a failed-to-parse item"); + } + }; + + let send_state = match Room::from(self.room().clone()) { + Room::Joined(room) => { + let response = room.send(content, Some(txn_id)).await; + + match response { + Ok(response) => EventSendState::Sent { event_id: response.event_id }, + Err(error) => EventSendState::SendingFailed { error: Arc::new(error) }, + } + } + _ => { + EventSendState::SendingFailed { + // FIXME: Probably not exactly right + error: Arc::new(matrix_sdk::Error::InconsistentState), + } + } + }; + + self.inner.update_event_send_state(txn_id, send_state).await; + + Ok(()) + } + /// Fetch unavailable details about the event with the given ID. /// /// This method only works for IDs of remote [`EventTimelineItem`]s, @@ -629,6 +690,10 @@ pub enum Error { #[error("Event with remote echo not found in timeline")] RemoteEventNotInTimeline, + /// Can't find an event with the given transaction ID, can't retry. + #[error("Event not found, can't retry sending")] + RetryEventNotInTimeline, + /// The event is currently unsupported for this use case. #[error("Unsupported event")] UnsupportedEvent,