Skip to content

Commit 8a6ced0

Browse files
committed
fix(send queue): when adding a local reaction, look for media events in dependent requests too
1 parent f20401c commit 8a6ced0

File tree

4 files changed

+113
-18
lines changed

4 files changed

+113
-18
lines changed

crates/matrix-sdk-base/src/store/send_queue.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,27 @@ pub struct DependentQueuedRequest {
367367
pub parent_key: Option<SentRequestKey>,
368368
}
369369

370+
impl DependentQueuedRequest {
371+
/// Does the dependent request represent a new event that is *not*
372+
/// aggregated, aka it is going to be its own item in a timeline?
373+
pub fn is_own_event(&self) -> bool {
374+
match self.kind {
375+
DependentQueuedRequestKind::EditEvent { .. }
376+
| DependentQueuedRequestKind::RedactEvent
377+
| DependentQueuedRequestKind::ReactEvent { .. }
378+
| DependentQueuedRequestKind::UploadFileWithThumbnail { .. } => {
379+
// These are all aggregated events, or non-visible items (file upload producing
380+
// a new MXC ID).
381+
false
382+
}
383+
DependentQueuedRequestKind::FinishUpload { .. } => {
384+
// This one graduates into a new media event.
385+
true
386+
}
387+
}
388+
}
389+
}
390+
370391
#[cfg(not(tarpaulin_include))]
371392
impl fmt::Debug for QueuedRequest {
372393
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {

crates/matrix-sdk-ui/src/timeline/controller/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,7 @@ impl<P: RoomDataProvider> TimelineController<P> {
505505
let Some(prev_status) = prev_status else {
506506
match &item.kind {
507507
EventTimelineItemKind::Local(local) => {
508-
if let Some(send_handle) = local.send_handle.clone() {
508+
if let Some(send_handle) = &local.send_handle {
509509
if send_handle
510510
.react(key.to_owned())
511511
.await

crates/matrix-sdk-ui/tests/integration/timeline/media.rs

Lines changed: 80 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
use std::{fs::File, io::Write as _, time::Duration};
15+
use std::{fs::File, io::Write as _, path::PathBuf, time::Duration};
1616

1717
use assert_matches::assert_matches;
1818
use assert_matches2::assert_let;
@@ -35,6 +35,24 @@ use tempfile::TempDir;
3535
use tokio::time::sleep;
3636
use wiremock::ResponseTemplate;
3737

38+
fn create_temporary_file(filename: &str) -> (TempDir, PathBuf) {
39+
let tmp_dir = TempDir::new().unwrap();
40+
let file_path = tmp_dir.path().join(filename);
41+
let mut file = File::create(&file_path).unwrap();
42+
file.write_all(b"hello world").unwrap();
43+
(tmp_dir, file_path)
44+
}
45+
46+
fn get_filename_and_caption(msg: &MessageType) -> (&str, Option<&str>) {
47+
match msg {
48+
MessageType::File(event) => (event.filename(), event.caption()),
49+
MessageType::Image(event) => (event.filename(), event.caption()),
50+
MessageType::Video(event) => (event.filename(), event.caption()),
51+
MessageType::Audio(event) => (event.filename(), event.caption()),
52+
_ => panic!("unexpected message type"),
53+
}
54+
}
55+
3856
#[async_test]
3957
async fn test_send_attachment() {
4058
let mock = MatrixMockServer::new().await;
@@ -48,6 +66,7 @@ async fn test_send_attachment() {
4866

4967
let (items, mut timeline_stream) =
5068
timeline.subscribe_filter_map(|item| item.as_event().cloned()).await;
69+
5170
assert!(items.is_empty());
5271

5372
let f = EventFactory::new();
@@ -66,13 +85,7 @@ async fn test_send_attachment() {
6685
assert!(timeline_stream.next().now_or_never().is_none());
6786

6887
// Store a file in a temporary directory.
69-
let tmp_dir = TempDir::new().unwrap();
70-
71-
let file_path = tmp_dir.path().join("test.bin");
72-
{
73-
let mut file = File::create(&file_path).unwrap();
74-
file.write_all(b"hello world").unwrap();
75-
}
88+
let (_tmp_dir, file_path) = create_temporary_file("test.bin");
7689

7790
// Set up mocks for the file upload.
7891
mock.mock_upload()
@@ -94,16 +107,14 @@ async fn test_send_attachment() {
94107
{
95108
assert_let_timeout!(Some(VectorDiff::PushBack { value: item }) = timeline_stream.next());
96109
assert_matches!(item.send_state(), Some(EventSendState::NotSentYet));
97-
98110
assert_let!(TimelineItemContent::Message(msg) = item.content());
99111

100112
// Body is the caption, because there's both a caption and filename.
101113
assert_eq!(msg.body(), "caption");
102-
assert_let!(MessageType::File(file) = msg.msgtype());
103-
assert_eq!(file.filename(), "test.bin");
104-
assert_eq!(file.caption(), Some("caption"));
114+
assert_eq!(get_filename_and_caption(msg.msgtype()), ("test.bin", Some("caption")));
105115

106116
// The URI refers to the local cache.
117+
assert_let!(MessageType::File(file) = msg.msgtype());
107118
assert_let!(MediaSource::Plain(uri) = &file.source);
108119
assert!(uri.to_string().contains("localhost"));
109120
}
@@ -116,12 +127,11 @@ async fn test_send_attachment() {
116127
Some(VectorDiff::Set { index: 1, value: item }) = timeline_stream.next()
117128
);
118129
assert_let!(TimelineItemContent::Message(msg) = item.content());
119-
120-
assert_let!(MessageType::File(file) = msg.msgtype());
121-
assert_eq!(file.filename(), "test.bin");
122-
assert_eq!(file.caption(), Some("caption"));
130+
assert_matches!(item.send_state(), Some(EventSendState::NotSentYet));
131+
assert_eq!(get_filename_and_caption(msg.msgtype()), ("test.bin", Some("caption")));
123132

124133
// The URI now refers to the final MXC URI.
134+
assert_let!(MessageType::File(file) = msg.msgtype());
125135
assert_let!(MediaSource::Plain(uri) = &file.source);
126136
assert_eq!(uri.to_string(), "mxc://sdk.rs/media");
127137
}
@@ -139,3 +149,57 @@ async fn test_send_attachment() {
139149
// That's all, folks!
140150
assert!(timeline_stream.next().now_or_never().is_none());
141151
}
152+
153+
#[async_test]
154+
async fn test_react_to_local_media() {
155+
let mock = MatrixMockServer::new().await;
156+
let client = mock.client_builder().build().await;
157+
158+
// Disable the sending queue, to simulate offline mode.
159+
client.send_queue().set_enabled(false).await;
160+
161+
mock.mock_room_state_encryption().plain().mount().await;
162+
163+
let room_id = room_id!("!a98sd12bjh:example.org");
164+
let room = mock.sync_joined_room(&client, room_id).await;
165+
let timeline = room.timeline().await.unwrap();
166+
167+
let (items, mut timeline_stream) =
168+
timeline.subscribe_filter_map(|item| item.as_event().cloned()).await;
169+
170+
assert!(items.is_empty());
171+
assert!(timeline_stream.next().now_or_never().is_none());
172+
173+
// Store a file in a temporary directory.
174+
let (_tmp_dir, file_path) = create_temporary_file("test.bin");
175+
176+
// Queue sending of an attachment (no captions).
177+
let config = AttachmentConfig::new();
178+
timeline.send_attachment(&file_path, mime::TEXT_PLAIN, config).use_send_queue().await.unwrap();
179+
180+
let item_id = {
181+
assert_let_timeout!(Some(VectorDiff::PushBack { value: item }) = timeline_stream.next());
182+
assert_let!(TimelineItemContent::Message(msg) = item.content());
183+
assert_eq!(get_filename_and_caption(msg.msgtype()), ("test.bin", None));
184+
185+
// The item starts with no reactions.
186+
assert!(item.reactions().is_empty());
187+
188+
item.identifier()
189+
};
190+
191+
// Add a reaction to the file media event.
192+
timeline.toggle_reaction(&item_id, "🤪").await.unwrap();
193+
194+
assert_let_timeout!(Some(VectorDiff::Set { index: 0, value: item }) = timeline_stream.next());
195+
assert_let!(TimelineItemContent::Message(msg) = item.content());
196+
assert_eq!(get_filename_and_caption(msg.msgtype()), ("test.bin", None));
197+
198+
// There's a reaction for the current user for the given emoji.
199+
let reactions = item.reactions();
200+
let own_user_id = client.user_id().unwrap();
201+
reactions.get("🤪").unwrap().get(own_user_id).unwrap();
202+
203+
// That's all, folks!
204+
assert!(timeline_stream.next().now_or_never().is_none());
205+
}

crates/matrix-sdk/src/send_queue.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1270,7 +1270,17 @@ impl QueueStorage {
12701270

12711271
// If the target event has been already sent, abort immediately.
12721272
if !requests.iter().any(|item| item.transaction_id == transaction_id) {
1273-
return Ok(None);
1273+
// We didn't find it as a queued request; try to find it as a dependent queued
1274+
// request.
1275+
let dependent_requests = store.load_dependent_queued_requests(&self.room_id).await?;
1276+
if !dependent_requests
1277+
.into_iter()
1278+
.filter_map(|item| item.is_own_event().then_some(item.own_transaction_id))
1279+
.any(|child_txn| *child_txn == *transaction_id)
1280+
{
1281+
// We didn't find it as either a request or a dependent request, abort.
1282+
return Ok(None);
1283+
}
12741284
}
12751285

12761286
// Record the dependent request.

0 commit comments

Comments
 (0)