refactor(sdk): Expose event sending errors through timeline item

… instead of through the return value of Timeline::send.
This commit is contained in:
Jonas Platte
2023-01-30 19:27:38 +01:00
committed by Jonas Platte
parent c8021cf2ba
commit 3a1eb62c38
9 changed files with 61 additions and 71 deletions

View File

@@ -259,14 +259,13 @@ interface Room {
// Raises an exception if there are no timeline listeners.
[Throws=ClientError]
void paginate_backwards(PaginationOptions opts);
[Throws=ClientError]
void send_read_receipt(string event_id);
[Throws=ClientError]
void send_read_marker(string fully_read_event_id, string? read_receipt_event_id);
[Throws=ClientError]
void send(RoomMessageEventContent msg, string? txn_id);
[Throws=ClientError]

View File

@@ -295,16 +295,18 @@ impl Room {
})
}
pub fn send(&self, msg: Arc<RoomMessageEventContent>, txn_id: Option<String>) -> Result<()> {
pub fn send(&self, msg: Arc<RoomMessageEventContent>, txn_id: Option<String>) {
let timeline = match &*self.timeline.read().unwrap() {
Some(t) => Arc::clone(t),
None => bail!("Timeline not set up, can't send message"),
None => {
error!("Timeline not set up, can't send message");
return;
}
};
RUNTIME.block_on(async move {
timeline.send((*msg).to_owned().into(), txn_id.as_deref().map(Into::into)).await?;
Ok(())
})
RUNTIME.spawn(async move {
timeline.send((*msg).to_owned().into(), txn_id.as_deref().map(Into::into)).await;
});
}
pub fn send_reply(
@@ -326,24 +328,27 @@ impl Room {
let event_id: &EventId =
in_reply_to_event_id.as_str().try_into().context("Failed to create EventId.")?;
RUNTIME.block_on(async move {
let reply_content = RUNTIME.block_on(async move {
let timeline_event = room.event(event_id).await.context("Couldn't find event.")?;
let event_content = timeline_event
.event
.deserialize_as::<RoomMessageEvent>()
.context("Couldn't deserialise event")?;
.context("Couldn't deserialize event")?;
let original_message =
event_content.as_original().context("Couldn't retrieve original message.")?;
let reply_content = RoomMessageEventContent::text_markdown(msg)
.make_reply_to(original_message, ForwardThread::Yes);
anyhow::Ok(
RoomMessageEventContent::text_markdown(msg)
.make_reply_to(original_message, ForwardThread::Yes),
)
})?;
timeline.send(reply_content.into(), txn_id.as_deref().map(Into::into)).await?;
Ok(())
})
RUNTIME.spawn(async move {
timeline.send(reply_content.into(), txn_id.as_deref().map(Into::into)).await;
});
Ok(())
}
pub fn edit(
@@ -365,7 +370,7 @@ impl Room {
let event_id: &EventId =
original_event_id.as_str().try_into().context("Failed to create EventId.")?;
RUNTIME.block_on(async move {
let edited_content = RUNTIME.block_on(async move {
let timeline_event = room.event(event_id).await.context("Couldn't find event.")?;
let event_content = timeline_event
@@ -384,11 +389,13 @@ impl Room {
let mut edited_content = RoomMessageEventContent::text_markdown(new_msg);
edited_content.relates_to = Some(Relation::Replacement(replacement));
Ok(edited_content)
})?;
timeline.send(edited_content.into(), txn_id.as_deref().map(Into::into)).await?;
Ok(())
})
RUNTIME.spawn(async move {
timeline.send(edited_content.into(), txn_id.as_deref().map(Into::into)).await;
});
Ok(())
}
/// Redacts an event from the room.

View File

@@ -177,7 +177,7 @@ pub enum EventSendState {
NotSendYet,
/// The local event has been sent to the server, but unsuccessfully: The
/// sending has failed.
SendingFailed,
SendingFailed { error: String },
/// The local event has been sent successfully to the server.
Sent { event_id: String },
}
@@ -188,7 +188,7 @@ impl From<&matrix_sdk::room::timeline::EventSendState> for EventSendState {
match value {
NotSentYet => Self::NotSendYet,
SendingFailed => Self::SendingFailed,
SendingFailed { error } => Self::SendingFailed { error: error.to_string() },
Sent { event_id } => Self::Sent { event_id: event_id.to_string() },
}
}

View File

@@ -52,6 +52,8 @@ use ruma::{
OwnedTransactionId, OwnedUserId, TransactionId, UserId,
};
use crate::Error;
/// An item in the timeline that represents at least one event.
///
/// There is always one main event that gives the `EventTimelineItem` its
@@ -209,13 +211,16 @@ impl EventTimelineItem {
}
/// This type represents the "send state" of a local event timeline item.
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug)]
pub enum EventSendState {
/// The local event has not been sent yet.
NotSentYet,
/// The local event has been sent to the server, but unsuccessfully: The
/// sending has failed.
SendingFailed,
SendingFailed {
/// Details about how sending the event failed.
error: Arc<Error>,
},
/// The local event has been sent successfully to the server.
Sent {
/// The event ID assigned by the server.

View File

@@ -14,7 +14,6 @@ use matrix_sdk_base::{
locks::Mutex,
};
use ruma::{
api::client::message::send_message_event::v3::Response as SendMessageEventResponse,
events::{
fully_read::FullyReadEvent, relation::Annotation, AnyMessageLikeEventContent,
AnySyncTimelineEvent,
@@ -151,30 +150,6 @@ impl<P: ProfileProvider> TimelineInner<P> {
.handle_event(kind);
}
/// Handle the response returned by the server when a local event has been
/// sent.
pub(super) fn handle_local_event_send_response(
&self,
txn_id: &TransactionId,
response: crate::error::Result<SendMessageEventResponse>,
) -> crate::error::Result<()> {
match response {
Ok(response) => {
self.update_event_send_state(
txn_id,
EventSendState::Sent { event_id: response.event_id },
);
Ok(())
}
Err(error) => {
self.update_event_send_state(txn_id, EventSendState::SendingFailed);
Err(error)
}
}
}
/// Update the send state of a local event represented by a transaction ID.
///
/// If no local event is found, a warning is raised.

View File

@@ -370,11 +370,7 @@ impl Timeline {
/// [`MessageLikeUnsigned`]: ruma::events::MessageLikeUnsigned
/// [`SyncMessageLikeEvent`]: ruma::events::SyncMessageLikeEvent
#[instrument(skip(self, content), fields(room_id = ?self.room().room_id()))]
pub async fn send(
&self,
content: AnyMessageLikeEventContent,
txn_id: Option<&TransactionId>,
) -> Result<()> {
pub async fn send(&self, content: AnyMessageLikeEventContent, txn_id: Option<&TransactionId>) {
let txn_id = txn_id.map_or_else(TransactionId::new, ToOwned::to_owned);
self.inner.handle_local_event(txn_id.clone(), content.clone()).await;
@@ -383,7 +379,12 @@ impl Timeline {
let room = Joined { inner: self.room().clone() };
let response = room.send(content, Some(&txn_id)).await;
self.inner.handle_local_event_send_response(&txn_id, response)
let send_state = match response {
Ok(response) => EventSendState::Sent { event_id: response.event_id },
Err(error) => EventSendState::SendingFailed { error: Arc::new(error) },
};
self.inner.update_event_send_state(&txn_id, send_state);
}
}

View File

@@ -14,9 +14,12 @@
//! Unit tests (based on private methods) for the timeline API.
use std::sync::{
atomic::{AtomicU32, Ordering::SeqCst},
Arc,
use std::{
io,
sync::{
atomic::{AtomicU32, Ordering::SeqCst},
Arc,
},
};
use assert_matches::assert_matches;
@@ -58,7 +61,7 @@ use super::{
EventTimelineItem, MembershipChange, Profile, TimelineInner, TimelineItem, TimelineItemContent,
VirtualTimelineItem,
};
use crate::room::timeline::event_item::EventSendState;
use crate::{room::timeline::event_item::EventSendState, Error};
static ALICE: Lazy<&UserId> = Lazy::new(|| user_id!("@alice:server.name"));
static BOB: Lazy<&UserId> = Lazy::new(|| user_id!("@bob:other.server"));
@@ -388,20 +391,24 @@ async fn remote_echo_full_trip() {
{
let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value);
let event = item.as_event().unwrap().as_local().unwrap();
assert_eq!(event.send_state, EventSendState::NotSentYet);
assert_matches!(event.send_state, EventSendState::NotSentYet);
}
// Scenario 2: The local event has not been sent to the server successfully, it
// has failed. In this case, there is no event ID.
{
timeline.inner.update_event_send_state(&txn_id, EventSendState::SendingFailed);
let some_io_error = Error::Io(io::Error::new(io::ErrorKind::Other, "this is a test"));
timeline.inner.update_event_send_state(
&txn_id,
EventSendState::SendingFailed { error: Arc::new(some_io_error) },
);
let item = assert_matches!(
stream.next().await,
Some(VecDiff::UpdateAt { value, index: 1 }) => value
);
let event = item.as_event().unwrap().as_local().unwrap();
assert_eq!(event.send_state, EventSendState::SendingFailed);
assert_matches!(event.send_state, EventSendState::SendingFailed { .. });
}
// Scenario 3: The local event has been sent successfully to the server and an

View File

@@ -194,7 +194,7 @@ async fn echo() {
assert_eq!(text.body, "Hello, World!");
// Wait for the sending to finish and assert everything was successful
send_hdl.await.unwrap().unwrap();
send_hdl.await.unwrap();
let sent_confirmation = assert_matches!(
timeline_stream.next().await,

View File

@@ -7,7 +7,7 @@ use std::{ops::Deref, time::Duration};
use futures::executor::block_on;
use matrix_sdk::{ruma::events::room::message::RoomMessageEventContent, Client};
use tokio::sync::mpsc;
use tracing::{error, info, warn};
use tracing::warn;
use tuirealm::{
props::{Alignment, Borders, Color},
terminal::TerminalBridge,
@@ -215,11 +215,7 @@ impl Update<Msg> for Model {
if let Some(tl) = self.sliding_sync.room_timeline.lock_ref().deref() {
block_on(async move {
// fire and forget
match tl.send(RoomMessageEventContent::text_plain(m).into(), None).await
{
Ok(_r) => info!("Message send"),
Err(e) => error!("Sending message failed: {e}"),
}
tl.send(RoomMessageEventContent::text_plain(m).into(), None).await;
});
} else {
warn!("asked to send message, but no room is selected");