atagen

macltok

public
Star0
Watch0
96fdf90aacd6dc2b8bd3d63773234245b389d28c
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
src/engine.rsRust4175 lines163.9 KB
1
use std::{
2
    borrow::Cow,
3
    collections::{HashMap, HashSet, VecDeque},
4
    future::Future,
5
    io::Read as _,
6
    panic::AssertUnwindSafe,
7
    sync::{
8
        Arc,
9
        atomic::{AtomicBool, Ordering},
10
    },
11
};
12
13
use futures_util::{FutureExt, StreamExt};
14
use matrix_sdk::{
15
    Client, Room, SessionChange,
16
    media::{MediaFormat, MediaRequestParameters, MediaThumbnailSettings},
17
    room::edit::EditedContent,
18
    room::reply::{EnforceThread, Reply as SdkReply},
19
    ruma::{
20
        OwnedDeviceId, OwnedEventId, OwnedRoomId, RoomId,
21
        api::client::receipt::create_receipt::v3::ReceiptType,
22
        events::{
23
            AnyGlobalAccountDataEventContent, AnyMessageLikeEventContent,
24
            GlobalAccountDataEventType, Mentions, RoomAccountDataEventType,
25
            receipt::ReceiptThread,
26
            room::message::{
27
                AddMentions, MessageType, Relation, ReplyWithinThread,
28
                RoomMessageEventContentWithoutRelation,
29
            },
30
        },
31
        serde::Raw,
32
    },
33
    send_queue::{LocalEchoContent, RoomSendQueueUpdate, SendHandle},
34
};
35
use matrix_sdk_ui::{
36
    sync_service::{State as SyncState, SyncService},
37
    timeline::{
38
        RoomExt, Timeline, TimelineEventItemId, TimelineFocus, TimelineReadReceiptTracking,
39
    },
40
};
41
use thiserror::Error;
42
use tokio::{
43
    sync::{Mutex, mpsc, oneshot, watch},
44
    task::{AbortHandle, Id as TaskId, JoinHandle, JoinSet},
45
};
46
47
use crate::{
48
    AccountCommand, AttachmentUpload, ClientHandle, Command, CommandErrorKind, CommandFailure,
49
    CommandId, CommandKind, ConnectionState, CreateRoomOptions, EncryptionCommand, EngineErrorKind,
50
    EngineFailure, Event, EventStream, HistoryState, InteractiveAuth, InteractiveAuthChallenge,
51
    InteractiveAuthOutcome, LifecycleCommand, MediaCommand, MediaId, MediaSize, MessageContent,
52
    MessageEditContent, MessageMentions, NotificationCommand, PowerLevelChanges, RichText,
53
    RoomCommand, RoomNotificationMode, RoomPreset, RoomVisibility, TimelineCommand, TimelineId,
54
    TypingState,
55
    encryption::{VerificationController, encryption_state},
56
    error::{CommandError, classify_sdk_error},
57
    normalise::{MediaRegistration, MediaScope, normalise_timeline, normalise_timeline_diffs},
58
    transport::{
59
        ClientQueryError, CommandEnvelope, CommandResultError, DriverPort, DriverRequest,
60
        EventSender, SendEventError, transport_pair,
61
    },
62
    upload::prepare_upload,
63
};
64
use matrix_sdk::ruma::api::{Metadata, OutgoingRequest, auth_scheme::SendAccessToken};
65
use matrix_sdk_crypto::{AttachmentDecryptor, MediaEncryptionInfo};
66
67
#[derive(Clone, Debug, Eq, PartialEq)]
68
#[non_exhaustive]
69
pub struct EngineConfig {
70
    pub command_capacity: usize,
71
    pub event_capacity: usize,
72
    pub initial_history_limit: u16,
73
    pub max_media_bytes: usize,
74
    pub max_media_sources: usize,
75
}
76
77
impl EngineConfig {
78
    #[must_use]
79
    pub const fn with_command_capacity(mut self, capacity: usize) -> Self {
80
        self.command_capacity = capacity;
81
        self
82
    }
83
84
    #[must_use]
85
    pub const fn with_event_capacity(mut self, capacity: usize) -> Self {
86
        self.event_capacity = capacity;
87
        self
88
    }
89
90
    #[must_use]
91
    pub const fn with_initial_history_limit(mut self, limit: u16) -> Self {
92
        self.initial_history_limit = limit;
93
        self
94
    }
95
96
    #[must_use]
97
    pub const fn with_max_media_bytes(mut self, bytes: usize) -> Self {
98
        self.max_media_bytes = bytes;
99
        self
100
    }
101
102
    #[must_use]
103
    pub const fn with_max_media_sources(mut self, sources: usize) -> Self {
104
        self.max_media_sources = sources;
105
        self
106
    }
107
}
108
109
impl Default for EngineConfig {
110
    fn default() -> Self {
111
        Self {
112
            command_capacity: 64,
113
            event_capacity: 128,
114
            initial_history_limit: 30,
115
            max_media_bytes: 20 * 1024 * 1024,
116
            max_media_sources: 2_048,
117
        }
118
    }
119
}
120
121
#[derive(Debug, Error)]
122
pub enum EngineError {
123
    #[error("the Matrix client is not authenticated")]
124
    Unauthenticated,
125
    #[error("command, event, and media source capacities must be nonzero")]
126
    ZeroCapacity,
127
    #[error("could not build the sync service")]
128
    SyncService(#[source] matrix_sdk_ui::sync_service::Error),
129
    #[error("the event receiver has closed")]
130
    EventReceiverClosed,
131
    #[error("engine task {task} panicked")]
132
    TaskPanicked { task: String },
133
}
134
135
impl EngineError {
136
    fn failure(&self) -> EngineFailure {
137
        EngineFailure {
138
            kind: match self {
139
                Self::EventReceiverClosed => EngineErrorKind::EventReceiverClosed,
140
                Self::Unauthenticated
141
                | Self::ZeroCapacity
142
                | Self::SyncService(_)
143
                | Self::TaskPanicked { .. } => EngineErrorKind::Other,
144
            },
145
            message: self.to_string(),
146
        }
147
    }
148
}
149
150
type UploadWatchers = HashMap<
151
    OwnedRoomId,
152
    (
153
        AbortHandle,
154
        watch::Receiver<Option<Result<(), CommandErrorKind>>>,
155
    ),
156
>;
157
type MediaFlights = Arc<Mutex<HashMap<(MediaId, MediaSize), Vec<CommandId>>>>;
158
159
pub struct Engine {
160
    client: Client,
161
    sync: Arc<SyncService>,
162
    port: DriverPort,
163
    rooms: HashMap<OwnedRoomId, RoomActorState>,
164
    next_room_generation: u64,
165
    room_actor_events: mpsc::UnboundedReceiver<RoomActorEvent>,
166
    room_actor_event_sender: mpsc::UnboundedSender<RoomActorEvent>,
167
    upload_watchers: UploadWatchers,
168
    tasks: TaskRegistry,
169
    session_changes: Option<tokio::sync::broadcast::Receiver<SessionChange>>,
170
    initial_history_limit: u16,
171
    room_command_capacity: usize,
172
    max_media_bytes: usize,
173
    media: Arc<Mutex<MediaRegistry>>,
174
    notification_commands: mpsc::Sender<CommandJob<NotificationCommand>>,
175
    notification_command_receiver: Option<mpsc::Receiver<CommandJob<NotificationCommand>>>,
176
    encryption_commands: mpsc::Sender<EncryptionWork>,
177
    encryption_command_receiver: Option<mpsc::Receiver<EncryptionWork>>,
178
    verification: Arc<Mutex<VerificationController>>,
179
    room_commands: mpsc::Sender<RoomJob>,
180
    room_command_receiver: Option<mpsc::Receiver<RoomJob>>,
181
    global_account_gates: HashMap<String, oneshot::Receiver<()>>,
182
    room_account_gates: HashMap<(OwnedRoomId, String), oneshot::Receiver<()>>,
183
    media_flights: MediaFlights,
184
}
185
186
struct CommandJob<T> {
187
    id: CommandId,
188
    command: T,
189
}
190
191
enum EncryptionWork {
192
    Command(CommandJob<EncryptionCommand>),
193
}
194
195
struct RoomJob {
196
    id: CommandId,
197
    command: RoomCommand,
198
    reply: Option<oneshot::Sender<Result<OwnedRoomId, CommandResultError>>>,
199
}
200
201
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
202
enum RoomMutationKey {
203
    Room(OwnedRoomId),
204
    Target(String),
205
    Direct(ruma::OwnedUserId),
206
    Independent(CommandId),
207
}
208
209
struct RoomJobCompletion {
210
    key: RoomMutationKey,
211
    id: CommandId,
212
    reply: Option<oneshot::Sender<Result<OwnedRoomId, CommandResultError>>>,
213
    result: Result<Option<OwnedRoomId>, CommandError>,
214
}
215
216
#[derive(Clone)]
217
enum TaskContext {
218
    Command {
219
        id: CommandId,
220
        kind: CommandKind,
221
    },
222
    Watcher(&'static str),
223
    RoomActor {
224
        room_id: OwnedRoomId,
225
        generation: u64,
226
    },
227
}
228
229
impl TaskContext {
230
    fn label(&self) -> String {
231
        match self {
232
            Self::Command { id, kind } => format!("{kind:?} command {}", id.get()),
233
            Self::Watcher(name) => (*name).to_owned(),
234
            Self::RoomActor {
235
                room_id,
236
                generation,
237
            } => format!("room actor {room_id} generation {generation}"),
238
        }
239
    }
240
}
241
242
struct TaskRegistry {
243
    tasks: JoinSet<()>,
244
    contexts: HashMap<TaskId, TaskContext>,
245
}
246
247
impl TaskRegistry {
248
    fn new() -> Self {
249
        Self {
250
            tasks: JoinSet::new(),
251
            contexts: HashMap::new(),
252
        }
253
    }
254
255
    fn is_empty(&self) -> bool {
256
        self.tasks.is_empty()
257
    }
258
259
    fn spawn(
260
        &mut self,
261
        context: TaskContext,
262
        future: impl Future<Output = ()> + Send + 'static,
263
    ) -> AbortHandle {
264
        let handle = self.tasks.spawn(future);
265
        self.contexts.insert(handle.id(), context);
266
        handle
267
    }
268
269
    async fn join_next(&mut self) -> Option<(TaskContext, Result<(), tokio::task::JoinError>)> {
270
        let result = self.tasks.join_next_with_id().await?;
271
        let id = match &result {
272
            Ok((id, ())) => *id,
273
            Err(error) => error.id(),
274
        };
275
        let context = self
276
            .contexts
277
            .remove(&id)
278
            .expect("every registered task has context");
279
        Some((context, result.map(|(_, ())| ())))
280
    }
281
282
    async fn shutdown(&mut self) {
283
        self.tasks.abort_all();
284
        while self.tasks.join_next().await.is_some() {}
285
        self.contexts.clear();
286
    }
287
}
288
289
enum RoomActorState {
290
    Opening {
291
        generation: u64,
292
        sender: mpsc::Sender<RoomActorRequest>,
293
    },
294
    Open {
295
        generation: u64,
296
        sender: mpsc::Sender<RoomActorRequest>,
297
    },
298
    Closing {
299
        generation: u64,
300
        pending: VecDeque<DeferredRoomLifecycle>,
301
    },
302
}
303
304
enum DeferredRoomLifecycle {
305
    Open(CommandId),
306
    Close(CommandId),
307
}
308
309
impl RoomActorState {
310
    const fn generation(&self) -> u64 {
311
        match self {
312
            Self::Opening { generation, .. }
313
            | Self::Open { generation, .. }
314
            | Self::Closing { generation, .. } => *generation,
315
        }
316
    }
317
318
    fn sender(&self) -> Option<&mpsc::Sender<RoomActorRequest>> {
319
        match self {
320
            Self::Opening { sender, .. } | Self::Open { sender, .. } => Some(sender),
321
            Self::Closing { .. } => None,
322
        }
323
    }
324
}
325
326
enum RoomActorEvent {
327
    Ready {
328
        room_id: OwnedRoomId,
329
        generation: u64,
330
    },
331
    Exited {
332
        room_id: OwnedRoomId,
333
        generation: u64,
334
    },
335
}
336
337
enum EngineActivity {
338
    Request(Option<Box<DriverRequest>>),
339
    Task(Option<(TaskContext, Result<(), tokio::task::JoinError>)>),
340
    RoomActor(Option<RoomActorEvent>),
341
}
342
343
struct MediaRegistry {
344
    entries: HashMap<MediaId, MediaRegistration>,
345
    order: VecDeque<MediaId>,
346
    limit: usize,
347
}
348
349
impl MediaRegistry {
350
    fn new(limit: usize) -> Self {
351
        Self {
352
            entries: HashMap::new(),
353
            order: VecDeque::new(),
354
            limit,
355
        }
356
    }
357
358
    fn insert_all(&mut self, media: Vec<MediaRegistration>) {
359
        for media in media {
360
            if !self.entries.contains_key(&media.id) {
361
                self.order.push_back(media.id.clone());
362
            }
363
            self.entries.insert(media.id.clone(), media);
364
        }
365
        while self.entries.len() > self.limit {
366
            if let Some(id) = self.order.pop_front() {
367
                self.entries.remove(&id);
368
            }
369
        }
370
    }
371
}
372
373
enum RoomActorRequest {
374
    Opened {
375
        id: CommandId,
376
    },
377
    Execute {
378
        id: CommandId,
379
        command: TimelineCommand,
380
    },
381
    Close {
382
        id: CommandId,
383
    },
384
}
385
386
struct RoomActor {
387
    room_id: OwnedRoomId,
388
    generation: u64,
389
    room: Room,
390
    receiver: mpsc::Receiver<RoomActorRequest>,
391
    events: EventSender,
392
    engine_events: mpsc::UnboundedSender<RoomActorEvent>,
393
    media: Arc<Mutex<MediaRegistry>>,
394
    initial_history_limit: u16,
395
    open_ids: Vec<CommandId>,
396
}
397
398
struct TimelineSession {
399
    id: TimelineId,
400
    timeline: Arc<Timeline>,
401
    events: EventSender,
402
    watcher: JoinHandle<()>,
403
    media: Arc<Mutex<MediaRegistry>>,
404
    current_user_id: Option<ruma::OwnedUserId>,
405
    scope: MediaScope,
406
    pagination_ids: Arc<Mutex<HashSet<CommandId>>>,
407
    pagination_sender: mpsc::Sender<PaginationRequest>,
408
    pagination_worker: JoinHandle<()>,
409
    pagination_results: Option<mpsc::UnboundedSender<(CommandId, Result<(), CommandError>)>>,
410
    pagination_result_task: JoinHandle<()>,
411
    reached_start: Arc<AtomicBool>,
412
}
413
414
struct PaginationRequest {
415
    timeline: Arc<Timeline>,
416
    events: EventSender,
417
    timeline_id: TimelineId,
418
    reached_start: Arc<AtomicBool>,
419
    limit: u16,
420
    command_id: Option<CommandId>,
421
    results: mpsc::UnboundedSender<(CommandId, Result<(), CommandError>)>,
422
}
423
424
impl TimelineSession {
425
    async fn start(
426
        id: TimelineId,
427
        timeline: Timeline,
428
        events: EventSender,
429
        media: Arc<Mutex<MediaRegistry>>,
430
        current_user_id: Option<ruma::OwnedUserId>,
431
        initial_history_limit: u16,
432
    ) -> Result<Self, SendEventError> {
433
        let timeline = Arc::new(timeline);
434
        let scope = MediaScope::new();
435
        let watcher = start_timeline_watcher(
436
            id.clone(),
437
            timeline.clone(),
438
            events.clone(),
439
            media.clone(),
440
            current_user_id.clone(),
441
            scope.clone(),
442
        )
443
        .await?;
444
445
        let (pagination_sender, mut pagination_receiver) = mpsc::channel::<PaginationRequest>(32);
446
        let pagination_worker = tokio::spawn(async move {
447
            while let Some(request) = pagination_receiver.recv().await {
448
                let command_id = request.command_id;
449
                let results = request.results.clone();
450
                if AssertUnwindSafe(run_pagination(request))
451
                    .catch_unwind()
452
                    .await
453
                    .is_err()
454
                {
455
                    tracing::warn!(?command_id, "pagination task panicked");
456
                    if let Some(id) = command_id {
457
                        let _ = results.send((
458
                            id,
459
                            Err(CommandError::new(
460
                                CommandErrorKind::Timeline,
461
                                "pagination failed",
462
                            )),
463
                        ));
464
                    }
465
                }
466
            }
467
        });
468
        let pagination_ids = Arc::new(Mutex::new(HashSet::new()));
469
        let (pagination_results, mut result_receiver) = mpsc::unbounded_channel();
470
        let result_events = events.clone();
471
        let result_ids = pagination_ids.clone();
472
        let pagination_result_task = tokio::spawn(async move {
473
            while let Some((id, result)) = result_receiver.recv().await {
474
                if result_ids.lock().await.remove(&id) {
475
                    let _ = emit_command_result(&result_events, id, CommandKind::Timeline, result)
476
                        .await;
477
                }
478
            }
479
        });
480
481
        let mut session = Self {
482
            id,
483
            timeline,
484
            events,
485
            watcher,
486
            media,
487
            current_user_id,
488
            scope,
489
            pagination_sender,
490
            pagination_worker,
491
            pagination_ids,
492
            pagination_results: Some(pagination_results),
493
            pagination_result_task,
494
            reached_start: Arc::new(AtomicBool::new(false)),
495
        };
496
        if initial_history_limit > 0 {
497
            session.paginate(initial_history_limit, None).await;
498
        }
499
        Ok(session)
500
    }
501
502
    async fn resync(&mut self) -> Result<(), SendEventError> {
503
        self.watcher.abort();
504
        let _ = (&mut self.watcher).await;
505
        self.watcher = start_timeline_watcher(
506
            self.id.clone(),
507
            self.timeline.clone(),
508
            self.events.clone(),
509
            self.media.clone(),
510
            self.current_user_id.clone(),
511
            self.scope.clone(),
512
        )
513
        .await?;
514
        Ok(())
515
    }
516
517
    async fn paginate(&mut self, limit: u16, command_id: Option<CommandId>) {
518
        if let Some(id) = command_id {
519
            self.pagination_ids.lock().await.insert(id);
520
        }
521
        let request = PaginationRequest {
522
            timeline: self.timeline.clone(),
523
            events: self.events.clone(),
524
            timeline_id: self.id.clone(),
525
            reached_start: self.reached_start.clone(),
526
            limit,
527
            command_id,
528
            results: self
529
                .pagination_results
530
                .as_ref()
531
                .expect("pagination session is open")
532
                .clone(),
533
        };
534
        if let Err(error) = self.pagination_sender.try_send(request) {
535
            let kind = match error {
536
                mpsc::error::TrySendError::Full(_) => CommandErrorKind::QueueFull,
537
                mpsc::error::TrySendError::Closed(_) => CommandErrorKind::TimelineNotOpen,
538
            };
539
            if let Some(id) = command_id {
540
                self.pagination_ids.lock().await.remove(&id);
541
                let _ = emit_command_result(
542
                    &self.events,
543
                    id,
544
                    CommandKind::Timeline,
545
                    Err(CommandError::new(kind, "pagination queue is unavailable")),
546
                )
547
                .await;
548
            }
549
        }
550
    }
551
552
    async fn close(mut self, kind: CommandErrorKind, message: &'static str) {
553
        self.pagination_worker.abort();
554
        let _ = (&mut self.pagination_worker).await;
555
        drop(self.pagination_results.take());
556
        let _ = (&mut self.pagination_result_task).await;
557
        let pending = self.pagination_ids.lock().await.drain().collect::<Vec<_>>();
558
        for id in pending {
559
            let _ = emit_command_result(
560
                &self.events,
561
                id,
562
                CommandKind::Timeline,
563
                Err(CommandError::new(kind, message)),
564
            )
565
            .await;
566
        }
567
        self.watcher.abort();
568
        let _ = (&mut self.watcher).await;
569
    }
570
}
571
572
async fn start_timeline_watcher(
573
    id: TimelineId,
574
    timeline: Arc<Timeline>,
575
    events: EventSender,
576
    media: Arc<Mutex<MediaRegistry>>,
577
    current_user_id: Option<ruma::OwnedUserId>,
578
    scope: MediaScope,
579
) -> Result<JoinHandle<()>, SendEventError> {
580
    let (items, mut stream) = timeline.subscribe().await;
581
    let normalised = normalise_timeline(items.iter(), current_user_id.as_deref(), &scope);
582
    media.lock().await.insert_all(normalised.media);
583
    events
584
        .send(Event::TimelineReset {
585
            timeline_id: id.clone(),
586
            items: normalised.items,
587
        })
588
        .await?;
589
    Ok(tokio::spawn(async move {
590
        while let Some(diffs) = stream.next().await {
591
            let normalised = normalise_timeline_diffs(diffs, current_user_id.as_deref(), &scope);
592
            media.lock().await.insert_all(normalised.media);
593
            if events
594
                .send(Event::TimelineChanged {
595
                    timeline_id: id.clone(),
596
                    diffs: normalised.diffs,
597
                })
598
                .await
599
                .is_err()
600
            {
601
                tracing::debug!(
602
                    ?id,
603
                    "timeline watcher stopped because the event receiver closed"
604
                );
605
                return;
606
            }
607
        }
608
        tracing::debug!(?id, "timeline watcher stopped");
609
    }))
610
}
611
612
impl Drop for TimelineSession {
613
    fn drop(&mut self) {
614
        self.watcher.abort();
615
        self.pagination_worker.abort();
616
        self.pagination_result_task.abort();
617
    }
618
}
619
620
impl Engine {
621
    pub async fn from_client(
622
        client: Client,
623
        config: EngineConfig,
624
    ) -> Result<(Self, ClientHandle, EventStream), EngineError> {
625
        if client.user_id().is_none() {
626
            return Err(EngineError::Unauthenticated);
627
        }
628
        if config.command_capacity == 0
629
            || config.event_capacity == 0
630
            || config.max_media_sources == 0
631
        {
632
            return Err(EngineError::ZeroCapacity);
633
        }
634
        let session_changes = client.subscribe_to_session_changes();
635
        let sync = Arc::new(
636
            SyncService::builder(client.clone())
637
                .with_offline_mode()
638
                .build()
639
                .await
640
                .map_err(EngineError::SyncService)?,
641
        );
642
        let (handle, port, events) = transport_pair(
643
            client.clone(),
644
            sync.clone(),
645
            config.command_capacity,
646
            config.event_capacity,
647
        );
648
        client.send_queue().enable_upload_progress(true);
649
        let (room_actor_event_sender, room_actor_events) = mpsc::unbounded_channel();
650
        let (notification_commands, notification_command_receiver) =
651
            mpsc::channel(config.command_capacity);
652
        let (encryption_commands, encryption_command_receiver) =
653
            mpsc::channel(config.command_capacity);
654
        let (room_commands, room_command_receiver) = mpsc::channel(config.command_capacity);
655
        Ok((
656
            Self {
657
                client,
658
                sync,
659
                port,
660
                rooms: HashMap::new(),
661
                next_room_generation: 0,
662
                room_actor_events,
663
                room_actor_event_sender,
664
                upload_watchers: HashMap::new(),
665
                tasks: TaskRegistry::new(),
666
                session_changes: Some(session_changes),
667
                initial_history_limit: config.initial_history_limit,
668
                room_command_capacity: config.command_capacity,
669
                max_media_bytes: config.max_media_bytes,
670
                media: Arc::new(Mutex::new(MediaRegistry::new(config.max_media_sources))),
671
                notification_commands,
672
                notification_command_receiver: Some(notification_command_receiver),
673
                encryption_commands,
674
                encryption_command_receiver: Some(encryption_command_receiver),
675
                verification: Arc::new(Mutex::new(VerificationController::default())),
676
                room_commands,
677
                room_command_receiver: Some(room_command_receiver),
678
                global_account_gates: HashMap::new(),
679
                room_account_gates: HashMap::new(),
680
                media_flights: Arc::new(Mutex::new(HashMap::new())),
681
            },
682
            handle,
683
            events,
684
        ))
685
    }
686
687
    pub async fn run(mut self) -> Result<(), EngineError> {
688
        let sender = self.port.event_sender();
689
        let user_id = self
690
            .client
691
            .user_id()
692
            .expect("authentication was checked during construction")
693
            .to_owned();
694
        if sender.send(Event::Authenticated { user_id }).await.is_err() {
695
            self.shutdown().await;
696
            return Err(EngineError::EventReceiverClosed);
697
        }
698
        self.port
699
            .connection_sender()
700
            .send_replace(ConnectionState::Connecting);
701
        if sender
702
            .send(Event::ConnectionChanged(ConnectionState::Connecting))
703
            .await
704
            .is_err()
705
        {
706
            self.shutdown().await;
707
            return Err(EngineError::EventReceiverClosed);
708
        }
709
        self.start_watchers();
710
        self.start_domain_workers();
711
        self.sync.start().await;
712
        let mut terminal_error = None;
713
714
        loop {
715
            let activity = tokio::select! {
716
                biased;
717
                _ = sender.closed() => {
718
                    terminal_error = Some(EngineError::EventReceiverClosed);
719
                    break;
720
                }
721
                request = self.port.recv() => EngineActivity::Request(request.map(Box::new)),
722
                completed = self.tasks.join_next(), if !self.tasks.is_empty() => {
723
                    EngineActivity::Task(completed)
724
                }
725
                event = self.room_actor_events.recv() => EngineActivity::RoomActor(event),
726
            };
727
            match activity {
728
                EngineActivity::Request(Some(request)) => {
729
                    let Some(envelope) = self.dispatch_request(*request).await else {
730
                        continue;
731
                    };
732
                    let id = envelope.id;
733
                    let command = envelope.command;
734
                    if matches!(command, Command::Lifecycle(LifecycleCommand::Shutdown)) {
735
                        let _ =
736
                            emit_command_result(&sender, id, CommandKind::Lifecycle, Ok(())).await;
737
                        break;
738
                    }
739
                    let kind = command.kind();
740
                    tracing::debug!(?id, ?kind, "dispatching command");
741
                    let result = self.execute(id, command).await;
742
                    if Self::emit_sync_result(&sender, id, kind, result)
743
                        .await
744
                        .is_err()
745
                    {
746
                        terminal_error = Some(EngineError::EventReceiverClosed);
747
                        break;
748
                    }
749
                }
750
                EngineActivity::Request(None) => break,
751
                EngineActivity::Task(Some((context, result))) => {
752
                    if let Err(error) = result
753
                        && error.is_panic()
754
                    {
755
                        match context {
756
                            TaskContext::Command { id, kind } => {
757
                                if sender
758
                                    .send(Event::CommandFailed(CommandFailure {
759
                                        id,
760
                                        command: kind,
761
                                        kind: CommandErrorKind::Other,
762
                                        message: "command task panicked".to_owned(),
763
                                    }))
764
                                    .await
765
                                    .is_err()
766
                                {
767
                                    terminal_error = Some(EngineError::EventReceiverClosed);
768
                                    break;
769
                                }
770
                            }
771
                            context => {
772
                                terminal_error = Some(EngineError::TaskPanicked {
773
                                    task: context.label(),
774
                                });
775
                                break;
776
                            }
777
                        }
778
                    }
779
                }
780
                EngineActivity::Task(None) => {}
781
                EngineActivity::RoomActor(Some(event)) => {
782
                    if let Err(error) = self.handle_room_actor_event(event).await {
783
                        terminal_error = Some(error);
784
                        break;
785
                    }
786
                }
787
                EngineActivity::RoomActor(None) => {
788
                    terminal_error = Some(EngineError::TaskPanicked {
789
                        task: "room actor event channel".to_owned(),
790
                    });
791
                    break;
792
                }
793
            }
794
        }
795
796
        if let Some(error) = terminal_error.as_ref() {
797
            let _ = sender.send(Event::Failed(error.failure())).await;
798
        }
799
        self.shutdown().await;
800
        let _ = sender.send(Event::Stopped).await;
801
        if let Some(error) = terminal_error {
802
            Err(error)
803
        } else {
804
            Ok(())
805
        }
806
    }
807
808
    async fn emit_sync_result(
809
        sender: &EventSender,
810
        id: CommandId,
811
        kind: CommandKind,
812
        result: Result<bool, CommandError>,
813
    ) -> Result<(), SendEventError> {
814
        match result {
815
            Ok(true) => Ok(()),
816
            Ok(false) => emit_command_result(sender, id, kind, Ok(())).await,
817
            Err(error) => {
818
                tracing::warn!(?id, ?kind, error = ?error, "command failed");
819
                emit_command_result(sender, id, kind, Err(error)).await?;
820
                Ok(())
821
            }
822
        }
823
    }
824
825
    fn spawn_logout(&mut self, id: CommandId) {
826
        let events = self.port.event_sender();
827
        let client = self.client.clone();
828
        self.tasks.spawn(
829
            TaskContext::Command {
830
                id,
831
                kind: CommandKind::Account,
832
            },
833
            async move {
834
                match client
835
                    .logout()
836
                    .await
837
                    .map(|_| ())
838
                    .map_err(|source| CommandError::from_sdk("logout failed", source))
839
                {
840
                    Ok(()) => {
841
                        let _ =
842
                            emit_command_result(&events, id, CommandKind::Account, Ok(())).await;
843
                        let _ = events.send(Event::LoggedOut).await;
844
                    }
845
                    Err(error) => {
846
                        let _ = emit_command_result(&events, id, CommandKind::Account, Err(error))
847
                            .await;
848
                    }
849
                }
850
            },
851
        );
852
    }
853
854
    async fn dispatch_request(&mut self, request: DriverRequest) -> Option<CommandEnvelope> {
855
        match request {
856
            DriverRequest::Command(command) => Some(command),
857
            DriverRequest::DeleteDevices {
858
                id,
859
                device_ids,
860
                auth,
861
                reply,
862
            } => {
863
                self.spawn_delete_devices(id, device_ids, auth, Some(reply));
864
                None
865
            }
866
            DriverRequest::RoomResult { id, command, reply } => {
867
                if let Err(error) = self.spawn_room_command_with_reply(id, command, Some(reply)) {
868
                    tracing::warn!(?id, error = ?error, "room result request failed");
869
                }
870
                None
871
            }
872
            DriverRequest::SetGlobalAccountData {
873
                event_type,
874
                content,
875
                reply,
876
            } => {
877
                self.spawn_global_account_data_write(event_type, content, reply);
878
                None
879
            }
880
            DriverRequest::SetRoomAccountData {
881
                room_id,
882
                event_type,
883
                content,
884
                reply,
885
            } => {
886
                self.spawn_room_account_data_write(room_id, event_type, content, reply);
887
                None
888
            }
889
        }
890
    }
891
892
    fn spawn_mark_read(&mut self, id: CommandId, timeline_id: TimelineId, event_id: OwnedEventId) {
893
        let events = self.port.event_sender();
894
        let client = self.client.clone();
895
        self.tasks.spawn(
896
            TaskContext::Command {
897
                id,
898
                kind: CommandKind::Room,
899
            },
900
            async move {
901
                let result = mark_read(&client, &timeline_id, event_id).await;
902
                let _ = emit_command_result(&events, id, CommandKind::Room, result).await;
903
            },
904
        );
905
    }
906
907
    fn spawn_typing(&mut self, id: CommandId, room_id: OwnedRoomId, typing: bool) {
908
        let events = self.port.event_sender();
909
        let client = self.client.clone();
910
        self.tasks.spawn(
911
            TaskContext::Command {
912
                id,
913
                kind: CommandKind::Room,
914
            },
915
            async move {
916
                let result = set_typing(&client, &room_id, typing).await;
917
                let _ = emit_command_result(&events, id, CommandKind::Room, result).await;
918
            },
919
        );
920
    }
921
922
    fn spawn_cancel_send(
923
        &mut self,
924
        id: CommandId,
925
        room_id: OwnedRoomId,
926
        transaction_id: matrix_sdk::ruma::OwnedTransactionId,
927
    ) {
928
        let events = self.port.event_sender();
929
        let client = self.client.clone();
930
        self.tasks.spawn(
931
            TaskContext::Command {
932
                id,
933
                kind: CommandKind::Room,
934
            },
935
            async move {
936
                let result = cancel_send(&client, &room_id, &transaction_id).await;
937
                let _ = emit_command_result(&events, id, CommandKind::Room, result).await;
938
            },
939
        );
940
    }
941
942
    fn spawn_retry_send(
943
        &mut self,
944
        id: CommandId,
945
        room_id: OwnedRoomId,
946
        transaction_id: matrix_sdk::ruma::OwnedTransactionId,
947
    ) {
948
        let events = self.port.event_sender();
949
        let client = self.client.clone();
950
        self.tasks.spawn(
951
            TaskContext::Command {
952
                id,
953
                kind: CommandKind::Room,
954
            },
955
            async move {
956
                let result = retry_send(&client, &room_id, &transaction_id).await;
957
                let _ = emit_command_result(&events, id, CommandKind::Room, result).await;
958
            },
959
        );
960
    }
961
962
    fn spawn_delete_devices(
963
        &mut self,
964
        id: CommandId,
965
        device_ids: Vec<OwnedDeviceId>,
966
        auth: Option<InteractiveAuth>,
967
        reply: Option<oneshot::Sender<Result<InteractiveAuthOutcome, CommandResultError>>>,
968
    ) {
969
        let events = self.port.event_sender();
970
        let client = self.client.clone();
971
        self.tasks.spawn(
972
            TaskContext::Command {
973
                id,
974
                kind: CommandKind::Account,
975
            },
976
            async move {
977
                let auth = match auth {
978
                    Some(auth) => match auth.into_sdk(
979
                        client
980
                            .user_id()
981
                            .expect("authentication was checked during construction"),
982
                    ) {
983
                        Ok(auth) => Some(auth),
984
                        Err(message) => {
985
                            let failure = CommandFailure {
986
                                id,
987
                                command: CommandKind::Account,
988
                                kind: CommandErrorKind::Other,
989
                                message: format!(
990
                                    "interactive authentication data is invalid, {message}"
991
                                ),
992
                            };
993
                            if let Some(reply) = reply {
994
                                let _ =
995
                                    reply.send(Err(CommandResultError::Failed(failure.clone())));
996
                            } else {
997
                                let _ = events.send(Event::CommandFailed(failure)).await;
998
                            }
999
                            return;
1000
                        }
1001
                    },
1002
                    None => None,
1003
                };
1004
                let result = client.delete_devices(&device_ids, auth).await;
1005
1006
                match result {
1007
                    Ok(_) => {
1008
                        if let Some(reply) = reply {
1009
                            let _ = reply.send(Ok(InteractiveAuthOutcome::Completed));
1010
                        } else {
1011
                            let _ = emit_command_result(&events, id, CommandKind::Account, Ok(()))
1012
                                .await;
1013
                        }
1014
                    }
1015
                    Err(source) => {
1016
                        if let Some(info) = source.as_uiaa_response() {
1017
                            let challenge = InteractiveAuthChallenge::from(info);
1018
                            if let Some(reply) = reply {
1019
                                let _ =
1020
                                    reply.send(Ok(InteractiveAuthOutcome::Challenge(challenge)));
1021
                            } else {
1022
                                let _ = events
1023
                                    .send(Event::InteractiveAuthRequired {
1024
                                        id,
1025
                                        command: CommandKind::Account,
1026
                                        challenge: challenge.clone(),
1027
                                    })
1028
                                    .await;
1029
                                let _ = events
1030
                                    .send(Event::CommandFailed(CommandFailure {
1031
                                        id,
1032
                                        command: CommandKind::Account,
1033
                                        kind: CommandErrorKind::InteractiveAuthRequired,
1034
                                        message: "interactive authentication is required"
1035
                                            .to_owned(),
1036
                                    }))
1037
                                    .await;
1038
                            }
1039
                        } else {
1040
                            let error = CommandError::from_http("deleting devices failed", source);
1041
                            let failure = CommandFailure {
1042
                                id,
1043
                                command: CommandKind::Account,
1044
                                kind: error.kind,
1045
                                message: error.to_string(),
1046
                            };
1047
                            if let Some(reply) = reply {
1048
                                let _ =
1049
                                    reply.send(Err(CommandResultError::Failed(failure.clone())));
1050
                            } else {
1051
                                let _ = events.send(Event::CommandFailed(failure)).await;
1052
                            }
1053
                        }
1054
                    }
1055
                }
1056
            },
1057
        );
1058
    }
1059
1060
    fn spawn_rename_device(
1061
        &mut self,
1062
        id: CommandId,
1063
        device_id: OwnedDeviceId,
1064
        display_name: String,
1065
    ) {
1066
        let events = self.port.event_sender();
1067
        let client = self.client.clone();
1068
        self.tasks.spawn(
1069
            TaskContext::Command {
1070
                id,
1071
                kind: CommandKind::Account,
1072
            },
1073
            async move {
1074
                match client.rename_device(&device_id, &display_name).await {
1075
                    Ok(_) => {
1076
                        let _ = events
1077
                            .send(Event::CommandSucceeded {
1078
                                id,
1079
                                command: CommandKind::Account,
1080
                            })
1081
                            .await;
1082
                    }
1083
                    Err(source) => {
1084
                        let error = CommandError::from_http("renaming device failed", source);
1085
                        let _ = events
1086
                            .send(Event::CommandFailed(CommandFailure {
1087
                                id,
1088
                                command: CommandKind::Account,
1089
                                kind: error.kind,
1090
                                message: error.to_string(),
1091
                            }))
1092
                            .await;
1093
                    }
1094
                }
1095
            },
1096
        );
1097
    }
1098
1099
    fn spawn_global_account_data_write(
1100
        &mut self,
1101
        event_type: GlobalAccountDataEventType,
1102
        content: Raw<AnyGlobalAccountDataEventContent>,
1103
        reply: oneshot::Sender<Result<(), ClientQueryError>>,
1104
    ) {
1105
        let (done, next) = oneshot::channel();
1106
        let previous = self
1107
            .global_account_gates
1108
            .insert(event_type.to_string(), next);
1109
        let client = self.client.clone();
1110
        self.tasks.spawn(
1111
            TaskContext::Watcher("global account data writer"),
1112
            async move {
1113
                if let Some(previous) = previous {
1114
                    let _ = previous.await;
1115
                }
1116
                let result = client
1117
                    .account()
1118
                    .set_account_data_raw(event_type, content)
1119
                    .await
1120
                    .map(|_| ())
1121
                    .map_err(ClientQueryError::Sdk);
1122
                let _ = reply.send(result);
1123
                let _ = done.send(());
1124
            },
1125
        );
1126
    }
1127
1128
    fn spawn_room_account_data_write(
1129
        &mut self,
1130
        room_id: OwnedRoomId,
1131
        event_type: RoomAccountDataEventType,
1132
        content: Raw<matrix_sdk::ruma::events::AnyRoomAccountDataEventContent>,
1133
        reply: oneshot::Sender<Result<(), ClientQueryError>>,
1134
    ) {
1135
        let (done, next) = oneshot::channel();
1136
        let previous = self
1137
            .room_account_gates
1138
            .insert((room_id.clone(), event_type.to_string()), next);
1139
        let client = self.client.clone();
1140
        self.tasks.spawn(
1141
            TaskContext::Watcher("room account data writer"),
1142
            async move {
1143
                if let Some(previous) = previous {
1144
                    let _ = previous.await;
1145
                }
1146
                let result = match client.get_room(&room_id) {
1147
                    Some(room) => room
1148
                        .set_account_data_raw(event_type, content)
1149
                        .await
1150
                        .map(|_| ())
1151
                        .map_err(ClientQueryError::Sdk),
1152
                    None => Err(ClientQueryError::RoomUnknown(room_id)),
1153
                };
1154
                let _ = reply.send(result);
1155
                let _ = done.send(());
1156
            },
1157
        );
1158
    }
1159
1160
    fn start_domain_workers(&mut self) {
1161
        let mut notification_commands = self
1162
            .notification_command_receiver
1163
            .take()
1164
            .expect("notification worker has not started");
1165
        let notification_client = self.client.clone();
1166
        let notification_events = self.port.event_sender();
1167
        self.tasks.spawn(
1168
            TaskContext::Watcher("notification command worker"),
1169
            async move {
1170
                while let Some(CommandJob { id, command }) = notification_commands.recv().await {
1171
                    let result = AssertUnwindSafe(execute_notification_command(
1172
                        &notification_client,
1173
                        command,
1174
                    ))
1175
                    .catch_unwind()
1176
                    .await
1177
                    .unwrap_or_else(|_| {
1178
                        Err(CommandError::new(
1179
                            CommandErrorKind::Other,
1180
                            "notification command panicked",
1181
                        ))
1182
                    });
1183
                    let _ = emit_command_result(
1184
                        &notification_events,
1185
                        id,
1186
                        CommandKind::Notification,
1187
                        result,
1188
                    )
1189
                    .await;
1190
                }
1191
            },
1192
        );
1193
1194
        let mut encryption_commands = self
1195
            .encryption_command_receiver
1196
            .take()
1197
            .expect("encryption worker has not started");
1198
        let encryption_client = self.client.clone();
1199
        let encryption_events = self.port.event_sender();
1200
        let verification = self.verification.clone();
1201
        self.tasks.spawn(
1202
            TaskContext::Watcher("encryption command worker"),
1203
            async move {
1204
                while let Some(work) = encryption_commands.recv().await {
1205
                    match work {
1206
                        EncryptionWork::Command(CommandJob { id, command }) => {
1207
                            if matches!(
1208
                                command,
1209
                                EncryptionCommand::Recover(_)
1210
                                    | EncryptionCommand::RecoverKeyBackup(_)
1211
                            ) {
1212
                                let result = AssertUnwindSafe(execute_encryption_command(
1213
                                    &encryption_client,
1214
                                    &encryption_events,
1215
                                    &mut VerificationController::default(),
1216
                                    command,
1217
                                ))
1218
                                .catch_unwind()
1219
                                .await
1220
                                .unwrap_or_else(|_| {
1221
                                    Err(CommandError::new(
1222
                                        CommandErrorKind::Other,
1223
                                        "encryption command panicked",
1224
                                    ))
1225
                                });
1226
                                let _ = emit_command_result(
1227
                                    &encryption_events,
1228
                                    id,
1229
                                    CommandKind::Encryption,
1230
                                    result,
1231
                                )
1232
                                .await;
1233
                                continue;
1234
                            }
1235
                            let is_key_backup = matches!(
1236
                                command,
1237
                                EncryptionCommand::CreateKeyBackup
1238
                                    | EncryptionCommand::EnableKeyBackup
1239
                                    | EncryptionCommand::DisableKeyBackup { .. }
1240
                            );
1241
                            let result = if is_key_backup {
1242
                                AssertUnwindSafe(execute_key_backup_command(
1243
                                    &encryption_client,
1244
                                    command,
1245
                                ))
1246
                                .catch_unwind()
1247
                                .await
1248
                                .unwrap_or_else(|_| {
1249
                                    Err(CommandError::new(
1250
                                        CommandErrorKind::Other,
1251
                                        "encryption command panicked",
1252
                                    ))
1253
                                })
1254
                            } else {
1255
                                let mut verification = verification.lock().await;
1256
                                let result = AssertUnwindSafe(execute_encryption_command(
1257
                                    &encryption_client,
1258
                                    &encryption_events,
1259
                                    &mut verification,
1260
                                    command,
1261
                                ))
1262
                                .catch_unwind()
1263
                                .await
1264
                                .unwrap_or_else(|_| {
1265
                                    Err(CommandError::new(
1266
                                        CommandErrorKind::Other,
1267
                                        "encryption command panicked",
1268
                                    ))
1269
                                });
1270
                                drop(verification);
1271
                                result
1272
                            };
1273
                            let _ = emit_command_result(
1274
                                &encryption_events,
1275
                                id,
1276
                                CommandKind::Encryption,
1277
                                result,
1278
                            )
1279
                            .await;
1280
                        }
1281
                    }
1282
                }
1283
            },
1284
        );
1285
1286
        let room_commands = self
1287
            .room_command_receiver
1288
            .take()
1289
            .expect("room command worker has not started");
1290
        let room_client = self.client.clone();
1291
        let room_events = self.port.event_sender();
1292
        self.tasks.spawn(
1293
            TaskContext::Watcher("room command worker"),
1294
            run_room_command_worker(
1295
                room_client,
1296
                room_events,
1297
                room_commands,
1298
                self.room_command_capacity,
1299
            ),
1300
        );
1301
    }
1302
1303
    fn start_watchers(&mut self) {
1304
        let mut session_changes = self
1305
            .session_changes
1306
            .take()
1307
            .expect("session watcher has not started");
1308
        let session_sender = self.port.event_sender();
1309
        self.tasks
1310
            .spawn(TaskContext::Watcher("session change watcher"), async move {
1311
                loop {
1312
                    let event = match session_changes.recv().await {
1313
                        Ok(SessionChange::TokensRefreshed) => Event::SessionTokensChanged,
1314
                        Ok(SessionChange::UnknownToken(_)) => Event::SessionAccessRejected,
1315
                        Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => {
1316
                            Event::SessionTokensChanged
1317
                        }
1318
                        Err(tokio::sync::broadcast::error::RecvError::Closed) => break,
1319
                    };
1320
                    if session_sender.send(event).await.is_err() {
1321
                        break;
1322
                    }
1323
                }
1324
            });
1325
1326
        let mut sync_states = self.sync.state();
1327
        let state_sender = self.port.event_sender();
1328
        let connection_sender = self.port.connection_sender();
1329
        self.tasks.spawn(
1330
            TaskContext::Watcher("connection state watcher"),
1331
            async move {
1332
                while let Some(state) = sync_states.next().await {
1333
                    let state = match state {
1334
                        SyncState::Idle => ConnectionState::Idle,
1335
                        SyncState::Running => ConnectionState::Online,
1336
                        SyncState::Offline => ConnectionState::Offline,
1337
                        SyncState::Terminated => ConnectionState::Stopped,
1338
                        SyncState::Error(error) => {
1339
                            tracing::warn!(error = %error, "sync service reported an error");
1340
                            ConnectionState::Offline
1341
                        }
1342
                    };
1343
                    connection_sender.send_replace(state);
1344
                    if state_sender
1345
                        .send(Event::ConnectionChanged(state))
1346
                        .await
1347
                        .is_err()
1348
                    {
1349
                        tracing::debug!(
1350
                            "connection watcher stopped because the event receiver closed"
1351
                        );
1352
                        break;
1353
                    }
1354
                }
1355
                tracing::debug!("connection watcher stopped because the sync state stream ended");
1356
            },
1357
        );
1358
1359
        let client = self.client.clone();
1360
        let encryption_sender = self.port.event_sender();
1361
        self.tasks.spawn(
1362
            TaskContext::Watcher("encryption state watcher"),
1363
            async move {
1364
            let Some(device_id) = client.device_id().map(ToOwned::to_owned) else {
1365
                tracing::warn!("encryption watcher stopped because the client has no device id");
1366
                return;
1367
            };
1368
            let recovery = client.encryption().recovery();
1369
            let backups = client.encryption().backups();
1370
            let mut recovery_stream = recovery.state_stream();
1371
            let mut backup_stream = backups.state_stream();
1372
            let mut verification_stream = client.encryption().verification_state();
1373
            let mut recovery_state = recovery.state();
1374
            let mut backup_state = backups.state();
1375
            let mut verification_state = verification_stream.get();
1376
            if encryption_sender
1377
                .send(Event::EncryptionChanged(encryption_state(
1378
                    device_id.clone(),
1379
                    recovery_state,
1380
                    verification_state,
1381
                    backup_state,
1382
                )))
1383
                .await
1384
                .is_err()
1385
            {
1386
                tracing::debug!(
1387
                    "encryption watcher stopped because the event receiver closed"
1388
                );
1389
                return;
1390
            }
1391
            loop {
1392
                tokio::select! {
1393
                    update = recovery_stream.next() => {
1394
                        let Some(update) = update else {
1395
                            tracing::debug!("encryption watcher stopped because the recovery stream ended");
1396
                            break;
1397
                        };
1398
                        recovery_state = update;
1399
                    }
1400
                    update = verification_stream.next() => {
1401
                        let Some(update) = update else {
1402
                            tracing::debug!("encryption watcher stopped because the verification stream ended");
1403
                            break;
1404
                        };
1405
                        verification_state = update;
1406
                    }
1407
                    update = backup_stream.next() => {
1408
                        let Some(update) = update else {
1409
                            tracing::debug!("backup watcher stopped because the backup state stream ended");
1410
                            break;
1411
                        };
1412
                        backup_state = update.unwrap_or_else(|_| backups.state());
1413
                    }
1414
                }
1415
                if encryption_sender
1416
                    .send(Event::EncryptionChanged(encryption_state(
1417
                        device_id.clone(),
1418
                        recovery_state,
1419
                        verification_state,
1420
                        backup_state,
1421
                    )))
1422
                    .await
1423
                    .is_err()
1424
                {
1425
                    tracing::debug!(
1426
                        "encryption watcher stopped because the event receiver closed"
1427
                    );
1428
                    break;
1429
                }
1430
            }
1431
            },
1432
        );
1433
1434
        let client = self.client.clone();
1435
        let room_sender = self.port.event_sender();
1436
        self.tasks.spawn(
1437
            TaskContext::Watcher("room list watcher"),
1438
            async move {
1439
            let (_, mut room_stream) = client.rooms_stream();
1440
            let mut info_stream = client.room_info_notable_update_receiver();
1441
            if room_sender.send(Event::RoomsChanged).await.is_err() {
1442
                tracing::debug!("room watcher stopped because the event receiver closed");
1443
                return;
1444
            }
1445
            loop {
1446
                tokio::select! {
1447
                    update = room_stream.next() => {
1448
                        if update.is_none() {
1449
                            tracing::debug!("room watcher stopped because the room stream ended");
1450
                            break;
1451
                        }
1452
                    }
1453
                    update = info_stream.recv() => {
1454
                        if update.is_err() && info_stream.is_closed() {
1455
                            tracing::debug!("room watcher stopped because the room info stream ended");
1456
                            break;
1457
                        }
1458
                    }
1459
                }
1460
                if room_sender.send(Event::RoomsChanged).await.is_err() {
1461
                    tracing::debug!("room watcher stopped because the event receiver closed");
1462
                    break;
1463
                }
1464
            }
1465
            },
1466
        );
1467
1468
        let client = self.client.clone();
1469
        let notification_sender = self.port.event_sender();
1470
        self.tasks.spawn(
1471
            TaskContext::Watcher("notification settings watcher"),
1472
            async move {
1473
                let settings = client.notification_settings().await;
1474
                let mut changes = settings.subscribe_to_changes();
1475
                while let Ok(()) | Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) =
1476
                    changes.recv().await
1477
                {
1478
                    if notification_sender
1479
                        .send(Event::NotificationSettingsChanged)
1480
                        .await
1481
                        .is_err()
1482
                    {
1483
                        break;
1484
                    }
1485
                }
1486
            },
1487
        );
1488
    }
1489
1490
    async fn execute(&mut self, id: CommandId, command: Command) -> Result<bool, CommandError> {
1491
        match command {
1492
            Command::Account(AccountCommand::Logout) => {
1493
                self.spawn_logout(id);
1494
                Ok(true)
1495
            }
1496
            Command::Account(AccountCommand::RenameDevice {
1497
                device_id,
1498
                display_name,
1499
            }) => {
1500
                self.spawn_rename_device(id, device_id, display_name);
1501
                Ok(true)
1502
            }
1503
            Command::Account(AccountCommand::DeleteDevices { device_ids, auth }) => {
1504
                self.spawn_delete_devices(id, device_ids, auth, None);
1505
                Ok(true)
1506
            }
1507
            Command::Room(RoomCommand::Open(room_id)) => self.open_room(id, room_id).await,
1508
            Command::Room(RoomCommand::Close(room_id)) => self.close_room(id, &room_id).await,
1509
            Command::Room(RoomCommand::MarkRead {
1510
                timeline_id,
1511
                event_id,
1512
            }) => {
1513
                self.spawn_mark_read(id, timeline_id, event_id);
1514
                Ok(true)
1515
            }
1516
            Command::Room(RoomCommand::SetTyping { room_id, typing }) => {
1517
                self.spawn_typing(id, room_id, typing);
1518
                Ok(true)
1519
            }
1520
            Command::Room(RoomCommand::Join { room, via }) => {
1521
                self.spawn_room_command(id, RoomCommand::Join { room, via })
1522
            }
1523
            Command::Room(RoomCommand::Leave(room_id)) => {
1524
                self.spawn_room_command(id, RoomCommand::Leave(room_id))
1525
            }
1526
            Command::Room(RoomCommand::Forget(room_id)) => {
1527
                self.spawn_room_command(id, RoomCommand::Forget(room_id))
1528
            }
1529
            Command::Room(RoomCommand::AcceptInvite(room_id)) => {
1530
                self.spawn_room_command(id, RoomCommand::AcceptInvite(room_id))
1531
            }
1532
            Command::Room(RoomCommand::RejectInvite(room_id)) => {
1533
                self.spawn_room_command(id, RoomCommand::RejectInvite(room_id))
1534
            }
1535
            Command::Room(command @ RoomCommand::InviteUser { .. })
1536
            | Command::Room(command @ RoomCommand::KickUser { .. })
1537
            | Command::Room(command @ RoomCommand::BanUser { .. })
1538
            | Command::Room(command @ RoomCommand::UnbanUser { .. })
1539
            | Command::Room(command @ RoomCommand::SetPowerLevels { .. })
1540
            | Command::Room(command @ RoomCommand::SetUserPowerLevel { .. }) => {
1541
                self.spawn_room_command(id, command)
1542
            }
1543
            Command::Room(RoomCommand::Create(options)) => {
1544
                self.spawn_room_command(id, RoomCommand::Create(options))
1545
            }
1546
            Command::Room(RoomCommand::CreateDirect(user_id)) => {
1547
                self.spawn_room_command(id, RoomCommand::CreateDirect(user_id))
1548
            }
1549
            Command::Room(RoomCommand::CancelSend {
1550
                room_id,
1551
                transaction_id,
1552
            }) => {
1553
                self.spawn_cancel_send(id, room_id, transaction_id);
1554
                Ok(true)
1555
            }
1556
            Command::Room(RoomCommand::RetrySend {
1557
                room_id,
1558
                transaction_id,
1559
            }) => {
1560
                self.spawn_retry_send(id, room_id, transaction_id);
1561
                Ok(true)
1562
            }
1563
            Command::Timeline(command) => self.route_timeline(id, command),
1564
            Command::Media(MediaCommand::Fetch { id: media_id, size }) => {
1565
                self.spawn_media_fetch(id, media_id, size);
1566
                Ok(true)
1567
            }
1568
            Command::Media(MediaCommand::Upload {
1569
                timeline_id,
1570
                filename,
1571
                mimetype,
1572
                bytes,
1573
                caption,
1574
            }) => {
1575
                let mut upload = AttachmentUpload::new(timeline_id, filename, mimetype, bytes);
1576
                upload.caption = caption;
1577
                self.spawn_media_upload(id, upload).await.map(|()| true)
1578
            }
1579
            Command::Media(MediaCommand::UploadAttachment(upload)) => {
1580
                self.spawn_media_upload(id, upload).await.map(|()| true)
1581
            }
1582
            Command::Encryption(command) => {
1583
                self.encryption_commands
1584
                    .try_send(EncryptionWork::Command(CommandJob { id, command }))
1585
                    .map_err(|error| domain_queue_error(error, "encryption"))?;
1586
                Ok(true)
1587
            }
1588
            Command::Notification(command) => {
1589
                self.notification_commands
1590
                    .try_send(CommandJob { id, command })
1591
                    .map_err(|error| domain_queue_error(error, "notification"))?;
1592
                Ok(true)
1593
            }
1594
            Command::Lifecycle(LifecycleCommand::Shutdown) => Ok(false),
1595
        }
1596
    }
1597
1598
    async fn open_room(
1599
        &mut self,
1600
        id: CommandId,
1601
        room_id: OwnedRoomId,
1602
    ) -> Result<bool, CommandError> {
1603
        match self.rooms.get_mut(&room_id) {
1604
            Some(RoomActorState::Opening { sender, .. }) => {
1605
                send_room_actor_request(sender, RoomActorRequest::Opened { id })?;
1606
                return Ok(true);
1607
            }
1608
            Some(RoomActorState::Open { .. }) => return Ok(false),
1609
            Some(RoomActorState::Closing { pending, .. }) => {
1610
                if pending.len() >= self.room_command_capacity {
1611
                    return Err(CommandError::new(
1612
                        CommandErrorKind::RoomBusy,
1613
                        "room lifecycle queue is full",
1614
                    ));
1615
                }
1616
                pending.push_back(DeferredRoomLifecycle::Open(id));
1617
                return Ok(true);
1618
            }
1619
            None => {}
1620
        }
1621
        self.start_room_actor(room_id, vec![id]).await?;
1622
        Ok(true)
1623
    }
1624
1625
    async fn start_room_actor(
1626
        &mut self,
1627
        room_id: OwnedRoomId,
1628
        open_ids: Vec<CommandId>,
1629
    ) -> Result<(), CommandError> {
1630
        let room = self.client.get_room(&room_id).ok_or_else(|| {
1631
            CommandError::new(
1632
                CommandErrorKind::RoomUnknown,
1633
                "room is not available in the local store",
1634
            )
1635
        })?;
1636
        self.next_room_generation = self.next_room_generation.wrapping_add(1);
1637
        let generation = self.next_room_generation;
1638
        let (sender, receiver) = mpsc::channel(self.room_command_capacity);
1639
        let actor = RoomActor {
1640
            room_id: room_id.clone(),
1641
            generation,
1642
            room,
1643
            receiver,
1644
            events: self.port.event_sender(),
1645
            engine_events: self.room_actor_event_sender.clone(),
1646
            media: self.media.clone(),
1647
            initial_history_limit: self.initial_history_limit,
1648
            open_ids,
1649
        };
1650
        let current_user_id = self.client.user_id().map(ToOwned::to_owned);
1651
        self.rooms.insert(
1652
            room_id.clone(),
1653
            RoomActorState::Opening { generation, sender },
1654
        );
1655
        let actor_room_id = room_id.clone();
1656
        let engine_events = self.room_actor_event_sender.clone();
1657
        self.tasks.spawn(
1658
            TaskContext::RoomActor {
1659
                room_id: room_id.clone(),
1660
                generation,
1661
            },
1662
            async move {
1663
                actor.run(current_user_id).await;
1664
                let _ = engine_events.send(RoomActorEvent::Exited {
1665
                    room_id: actor_room_id,
1666
                    generation,
1667
                });
1668
            },
1669
        );
1670
        self.update_room_subscriptions().await;
1671
        Ok(())
1672
    }
1673
1674
    fn spawn_room_command(
1675
        &mut self,
1676
        id: CommandId,
1677
        command: RoomCommand,
1678
    ) -> Result<bool, CommandError> {
1679
        self.spawn_room_command_with_reply(id, command, None)?;
1680
        Ok(true)
1681
    }
1682
1683
    fn spawn_room_command_with_reply(
1684
        &mut self,
1685
        id: CommandId,
1686
        command: RoomCommand,
1687
        reply: Option<oneshot::Sender<Result<OwnedRoomId, CommandResultError>>>,
1688
    ) -> Result<(), CommandError> {
1689
        let job = RoomJob { id, command, reply };
1690
        self.room_commands.try_send(job).map_err(|error| {
1691
            let (job, kind, message) = match error {
1692
                mpsc::error::TrySendError::Full(job) => (
1693
                    job,
1694
                    CommandErrorKind::QueueFull,
1695
                    "room command queue is full",
1696
                ),
1697
                mpsc::error::TrySendError::Closed(job) => (
1698
                    job,
1699
                    CommandErrorKind::Other,
1700
                    "room command worker has stopped",
1701
                ),
1702
            };
1703
            let error = CommandError::new(kind, message);
1704
            if let Some(reply) = job.reply {
1705
                let _ = reply.send(Err(CommandResultError::Failed(CommandFailure {
1706
                    id: job.id,
1707
                    command: CommandKind::Room,
1708
                    kind: error.kind,
1709
                    message: error.to_string(),
1710
                })));
1711
            }
1712
            error
1713
        })
1714
    }
1715
1716
    fn route_timeline(
1717
        &mut self,
1718
        id: CommandId,
1719
        command: TimelineCommand,
1720
    ) -> Result<bool, CommandError> {
1721
        let room_id = command.room_id();
1722
        let sender = self
1723
            .rooms
1724
            .get(room_id)
1725
            .and_then(RoomActorState::sender)
1726
            .ok_or_else(|| CommandError::new(CommandErrorKind::RoomNotOpen, "room is not open"))?;
1727
        send_room_actor_request(sender, RoomActorRequest::Execute { id, command })?;
1728
        Ok(true)
1729
    }
1730
1731
    async fn close_room(&mut self, id: CommandId, room_id: &RoomId) -> Result<bool, CommandError> {
1732
        let Some(state) = self.rooms.get_mut(room_id) else {
1733
            return Ok(false);
1734
        };
1735
        if let RoomActorState::Closing { pending, .. } = state {
1736
            if pending.len() >= self.room_command_capacity {
1737
                return Err(CommandError::new(
1738
                    CommandErrorKind::RoomBusy,
1739
                    "room lifecycle queue is full",
1740
                ));
1741
            }
1742
            pending.push_back(DeferredRoomLifecycle::Close(id));
1743
            return Ok(true);
1744
        }
1745
        let Some(sender) = state.sender().cloned() else {
1746
            return Ok(false);
1747
        };
1748
        let generation = state.generation();
1749
        send_room_actor_request(&sender, RoomActorRequest::Close { id })?;
1750
        self.rooms.insert(
1751
            room_id.to_owned(),
1752
            RoomActorState::Closing {
1753
                generation,
1754
                pending: VecDeque::new(),
1755
            },
1756
        );
1757
        self.update_room_subscriptions().await;
1758
        Ok(true)
1759
    }
1760
1761
    async fn handle_room_actor_event(&mut self, event: RoomActorEvent) -> Result<(), EngineError> {
1762
        match event {
1763
            RoomActorEvent::Ready {
1764
                room_id,
1765
                generation,
1766
            } => {
1767
                let stale = self
1768
                    .rooms
1769
                    .get(&room_id)
1770
                    .is_none_or(|state| state.generation() != generation);
1771
                if stale {
1772
                    return Ok(());
1773
                }
1774
                let Some(state) = self.rooms.remove(&room_id) else {
1775
                    return Ok(());
1776
                };
1777
                match state {
1778
                    RoomActorState::Opening {
1779
                        generation: current,
1780
                        sender,
1781
                    } if current == generation => {
1782
                        self.rooms
1783
                            .insert(room_id, RoomActorState::Open { generation, sender });
1784
                    }
1785
                    state => {
1786
                        self.rooms.insert(room_id, state);
1787
                    }
1788
                }
1789
            }
1790
            RoomActorEvent::Exited {
1791
                room_id,
1792
                generation,
1793
            } => {
1794
                if self
1795
                    .rooms
1796
                    .get(&room_id)
1797
                    .is_none_or(|state| state.generation() != generation)
1798
                {
1799
                    return Ok(());
1800
                }
1801
                let state = self
1802
                    .rooms
1803
                    .remove(&room_id)
1804
                    .expect("room generation was checked");
1805
                let pending = match state {
1806
                    RoomActorState::Closing { pending, .. } => pending,
1807
                    RoomActorState::Opening { .. } | RoomActorState::Open { .. } => VecDeque::new(),
1808
                };
1809
                for operation in pending {
1810
                    let (id, result) = match operation {
1811
                        DeferredRoomLifecycle::Open(id) => {
1812
                            (id, self.open_room(id, room_id.clone()).await)
1813
                        }
1814
                        DeferredRoomLifecycle::Close(id) => {
1815
                            (id, self.close_room(id, &room_id).await)
1816
                        }
1817
                    };
1818
                    match result {
1819
                        Ok(true) => {}
1820
                        Ok(false) => emit_command_result(
1821
                            &self.port.event_sender(),
1822
                            id,
1823
                            CommandKind::Room,
1824
                            Ok(()),
1825
                        )
1826
                        .await
1827
                        .map_err(|_| EngineError::EventReceiverClosed)?,
1828
                        Err(error) => emit_command_result(
1829
                            &self.port.event_sender(),
1830
                            id,
1831
                            CommandKind::Room,
1832
                            Err(error),
1833
                        )
1834
                        .await
1835
                        .map_err(|_| EngineError::EventReceiverClosed)?,
1836
                    }
1837
                }
1838
                self.update_room_subscriptions().await;
1839
            }
1840
        }
1841
        Ok(())
1842
    }
1843
1844
    async fn update_room_subscriptions(&self) {
1845
        let room_ids = self
1846
            .rooms
1847
            .iter()
1848
            .filter(|(_, state)| !matches!(state, RoomActorState::Closing { .. }))
1849
            .map(|(room_id, _)| room_id.clone())
1850
            .collect::<Vec<_>>();
1851
        let room_refs = room_ids.iter().map(AsRef::as_ref).collect::<Vec<_>>();
1852
        self.sync
1853
            .room_list_service()
1854
            .subscribe_to_rooms(&room_refs)
1855
            .await;
1856
    }
1857
1858
    fn spawn_media_fetch(&mut self, command_id: CommandId, media_id: MediaId, size: MediaSize) {
1859
        let client = self.client.clone();
1860
        let media = self.media.clone();
1861
        let events = self.port.event_sender();
1862
        let max_media_bytes = self.max_media_bytes;
1863
        let flights = self.media_flights.clone();
1864
        self.tasks.spawn(
1865
            TaskContext::Command {
1866
                id: command_id,
1867
                kind: CommandKind::Media,
1868
            },
1869
            async move {
1870
                let leader = {
1871
                    let mut flights = flights.lock().await;
1872
                    let waiters = flights.entry((media_id.clone(), size)).or_default();
1873
                    waiters.push(command_id);
1874
                    waiters.len() == 1
1875
                };
1876
                if !leader {
1877
                    return;
1878
                }
1879
                let result = fetch_media(
1880
                    client,
1881
                    media,
1882
                    events.clone(),
1883
                    max_media_bytes,
1884
                    media_id.clone(),
1885
                    size,
1886
                )
1887
                .await;
1888
                let waiters = {
1889
                    let mut flights = flights.lock().await;
1890
                    flights.remove(&(media_id, size)).unwrap_or_default()
1891
                };
1892
                let result = match result {
1893
                    Ok(()) => Ok(()),
1894
                    Err(error) => {
1895
                        let kind = error.kind;
1896
                        let message = error.to_string();
1897
                        Err((kind, message))
1898
                    }
1899
                };
1900
                for waiter in waiters {
1901
                    let waiter_result = match &result {
1902
                        Ok(()) => Ok(()),
1903
                        Err((kind, message)) => Err(CommandError::new(*kind, message.clone())),
1904
                    };
1905
                    if emit_command_result(&events, waiter, CommandKind::Media, waiter_result)
1906
                        .await
1907
                        .is_err()
1908
                    {
1909
                        tracing::debug!(?waiter, "media fetch result was not delivered");
1910
                    }
1911
                }
1912
            },
1913
        );
1914
    }
1915
1916
    async fn spawn_media_upload(
1917
        &mut self,
1918
        command_id: CommandId,
1919
        upload: AttachmentUpload,
1920
    ) -> Result<(), CommandError> {
1921
        let room_id = upload.timeline_id.room_id().clone();
1922
        let room = self.client.get_room(&room_id).ok_or_else(|| {
1923
            CommandError::new(
1924
                CommandErrorKind::RoomUnknown,
1925
                "room is not available in the local store",
1926
            )
1927
        })?;
1928
        if self
1929
            .upload_watchers
1930
            .get(&room_id)
1931
            .is_some_and(|(watcher, _)| watcher.is_finished())
1932
        {
1933
            self.upload_watchers.remove(&room_id);
1934
        }
1935
        let ready = if let Some((_, ready)) = self.upload_watchers.get(&room_id) {
1936
            ready.clone()
1937
        } else {
1938
            let (ready_sender, ready) = watch::channel(None);
1939
            let watched_room_id = room_id.clone();
1940
            let progress_room_id = room_id.clone();
1941
            let queued_room = room.clone();
1942
            let events = self.port.event_sender();
1943
            let watcher =
1944
                self.tasks
1945
                    .spawn(TaskContext::Watcher("media upload watcher"), async move {
1946
                        let (_, mut updates) = match queued_room.send_queue().subscribe().await {
1947
                            Ok(subscription) => subscription,
1948
                            Err(source) => {
1949
                                tracing::warn!(room_id = %progress_room_id, error = %source, "reading media send queue failed");
1950
                                ready_sender.send_replace(Some(Err(CommandErrorKind::Store)));
1951
                                return;
1952
                            }
1953
                        };
1954
                        ready_sender.send_replace(Some(Ok(())));
1955
                        loop {
1956
                            match updates.recv().await {
1957
                                Ok(RoomSendQueueUpdate::MediaUpload {
1958
                                    related_to,
1959
                                    file,
1960
                                    progress,
1961
                                    ..
1962
                                }) => {
1963
                                    if events
1964
                                        .send(Event::MediaUploadProgress {
1965
                                            room_id: progress_room_id.clone(),
1966
                                            transaction_id: related_to,
1967
                                            uploaded_bytes: progress.current as u64,
1968
                                            total_bytes: progress.total as u64,
1969
                                            complete: file.is_some()
1970
                                                && progress.current == progress.total,
1971
                                        })
1972
                                        .await
1973
                                        .is_err()
1974
                                    {
1975
                                        break;
1976
                                    }
1977
                                }
1978
                                Ok(RoomSendQueueUpdate::SendError {
1979
                                    transaction_id,
1980
                                    error,
1981
                                    is_recoverable,
1982
                                }) => {
1983
                                    let is_attachment = queued_room
1984
                                        .send_queue()
1985
                                        .subscribe()
1986
                                        .await
1987
                                        .is_ok_and(|(echoes, _)| {
1988
                                            echoes.iter().any(|echo| {
1989
                                                echo.transaction_id == transaction_id
1990
                                                    && is_attachment_echo(&echo.content)
1991
                                            })
1992
                                        });
1993
                                    if is_attachment
1994
                                        && events
1995
                                            .send(Event::MediaUploadFailed {
1996
                                                room_id: progress_room_id.clone(),
1997
                                                transaction_id,
1998
                                                kind: classify_sdk_error(&error),
1999
                                                recoverable: is_recoverable,
2000
                                            })
2001
                                            .await
2002
                                            .is_err()
2003
                                    {
2004
                                        break;
2005
                                    }
2006
                                }
2007
                                Ok(_)
2008
                                | Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => {}
2009
                                Err(tokio::sync::broadcast::error::RecvError::Closed) => break,
2010
                            }
2011
                        }
2012
                    });
2013
            self.upload_watchers
2014
                .insert(watched_room_id, (watcher, ready.clone()));
2015
            ready
2016
        };
2017
        let max_media_bytes = self.max_media_bytes;
2018
        let events = self.port.event_sender();
2019
        self.tasks.spawn(
2020
            TaskContext::Command {
2021
                id: command_id,
2022
                kind: CommandKind::Media,
2023
            },
2024
            async move {
2025
                let mut ready = ready;
2026
                let result = async {
2027
                    loop {
2028
                        if let Some(result) = *ready.borrow_and_update() {
2029
                            result.map_err(|kind| {
2030
                                CommandError::new(kind, "reading media send queue failed")
2031
                            })?;
2032
                            break;
2033
                        }
2034
                        ready.changed().await.map_err(|_| {
2035
                            CommandError::new(
2036
                                CommandErrorKind::Store,
2037
                                "media send queue watcher stopped",
2038
                            )
2039
                        })?;
2040
                    }
2041
                    queue_media_upload(room, upload, max_media_bytes).await
2042
                }
2043
                .await;
2044
                if emit_command_result(&events, command_id, CommandKind::Media, result)
2045
                    .await
2046
                    .is_err()
2047
                {
2048
                    tracing::debug!(?command_id, "media upload result was not delivered");
2049
                }
2050
            },
2051
        );
2052
        Ok(())
2053
    }
2054
2055
    async fn shutdown(&mut self) {
2056
        self.tasks.shutdown().await;
2057
        self.verification.lock().await.stop().await;
2058
        self.rooms.clear();
2059
        self.upload_watchers.clear();
2060
        self.media.lock().await.entries.clear();
2061
        self.sync.stop().await;
2062
        self.port
2063
            .connection_sender()
2064
            .send_replace(ConnectionState::Stopped);
2065
        let _ = self
2066
            .port
2067
            .send(Event::ConnectionChanged(ConnectionState::Stopped))
2068
            .await;
2069
    }
2070
}
2071
2072
async fn mark_read(
2073
    client: &Client,
2074
    timeline_id: &TimelineId,
2075
    event_id: OwnedEventId,
2076
) -> Result<(), CommandError> {
2077
    let room = client
2078
        .get_room(timeline_id.room_id())
2079
        .ok_or_else(room_unknown)?;
2080
    let receipt_thread = match timeline_id {
2081
        TimelineId::Room(_) => ReceiptThread::Main,
2082
        TimelineId::Thread { root_event_id, .. } => ReceiptThread::Thread(root_event_id.clone()),
2083
    };
2084
    room.send_single_receipt(ReceiptType::Read, receipt_thread, event_id.clone())
2085
        .await
2086
        .map_err(|source| CommandError::from_sdk("sending read receipt failed", source))?;
2087
    if matches!(timeline_id, TimelineId::Room(_)) {
2088
        room.send_single_receipt(ReceiptType::FullyRead, ReceiptThread::Unthreaded, event_id)
2089
            .await
2090
            .map_err(|source| {
2091
                CommandError::from_sdk("sending fully read receipt failed", source)
2092
            })?;
2093
    }
2094
    Ok(())
2095
}
2096
2097
async fn set_typing(client: &Client, room_id: &RoomId, typing: bool) -> Result<(), CommandError> {
2098
    let room = client.get_room(room_id).ok_or_else(room_unknown)?;
2099
    room.typing_notice(typing)
2100
        .await
2101
        .map_err(|source| CommandError::from_sdk("typing update failed", source))
2102
}
2103
2104
fn is_attachment_echo(content: &LocalEchoContent) -> bool {
2105
    let LocalEchoContent::Event {
2106
        serialized_event, ..
2107
    } = content
2108
    else {
2109
        return false;
2110
    };
2111
    matches!(
2112
        serialized_event.deserialize(),
2113
        Ok(AnyMessageLikeEventContent::RoomMessage(content))
2114
            if matches!(
2115
                content.msgtype,
2116
                MessageType::Audio(_) | MessageType::File(_) | MessageType::Image(_) | MessageType::Video(_)
2117
            )
2118
    )
2119
}
2120
2121
async fn send_handle(
2122
    client: &Client,
2123
    room_id: &RoomId,
2124
    transaction_id: &matrix_sdk::ruma::TransactionId,
2125
) -> Result<SendHandle, CommandError> {
2126
    let room = client.get_room(room_id).ok_or_else(room_unknown)?;
2127
    let (echoes, _) = room.send_queue().subscribe().await.map_err(|source| {
2128
        CommandError::with_source(CommandErrorKind::Store, "reading send queue failed", source)
2129
    })?;
2130
    echoes
2131
        .into_iter()
2132
        .find_map(|echo| {
2133
            if echo.transaction_id.as_str() != transaction_id.as_str() {
2134
                return None;
2135
            }
2136
            match echo.content {
2137
                LocalEchoContent::Event { send_handle, .. } => Some(send_handle),
2138
                _ => None,
2139
            }
2140
        })
2141
        .ok_or_else(|| {
2142
            CommandError::new(
2143
                CommandErrorKind::Other,
2144
                "send is no longer pending or is not a message",
2145
            )
2146
        })
2147
}
2148
2149
async fn cancel_send(
2150
    client: &Client,
2151
    room_id: &RoomId,
2152
    transaction_id: &matrix_sdk::ruma::TransactionId,
2153
) -> Result<(), CommandError> {
2154
    let handle = send_handle(client, room_id, transaction_id).await?;
2155
    match handle.abort().await {
2156
        Ok(true) => Ok(()),
2157
        Ok(false) => Err(CommandError::new(
2158
            CommandErrorKind::Other,
2159
            "send could not be cancelled because it has already been sent",
2160
        )),
2161
        Err(source) => Err(CommandError::with_source(
2162
            CommandErrorKind::Store,
2163
            "cancelling send failed",
2164
            source,
2165
        )),
2166
    }
2167
}
2168
2169
async fn retry_send(
2170
    client: &Client,
2171
    room_id: &RoomId,
2172
    transaction_id: &matrix_sdk::ruma::TransactionId,
2173
) -> Result<(), CommandError> {
2174
    let handle = send_handle(client, room_id, transaction_id).await?;
2175
    handle.unwedge().await.map_err(|source| {
2176
        CommandError::with_source(CommandErrorKind::Store, "retrying send failed", source)
2177
    })?;
2178
    client.send_queue().set_enabled(true).await;
2179
    Ok(())
2180
}
2181
2182
fn send_room_actor_request(
2183
    sender: &mpsc::Sender<RoomActorRequest>,
2184
    request: RoomActorRequest,
2185
) -> Result<(), CommandError> {
2186
    sender.try_send(request).map_err(|error| match error {
2187
        mpsc::error::TrySendError::Full(_) => {
2188
            CommandError::new(CommandErrorKind::RoomBusy, "room command queue is full")
2189
        }
2190
        mpsc::error::TrySendError::Closed(_) => {
2191
            CommandError::new(CommandErrorKind::RoomNotOpen, "room is not open")
2192
        }
2193
    })
2194
}
2195
2196
fn domain_queue_error<T>(error: mpsc::error::TrySendError<T>, domain: &str) -> CommandError {
2197
    match error {
2198
        mpsc::error::TrySendError::Full(_) => CommandError::new(
2199
            CommandErrorKind::QueueFull,
2200
            format!("{domain} command queue is full"),
2201
        ),
2202
        mpsc::error::TrySendError::Closed(_) => CommandError::new(
2203
            CommandErrorKind::Other,
2204
            format!("{domain} command worker has stopped"),
2205
        ),
2206
    }
2207
}
2208
2209
async fn run_room_command_worker(
2210
    client: Client,
2211
    events: EventSender,
2212
    mut commands: mpsc::Receiver<RoomJob>,
2213
    queue_limit: usize,
2214
) {
2215
    let mut queues = HashMap::<RoomMutationKey, VecDeque<RoomJob>>::new();
2216
    let mut running = HashSet::<RoomMutationKey>::new();
2217
    let mut tasks = JoinSet::<RoomJobCompletion>::new();
2218
    let mut receiving = true;
2219
    let mut queued = 0_usize;
2220
    while receiving || !tasks.is_empty() {
2221
        tokio::select! {
2222
            biased;
2223
            completed = tasks.join_next(), if !tasks.is_empty() => {
2224
                let completion = match completed {
2225
                    Some(Ok(completion)) => completion,
2226
                    Some(Err(error)) => panic!("room command task failed: {error}"),
2227
                    None => continue,
2228
                };
2229
                let key = completion.key.clone();
2230
                emit_room_job_result(&events, completion).await;
2231
                let next = queues.get_mut(&key).and_then(VecDeque::pop_front);
2232
                if next.is_some() {
2233
                    queued -= 1;
2234
                }
2235
                if queues.get(&key).is_some_and(VecDeque::is_empty) {
2236
                    queues.remove(&key);
2237
                }
2238
                if let Some(job) = next {
2239
                    spawn_room_job(&mut tasks, client.clone(), key, job);
2240
                } else {
2241
                    running.remove(&key);
2242
                }
2243
            }
2244
            job = commands.recv(), if receiving => {
2245
                match job {
2246
                    Some(job) => {
2247
                        let key = room_mutation_key(job.id, &job.command);
2248
                        if running.insert(key.clone()) {
2249
                            spawn_room_job(&mut tasks, client.clone(), key, job);
2250
                        } else if queued < queue_limit {
2251
                            queues.entry(key).or_default().push_back(job);
2252
                            queued += 1;
2253
                        } else {
2254
                            let failure = CommandFailure {
2255
                                id: job.id,
2256
                                command: CommandKind::Room,
2257
                                kind: CommandErrorKind::QueueFull,
2258
                                message: "room command queue is full".to_owned(),
2259
                            };
2260
                            if let Some(reply) = job.reply {
2261
                                let _ = reply.send(Err(CommandResultError::Failed(failure)));
2262
                            } else {
2263
                                let _ = events.send(Event::CommandFailed(failure)).await;
2264
                            }
2265
                        }
2266
                    }
2267
                    None => receiving = false,
2268
                }
2269
            }
2270
        }
2271
    }
2272
}
2273
2274
fn spawn_room_job(
2275
    tasks: &mut JoinSet<RoomJobCompletion>,
2276
    client: Client,
2277
    key: RoomMutationKey,
2278
    job: RoomJob,
2279
) {
2280
    tasks.spawn(async move {
2281
        let RoomJob { id, command, reply } = job;
2282
        let result = AssertUnwindSafe(execute_room_command(&client, command))
2283
            .catch_unwind()
2284
            .await
2285
            .unwrap_or_else(|_| {
2286
                Err(CommandError::new(
2287
                    CommandErrorKind::Other,
2288
                    "room command panicked",
2289
                ))
2290
            });
2291
        RoomJobCompletion {
2292
            key,
2293
            id,
2294
            reply,
2295
            result,
2296
        }
2297
    });
2298
}
2299
2300
fn room_mutation_key(id: CommandId, command: &RoomCommand) -> RoomMutationKey {
2301
    match command {
2302
        RoomCommand::Join { room, .. } if room.is_room_id() => RoomMutationKey::Room(
2303
            OwnedRoomId::try_from(room.clone()).expect("room ID variant was checked"),
2304
        ),
2305
        RoomCommand::Join { room, .. } => RoomMutationKey::Target(room.to_string()),
2306
        RoomCommand::Leave(room_id)
2307
        | RoomCommand::Forget(room_id)
2308
        | RoomCommand::AcceptInvite(room_id)
2309
        | RoomCommand::RejectInvite(room_id)
2310
        | RoomCommand::InviteUser { room_id, .. }
2311
        | RoomCommand::KickUser { room_id, .. }
2312
        | RoomCommand::BanUser { room_id, .. }
2313
        | RoomCommand::UnbanUser { room_id, .. }
2314
        | RoomCommand::SetPowerLevels { room_id, .. }
2315
        | RoomCommand::SetUserPowerLevel { room_id, .. } => RoomMutationKey::Room(room_id.clone()),
2316
        RoomCommand::CreateDirect(user_id) => RoomMutationKey::Direct(user_id.clone()),
2317
        _ => RoomMutationKey::Independent(id),
2318
    }
2319
}
2320
2321
async fn emit_room_job_result(events: &EventSender, completion: RoomJobCompletion) {
2322
    let RoomJobCompletion {
2323
        id, reply, result, ..
2324
    } = completion;
2325
    match result {
2326
        Ok(Some(room_id)) => {
2327
            if let Some(reply) = reply {
2328
                let _ = reply.send(Ok(room_id.clone()));
2329
            } else {
2330
                let _ = emit_command_result_with_room(events, id, room_id).await;
2331
            }
2332
        }
2333
        Ok(None) => {
2334
            if let Some(reply) = reply {
2335
                let _ = reply.send(Err(CommandResultError::Failed(CommandFailure {
2336
                    id,
2337
                    command: CommandKind::Room,
2338
                    kind: CommandErrorKind::Other,
2339
                    message: "room operation did not return a room ID".to_owned(),
2340
                })));
2341
            } else {
2342
                let _ = emit_command_result(events, id, CommandKind::Room, Ok(())).await;
2343
            }
2344
        }
2345
        Err(error) => {
2346
            let failure = CommandFailure {
2347
                id,
2348
                command: CommandKind::Room,
2349
                kind: error.kind,
2350
                message: error.to_string(),
2351
            };
2352
            if let Some(reply) = reply {
2353
                let _ = reply.send(Err(CommandResultError::Failed(failure)));
2354
            } else {
2355
                let _ = events.send(Event::CommandFailed(failure)).await;
2356
            }
2357
        }
2358
    }
2359
}
2360
2361
async fn execute_room_command(
2362
    client: &Client,
2363
    command: RoomCommand,
2364
) -> Result<Option<OwnedRoomId>, CommandError> {
2365
    match command {
2366
        RoomCommand::Join { room, via } => client
2367
            .join_room_by_id_or_alias(&room, &via)
2368
            .await
2369
            .map(|room| Some(room.room_id().to_owned()))
2370
            .map_err(|source| CommandError::from_sdk("joining room failed", source)),
2371
        RoomCommand::Leave(room_id) => match client.get_room(&room_id) {
2372
            Some(room) => room
2373
                .leave()
2374
                .await
2375
                .map(|_| None)
2376
                .map_err(|source| CommandError::from_sdk("leaving room failed", source)),
2377
            None => Err(room_unknown()),
2378
        },
2379
        RoomCommand::Forget(room_id) => match client.get_room(&room_id) {
2380
            Some(room) => room
2381
                .forget()
2382
                .await
2383
                .map(|_| None)
2384
                .map_err(|source| CommandError::from_sdk("forgetting room failed", source)),
2385
            None => Err(room_unknown()),
2386
        },
2387
        RoomCommand::AcceptInvite(room_id) => match client.get_room(&room_id) {
2388
            Some(room) => room
2389
                .join()
2390
                .await
2391
                .map(|_| None)
2392
                .map_err(|source| CommandError::from_sdk("accepting invite failed", source)),
2393
            None => Err(room_unknown()),
2394
        },
2395
        RoomCommand::RejectInvite(room_id) => match client.get_room(&room_id) {
2396
            Some(room) => room
2397
                .leave()
2398
                .await
2399
                .map(|_| None)
2400
                .map_err(|source| CommandError::from_sdk("rejecting invite failed", source)),
2401
            None => Err(room_unknown()),
2402
        },
2403
        RoomCommand::InviteUser { room_id, user_id } => match client.get_room(&room_id) {
2404
            Some(room) => room
2405
                .invite_user_by_id(&user_id)
2406
                .await
2407
                .map(|()| None)
2408
                .map_err(|source| CommandError::from_sdk("inviting user failed", source)),
2409
            None => Err(room_unknown()),
2410
        },
2411
        RoomCommand::KickUser {
2412
            room_id,
2413
            user_id,
2414
            reason,
2415
        } => match client.get_room(&room_id) {
2416
            Some(room) => room
2417
                .kick_user(&user_id, reason.as_deref())
2418
                .await
2419
                .map(|_| None)
2420
                .map_err(|source| CommandError::from_sdk("kicking user failed", source)),
2421
            None => Err(room_unknown()),
2422
        },
2423
        RoomCommand::BanUser {
2424
            room_id,
2425
            user_id,
2426
            reason,
2427
        } => match client.get_room(&room_id) {
2428
            Some(room) => room
2429
                .ban_user(&user_id, reason.as_deref())
2430
                .await
2431
                .map(|_| None)
2432
                .map_err(|source| CommandError::from_sdk("banning user failed", source)),
2433
            None => Err(room_unknown()),
2434
        },
2435
        RoomCommand::UnbanUser {
2436
            room_id,
2437
            user_id,
2438
            reason,
2439
        } => match client.get_room(&room_id) {
2440
            Some(room) => room
2441
                .unban_user(&user_id, reason.as_deref())
2442
                .await
2443
                .map(|_| None)
2444
                .map_err(|source| CommandError::from_sdk("unbanning user failed", source)),
2445
            None => Err(room_unknown()),
2446
        },
2447
        RoomCommand::SetPowerLevels { room_id, changes } => match client.get_room(&room_id) {
2448
            Some(room) => apply_power_level_changes(&room, changes)
2449
                .await
2450
                .map(|_| None),
2451
            None => Err(room_unknown()),
2452
        },
2453
        RoomCommand::SetUserPowerLevel {
2454
            room_id,
2455
            user_id,
2456
            level,
2457
        } => match client.get_room(&room_id) {
2458
            Some(room) => match matrix_sdk::ruma::Int::try_from(level) {
2459
                Ok(level) => room
2460
                    .update_power_levels(vec![(&user_id, level)])
2461
                    .await
2462
                    .map(|_| None)
2463
                    .map_err(|source| {
2464
                        CommandError::from_sdk("updating user power level failed", source)
2465
                    }),
2466
                Err(_) => Err(CommandError::new(
2467
                    CommandErrorKind::Other,
2468
                    "power level is outside the Matrix integer range",
2469
                )),
2470
            },
2471
            None => Err(room_unknown()),
2472
        },
2473
        RoomCommand::Create(options) => client
2474
            .create_room(create_room_request(options))
2475
            .await
2476
            .map(|room| Some(room.room_id().to_owned()))
2477
            .map_err(|source| CommandError::from_sdk("creating room failed", source)),
2478
        RoomCommand::CreateDirect(user_id) => client
2479
            .create_dm(&user_id)
2480
            .await
2481
            .map(|room| Some(room.room_id().to_owned()))
2482
            .map_err(|source| CommandError::from_sdk("creating direct room failed", source)),
2483
        _ => Err(CommandError::new(
2484
            CommandErrorKind::Other,
2485
            "unsupported room command",
2486
        )),
2487
    }
2488
}
2489
2490
fn room_unknown() -> CommandError {
2491
    CommandError::new(
2492
        CommandErrorKind::RoomUnknown,
2493
        "room is not available in the local store",
2494
    )
2495
}
2496
2497
const fn sdk_notification_mode(
2498
    mode: RoomNotificationMode,
2499
) -> matrix_sdk::notification_settings::RoomNotificationMode {
2500
    match mode {
2501
        RoomNotificationMode::AllMessages => {
2502
            matrix_sdk::notification_settings::RoomNotificationMode::AllMessages
2503
        }
2504
        RoomNotificationMode::MentionsAndKeywordsOnly => {
2505
            matrix_sdk::notification_settings::RoomNotificationMode::MentionsAndKeywordsOnly
2506
        }
2507
        RoomNotificationMode::Mute => matrix_sdk::notification_settings::RoomNotificationMode::Mute,
2508
    }
2509
}
2510
2511
async fn execute_notification_command(
2512
    client: &Client,
2513
    command: NotificationCommand,
2514
) -> Result<(), CommandError> {
2515
    use matrix_sdk::notification_settings::{IsEncrypted, IsOneToOne};
2516
2517
    let settings = client.notification_settings().await;
2518
    let result = match command {
2519
        NotificationCommand::SetRoomMode { room_id, mode } => {
2520
            settings
2521
                .set_room_notification_mode(&room_id, sdk_notification_mode(mode))
2522
                .await
2523
        }
2524
        NotificationCommand::ResetRoomMode(room_id) => {
2525
            settings.delete_user_defined_room_rules(&room_id).await
2526
        }
2527
        NotificationCommand::SetDefaultMode {
2528
            encrypted,
2529
            one_to_one,
2530
            mode,
2531
        } => {
2532
            settings
2533
                .set_default_room_notification_mode(
2534
                    IsEncrypted::from(encrypted),
2535
                    IsOneToOne::from(one_to_one),
2536
                    sdk_notification_mode(mode),
2537
                )
2538
                .await
2539
        }
2540
        NotificationCommand::AddKeyword(keyword) => settings.add_keyword(keyword).await,
2541
        NotificationCommand::RemoveKeyword(keyword) => settings.remove_keyword(&keyword).await,
2542
    };
2543
    result.map_err(|source| {
2544
        CommandError::with_source(
2545
            CommandErrorKind::Server,
2546
            "updating notification settings failed",
2547
            source,
2548
        )
2549
    })
2550
}
2551
2552
async fn execute_encryption_command(
2553
    client: &Client,
2554
    events: &EventSender,
2555
    verification: &mut VerificationController,
2556
    command: EncryptionCommand,
2557
) -> Result<(), CommandError> {
2558
    match command {
2559
        EncryptionCommand::Recover(key) => client
2560
            .encryption()
2561
            .recovery()
2562
            .recover(&key)
2563
            .await
2564
            .map_err(|source| CommandError::from_recovery("account recovery failed", source)),
2565
        EncryptionCommand::StartVerification(target) => {
2566
            verification.start(client, events.clone(), target).await
2567
        }
2568
        EncryptionCommand::Verify(action) => {
2569
            verification.apply(client, events.clone(), action).await
2570
        }
2571
        command => execute_key_backup_command(client, command).await,
2572
    }
2573
}
2574
2575
async fn execute_key_backup_command(
2576
    client: &Client,
2577
    command: EncryptionCommand,
2578
) -> Result<(), CommandError> {
2579
    match command {
2580
        EncryptionCommand::CreateKeyBackup => client
2581
            .encryption()
2582
            .backups()
2583
            .create()
2584
            .await
2585
            .map_err(|source| CommandError::from_sdk("creating key backup failed", source)),
2586
        EncryptionCommand::EnableKeyBackup => client
2587
            .encryption()
2588
            .recovery()
2589
            .enable_backup()
2590
            .await
2591
            .map_err(|source| CommandError::from_recovery("enabling key backup failed", source)),
2592
        EncryptionCommand::DisableKeyBackup { delete_from_server } => {
2593
            let backups = client.encryption().backups();
2594
            if delete_from_server {
2595
                backups.disable_and_delete().await
2596
            } else {
2597
                backups.disable().await
2598
            }
2599
            .map_err(|source| CommandError::from_sdk("disabling key backup failed", source))
2600
        }
2601
        EncryptionCommand::RecoverKeyBackup(key) => client
2602
            .encryption()
2603
            .recovery()
2604
            .recover_and_fix_backup(&key)
2605
            .await
2606
            .map_err(|source| CommandError::from_recovery("recovering key backup failed", source)),
2607
        _ => unreachable!("only key backup commands are spawned here"),
2608
    }
2609
}
2610
2611
async fn apply_power_level_changes(
2612
    room: &Room,
2613
    changes: PowerLevelChanges,
2614
) -> Result<(), CommandError> {
2615
    use matrix_sdk::ruma::events::{
2616
        MessageLikeEventType, StateEventType, room::power_levels::RoomPowerLevelsEventContent,
2617
    };
2618
2619
    let mut levels = room
2620
        .power_levels()
2621
        .await
2622
        .map_err(|source| CommandError::from_sdk("reading power levels failed", source.into()))?;
2623
    if let Some(value) = changes.ban {
2624
        levels.ban = matrix_power_level(value)?;
2625
    }
2626
    if let Some(value) = changes.invite {
2627
        levels.invite = matrix_power_level(value)?;
2628
    }
2629
    if let Some(value) = changes.kick {
2630
        levels.kick = matrix_power_level(value)?;
2631
    }
2632
    if let Some(value) = changes.redact {
2633
        levels.redact = matrix_power_level(value)?;
2634
    }
2635
    if let Some(value) = changes.events_default {
2636
        levels.events_default = matrix_power_level(value)?;
2637
    }
2638
    if let Some(value) = changes.state_default {
2639
        levels.state_default = matrix_power_level(value)?;
2640
    }
2641
    if let Some(value) = changes.users_default {
2642
        levels.users_default = matrix_power_level(value)?;
2643
    }
2644
    if let Some(value) = changes.room_notification {
2645
        levels.notifications.room = matrix_power_level(value)?;
2646
    }
2647
    for (event_type, value) in [
2648
        (StateEventType::RoomName.into(), changes.room_name),
2649
        (StateEventType::RoomAvatar.into(), changes.room_avatar),
2650
        (StateEventType::RoomTopic.into(), changes.room_topic),
2651
        (StateEventType::SpaceChild.into(), changes.space_child),
2652
        (MessageLikeEventType::Beacon.into(), changes.beacon),
2653
        (StateEventType::BeaconInfo.into(), changes.beacon_info),
2654
    ] {
2655
        if let Some(value) = value {
2656
            levels.events.insert(event_type, matrix_power_level(value)?);
2657
        }
2658
    }
2659
    let content = RoomPowerLevelsEventContent::try_from(levels).map_err(|source| {
2660
        CommandError::with_source(
2661
            CommandErrorKind::Other,
2662
            "normalizing power levels failed",
2663
            source,
2664
        )
2665
    })?;
2666
    room.send_state_event(content)
2667
        .await
2668
        .map(|_| ())
2669
        .map_err(|source| CommandError::from_sdk("updating power levels failed", source))
2670
}
2671
2672
fn matrix_power_level(value: i64) -> Result<matrix_sdk::ruma::Int, CommandError> {
2673
    value.try_into().map_err(|source| {
2674
        CommandError::with_source(
2675
            CommandErrorKind::Other,
2676
            "power level is outside the Matrix integer range",
2677
            source,
2678
        )
2679
    })
2680
}
2681
2682
fn create_room_request(
2683
    options: CreateRoomOptions,
2684
) -> matrix_sdk::ruma::api::client::room::create_room::v3::Request {
2685
    use matrix_sdk::ruma::api::client::room::{
2686
        Visibility as SdkVisibility,
2687
        create_room::v3::{Request, RoomPreset as SdkPreset},
2688
    };
2689
    let mut request = Request::new();
2690
    request.name = options.name;
2691
    request.topic = options.topic;
2692
    request.room_alias_name = options.alias_localpart;
2693
    request.invite = options.invite;
2694
    request.is_direct = options.direct;
2695
    request.visibility = match options.visibility {
2696
        RoomVisibility::Private => SdkVisibility::Private,
2697
        RoomVisibility::Public => SdkVisibility::Public,
2698
    };
2699
    request.preset = options.preset.map(|preset| match preset {
2700
        RoomPreset::PrivateChat => SdkPreset::PrivateChat,
2701
        RoomPreset::TrustedPrivateChat => SdkPreset::TrustedPrivateChat,
2702
        RoomPreset::PublicChat => SdkPreset::PublicChat,
2703
    });
2704
    request
2705
}
2706
2707
async fn queue_media_upload(
2708
    room: Room,
2709
    upload: AttachmentUpload,
2710
    max_media_bytes: usize,
2711
) -> Result<(), CommandError> {
2712
    let (upload, mime, mut config) = prepare_upload(upload, max_media_bytes)?;
2713
    if let TimelineId::Thread { root_event_id, .. } = &upload.timeline_id {
2714
        config.reply = Some(SdkReply {
2715
            event_id: root_event_id.clone(),
2716
            enforce_thread: EnforceThread::Threaded(ReplyWithinThread::No),
2717
            add_mentions: AddMentions::No,
2718
        });
2719
    }
2720
    room.send_queue()
2721
        .send_attachment(upload.filename, mime, upload.bytes, config)
2722
        .await
2723
        .map(|_| ())
2724
        .map_err(|source| {
2725
            CommandError::with_source(
2726
                CommandErrorKind::MediaUnavailable,
2727
                "media upload could not be queued",
2728
                source,
2729
            )
2730
        })
2731
}
2732
2733
async fn fetch_media(
2734
    client: Client,
2735
    registry: Arc<Mutex<MediaRegistry>>,
2736
    events: EventSender,
2737
    max_media_bytes: usize,
2738
    media_id: MediaId,
2739
    size: MediaSize,
2740
) -> Result<(), CommandError> {
2741
    let media = registry
2742
        .lock()
2743
        .await
2744
        .entries
2745
        .get(&media_id)
2746
        .cloned()
2747
        .ok_or_else(|| {
2748
            CommandError::new(
2749
                CommandErrorKind::MediaUnknown,
2750
                "media is not registered in a retained timeline",
2751
            )
2752
        })?;
2753
    let (source, format, mimetype, declared_size) = match size {
2754
        MediaSize::Full => (
2755
            media.full.source,
2756
            MediaFormat::File,
2757
            media.full.mimetype,
2758
            media.full.declared_size,
2759
        ),
2760
        MediaSize::Thumbnail => {
2761
            if let Some(thumbnail) = media.thumbnail {
2762
                (
2763
                    thumbnail.source,
2764
                    MediaFormat::File,
2765
                    thumbnail.mimetype,
2766
                    thumbnail.declared_size,
2767
                )
2768
            } else if matches!(
2769
                media.kind,
2770
                crate::MediaKind::Image | crate::MediaKind::Video
2771
            ) && matches!(
2772
                &media.full.source,
2773
                matrix_sdk::ruma::events::room::MediaSource::Plain(_)
2774
            ) {
2775
                (
2776
                    media.full.source,
2777
                    MediaFormat::Thumbnail(MediaThumbnailSettings::new(
2778
                        800_u32.into(),
2779
                        800_u32.into(),
2780
                    )),
2781
                    media.full.mimetype,
2782
                    None,
2783
                )
2784
            } else {
2785
                return Err(CommandError::new(
2786
                    CommandErrorKind::MediaUnavailable,
2787
                    "the requested media size is unavailable",
2788
                ));
2789
            }
2790
        }
2791
    };
2792
    if declared_size.is_some_and(|size| size > max_media_bytes as u64) {
2793
        return Err(CommandError::new(
2794
            CommandErrorKind::MediaTooLarge,
2795
            "media exceeds the configured byte limit",
2796
        ));
2797
    }
2798
    let request = MediaRequestParameters { source, format };
2799
    if let Some(cached) = cached_media(&client, &request).await? {
2800
        if cached.len() > max_media_bytes {
2801
            remove_oversize_media(&client, &request).await;
2802
            return Err(CommandError::new(
2803
                CommandErrorKind::MediaTooLarge,
2804
                "cached media exceeds the configured byte limit",
2805
            ));
2806
        }
2807
        let bytes = maybe_decrypt_media(cached, &request, max_media_bytes)?;
2808
        events
2809
            .send(Event::MediaReady {
2810
                media_id,
2811
                size,
2812
                bytes: Arc::from(bytes),
2813
                mimetype: mimetype.unwrap_or_else(|| "application/octet-stream".into()),
2814
            })
2815
            .await
2816
            .map_err(send_error)?;
2817
        return Ok(());
2818
    }
2819
    let bytes = download_bounded(&client, &request, max_media_bytes).await?;
2820
    cache_media(&client, &request, &bytes).await;
2821
    let bytes = maybe_decrypt_media(bytes, &request, max_media_bytes)?;
2822
    events
2823
        .send(Event::MediaReady {
2824
            media_id,
2825
            size,
2826
            bytes: Arc::from(bytes),
2827
            mimetype: mimetype.unwrap_or_else(|| "application/octet-stream".into()),
2828
        })
2829
        .await
2830
        .map_err(send_error)
2831
}
2832
async fn cached_media(
2833
    client: &Client,
2834
    request: &MediaRequestParameters,
2835
) -> Result<Option<Vec<u8>>, CommandError> {
2836
    client
2837
        .media_store()
2838
        .lock()
2839
        .await
2840
        .map_err(|source| {
2841
            CommandError::with_source(
2842
                CommandErrorKind::Store,
2843
                "reading media cache failed",
2844
                source,
2845
            )
2846
        })?
2847
        .get_media_content(request)
2848
        .await
2849
        .map_err(|source| {
2850
            CommandError::with_source(
2851
                CommandErrorKind::Store,
2852
                "reading media cache failed",
2853
                source,
2854
            )
2855
        })
2856
}
2857
2858
async fn remove_oversize_media(client: &Client, request: &MediaRequestParameters) {
2859
    if let Ok(store) = client.media_store().lock().await {
2860
        let _ = store.remove_media_content(request).await;
2861
    }
2862
}
2863
2864
async fn cache_media(client: &Client, request: &MediaRequestParameters, bytes: &[u8]) {
2865
    if let Ok(store) = client.media_store().lock().await {
2866
        let _ = store
2867
            .add_media_content(
2868
                request,
2869
                bytes.to_vec(),
2870
                matrix_sdk::media::store::IgnoreMediaRetentionPolicy::No,
2871
            )
2872
            .await;
2873
    }
2874
}
2875
2876
fn maybe_decrypt_media(
2877
    bytes: Vec<u8>,
2878
    request: &MediaRequestParameters,
2879
    max_media_bytes: usize,
2880
) -> Result<Vec<u8>, CommandError> {
2881
    let matrix_sdk::ruma::events::room::MediaSource::Encrypted(file) = &request.source else {
2882
        return Ok(bytes);
2883
    };
2884
    let info: MediaEncryptionInfo = (**file).clone().into();
2885
    let mut cursor = std::io::Cursor::new(bytes);
2886
    let mut decryptor = AttachmentDecryptor::new(&mut cursor, info).map_err(|source| {
2887
        CommandError::with_source(
2888
            CommandErrorKind::MediaUnavailable,
2889
            "media decryption failed",
2890
            source,
2891
        )
2892
    })?;
2893
    let mut decrypted = Vec::new();
2894
    let mut chunk = [0_u8; 8192];
2895
    loop {
2896
        match decryptor.read(&mut chunk) {
2897
            Ok(0) => break,
2898
            Ok(read) => {
2899
                if read > max_media_bytes.saturating_sub(decrypted.len()) {
2900
                    return Err(CommandError::new(
2901
                        CommandErrorKind::MediaTooLarge,
2902
                        "decrypted media exceeds the configured byte limit",
2903
                    ));
2904
                }
2905
                decrypted.extend_from_slice(&chunk[..read]);
2906
            }
2907
            Err(source) => {
2908
                return Err(CommandError::with_source(
2909
                    CommandErrorKind::MediaUnavailable,
2910
                    "media decryption failed",
2911
                    source,
2912
                ));
2913
            }
2914
        }
2915
    }
2916
    Ok(decrypted)
2917
}
2918
2919
async fn download_bounded(
2920
    client: &Client,
2921
    request: &MediaRequestParameters,
2922
    max_media_bytes: usize,
2923
) -> Result<Vec<u8>, CommandError> {
2924
    if is_local_media(&request.source) {
2925
        return Err(CommandError::new(
2926
            CommandErrorKind::MediaUnavailable,
2927
            "local media is not in the media cache",
2928
        ));
2929
    }
2930
    let http_request = media_http_request(client, request).await?;
2931
    let http_client = client.http_client().clone();
2932
    let request = http_client
2933
        .execute(request_from_http(http_request)?)
2934
        .await
2935
        .map_err(|source| {
2936
            CommandError::with_source(CommandErrorKind::Network, "media download failed", source)
2937
        })?;
2938
    let status = request.status();
2939
    if status == matrix_sdk::reqwest::StatusCode::UNAUTHORIZED {
2940
        client.refresh_access_token().await.map_err(|source| {
2941
            CommandError::from_refresh_token("refreshing media access token failed", source)
2942
        })?;
2943
        return Err(CommandError::new(
2944
            CommandErrorKind::Unauthenticated,
2945
            "media download was not authorized",
2946
        ));
2947
    }
2948
    if !status.is_success() {
2949
        return Err(CommandError::new(
2950
            CommandErrorKind::MediaUnavailable,
2951
            format!("media download failed with status {status}"),
2952
        ));
2953
    }
2954
    if request
2955
        .content_length()
2956
        .is_some_and(|length| length > max_media_bytes as u64)
2957
    {
2958
        return Err(CommandError::new(
2959
            CommandErrorKind::MediaTooLarge,
2960
            "media exceeds the configured byte limit",
2961
        ));
2962
    }
2963
    let mut stream = request.bytes_stream();
2964
    let mut bytes = Vec::new();
2965
    while let Some(chunk) = stream.next().await {
2966
        let chunk = chunk.map_err(|source| {
2967
            CommandError::with_source(CommandErrorKind::Network, "media download failed", source)
2968
        })?;
2969
        if chunk.len() > max_media_bytes.saturating_sub(bytes.len()) {
2970
            return Err(CommandError::new(
2971
                CommandErrorKind::MediaTooLarge,
2972
                "downloaded media exceeds the configured byte limit",
2973
            ));
2974
        }
2975
        bytes.extend_from_slice(&chunk);
2976
    }
2977
    Ok(bytes)
2978
}
2979
2980
fn is_local_media(source: &matrix_sdk::ruma::events::room::MediaSource) -> bool {
2981
    let uri = match source {
2982
        matrix_sdk::ruma::events::room::MediaSource::Plain(uri) => uri,
2983
        matrix_sdk::ruma::events::room::MediaSource::Encrypted(file) => &file.url,
2984
    };
2985
    let Ok(server) = uri.server_name() else {
2986
        return false;
2987
    };
2988
    server.as_str() == "send-queue.localhost"
2989
}
2990
2991
async fn media_http_request(
2992
    client: &Client,
2993
    request: &MediaRequestParameters,
2994
) -> Result<http::Request<Vec<u8>>, CommandError> {
2995
    use matrix_sdk::ruma::api::client::{authenticated_media, media};
2996
2997
    let versions = client.supported_versions().await.map_err(|source| {
2998
        CommandError::with_source(
2999
            CommandErrorKind::Network,
3000
            "reading supported server versions failed",
3001
            source,
3002
        )
3003
    })?;
3004
    let use_auth =
3005
        authenticated_media::get_content::v1::Request::PATH_BUILDER.is_supported(&versions);
3006
    let base = client.homeserver().to_string();
3007
    let token = client.access_token();
3008
    let build = |request: http::Request<Vec<u8>>| -> Result<http::Request<Vec<u8>>, CommandError> {
3009
        let mut request = request;
3010
        if let Some(token) = token.as_deref() {
3011
            request.headers_mut().insert(
3012
                http::header::AUTHORIZATION,
3013
                format!("Bearer {token}").parse().map_err(|_| {
3014
                    CommandError::new(
3015
                        CommandErrorKind::Unauthenticated,
3016
                        "stored access token is invalid",
3017
                    )
3018
                })?,
3019
            );
3020
        }
3021
        Ok(request)
3022
    };
3023
    match &request.source {
3024
        matrix_sdk::ruma::events::room::MediaSource::Encrypted(file) => {
3025
            if use_auth {
3026
                let endpoint = authenticated_media::get_content::v1::Request::from_uri(&file.url)
3027
                    .map_err(|source| {
3028
                    CommandError::with_source(
3029
                        CommandErrorKind::Other,
3030
                        "media URI is invalid",
3031
                        source,
3032
                    )
3033
                })?;
3034
                endpoint
3035
                    .try_into_http_request::<Vec<u8>>(
3036
                        &base,
3037
                        SendAccessToken::IfRequired(token.as_deref().unwrap_or("")),
3038
                        Cow::Borrowed(&versions),
3039
                    )
3040
                    .map_err(|_| {
3041
                        CommandError::new(
3042
                            CommandErrorKind::Unauthenticated,
3043
                            "media request could not be authorized",
3044
                        )
3045
                    })
3046
                    .and_then(build)
3047
            } else {
3048
                #[allow(deprecated)]
3049
                let endpoint =
3050
                    media::get_content::v3::Request::from_url(&file.url).map_err(|source| {
3051
                        CommandError::with_source(
3052
                            CommandErrorKind::Other,
3053
                            "media URI is invalid",
3054
                            source,
3055
                        )
3056
                    })?;
3057
                endpoint
3058
                    .try_into_http_request::<Vec<u8>>(
3059
                        &base,
3060
                        SendAccessToken::None,
3061
                        Cow::Borrowed(&versions),
3062
                    )
3063
                    .map_err(|_| {
3064
                        CommandError::new(CommandErrorKind::Other, "media request is invalid")
3065
                    })
3066
                    .and_then(build)
3067
            }
3068
        }
3069
        matrix_sdk::ruma::events::room::MediaSource::Plain(uri) => {
3070
            if let MediaFormat::Thumbnail(settings) = &request.format {
3071
                if use_auth {
3072
                    let mut endpoint =
3073
                        authenticated_media::get_content_thumbnail::v1::Request::from_uri(
3074
                            uri,
3075
                            settings.width,
3076
                            settings.height,
3077
                        )
3078
                        .map_err(|source| {
3079
                            CommandError::with_source(
3080
                                CommandErrorKind::Other,
3081
                                "media URI is invalid",
3082
                                source,
3083
                            )
3084
                        })?;
3085
                    endpoint.method = Some(settings.method.clone());
3086
                    endpoint.animated = Some(settings.animated);
3087
                    endpoint
3088
                        .try_into_http_request::<Vec<u8>>(
3089
                            &base,
3090
                            SendAccessToken::IfRequired(token.as_deref().unwrap_or("")),
3091
                            Cow::Borrowed(&versions),
3092
                        )
3093
                        .map_err(|_| {
3094
                            CommandError::new(
3095
                                CommandErrorKind::Unauthenticated,
3096
                                "media request could not be authorized",
3097
                            )
3098
                        })
3099
                        .and_then(build)
3100
                } else {
3101
                    #[allow(deprecated)]
3102
                    let endpoint = {
3103
                        let mut endpoint = media::get_content_thumbnail::v3::Request::from_url(
3104
                            uri,
3105
                            settings.width,
3106
                            settings.height,
3107
                        )
3108
                        .map_err(|source| {
3109
                            CommandError::with_source(
3110
                                CommandErrorKind::Other,
3111
                                "media URI is invalid",
3112
                                source,
3113
                            )
3114
                        })?;
3115
                        endpoint.method = Some(settings.method.clone());
3116
                        endpoint.animated = Some(settings.animated);
3117
                        endpoint
3118
                    };
3119
                    endpoint
3120
                        .try_into_http_request::<Vec<u8>>(
3121
                            &base,
3122
                            SendAccessToken::None,
3123
                            Cow::Borrowed(&versions),
3124
                        )
3125
                        .map_err(|_| {
3126
                            CommandError::new(CommandErrorKind::Other, "media request is invalid")
3127
                        })
3128
                        .and_then(build)
3129
                }
3130
            } else if use_auth {
3131
                let endpoint = authenticated_media::get_content::v1::Request::from_uri(uri)
3132
                    .map_err(|source| {
3133
                        CommandError::with_source(
3134
                            CommandErrorKind::Other,
3135
                            "media URI is invalid",
3136
                            source,
3137
                        )
3138
                    })?;
3139
                endpoint
3140
                    .try_into_http_request::<Vec<u8>>(
3141
                        &base,
3142
                        SendAccessToken::IfRequired(token.as_deref().unwrap_or("")),
3143
                        Cow::Borrowed(&versions),
3144
                    )
3145
                    .map_err(|_| {
3146
                        CommandError::new(
3147
                            CommandErrorKind::Unauthenticated,
3148
                            "media request could not be authorized",
3149
                        )
3150
                    })
3151
                    .and_then(build)
3152
            } else {
3153
                #[allow(deprecated)]
3154
                let endpoint =
3155
                    media::get_content::v3::Request::from_url(uri).map_err(|source| {
3156
                        CommandError::with_source(
3157
                            CommandErrorKind::Other,
3158
                            "media URI is invalid",
3159
                            source,
3160
                        )
3161
                    })?;
3162
                endpoint
3163
                    .try_into_http_request::<Vec<u8>>(
3164
                        &base,
3165
                        SendAccessToken::None,
3166
                        Cow::Borrowed(&versions),
3167
                    )
3168
                    .map_err(|_| {
3169
                        CommandError::new(CommandErrorKind::Other, "media request is invalid")
3170
                    })
3171
                    .and_then(build)
3172
            }
3173
        }
3174
    }
3175
}
3176
3177
fn request_from_http(
3178
    request: http::Request<Vec<u8>>,
3179
) -> Result<matrix_sdk::reqwest::Request, CommandError> {
3180
    let (parts, _) = request.into_parts();
3181
    let mut builder = matrix_sdk::reqwest::Client::new().get(parts.uri.to_string());
3182
    for (name, value) in parts.headers.iter() {
3183
        builder = builder.header(name.as_str(), value.as_bytes());
3184
    }
3185
    builder
3186
        .build()
3187
        .map_err(|_| CommandError::new(CommandErrorKind::Network, "media request is invalid"))
3188
}
3189
3190
impl RoomActor {
3191
    async fn run(mut self, current_user_id: Option<ruma::OwnedUserId>) {
3192
        let timeline = match self
3193
            .room
3194
            .timeline_builder()
3195
            .with_focus(TimelineFocus::Live {
3196
                hide_threaded_events: true,
3197
            })
3198
            .track_read_marker_and_receipts(TimelineReadReceiptTracking::AllEvents)
3199
            .build()
3200
            .await
3201
        {
3202
            Ok(timeline) => timeline,
3203
            Err(error) => {
3204
                tracing::warn!(error = %error, room_id = %self.room_id, "room actor could not build its timeline");
3205
                let message = format!("room timeline build failed: {error}");
3206
                for id in self.open_ids.drain(..) {
3207
                    let _ = emit_command_result(
3208
                        &self.events,
3209
                        id,
3210
                        CommandKind::Room,
3211
                        Err(CommandError::new(
3212
                            CommandErrorKind::Timeline,
3213
                            message.clone(),
3214
                        )),
3215
                    )
3216
                    .await;
3217
                }
3218
                self.receiver.close();
3219
                while let Some(request) = self.receiver.recv().await {
3220
                    let (id, command) = match request {
3221
                        RoomActorRequest::Opened { id } | RoomActorRequest::Close { id } => {
3222
                            (id, CommandKind::Room)
3223
                        }
3224
                        RoomActorRequest::Execute { id, .. } => (id, CommandKind::Timeline),
3225
                    };
3226
                    let _ = emit_command_result(
3227
                        &self.events,
3228
                        id,
3229
                        command,
3230
                        Err(CommandError::new(
3231
                            CommandErrorKind::Timeline,
3232
                            "room timeline build failed",
3233
                        )),
3234
                    )
3235
                    .await;
3236
                }
3237
                return;
3238
            }
3239
        };
3240
        let room_timeline_id = TimelineId::Room(self.room_id.clone());
3241
        let mut room_timeline = match TimelineSession::start(
3242
            room_timeline_id,
3243
            timeline,
3244
            self.events.clone(),
3245
            self.media.clone(),
3246
            current_user_id.clone(),
3247
            self.initial_history_limit,
3248
        )
3249
        .await
3250
        {
3251
            Ok(timeline) => timeline,
3252
            Err(_) => return,
3253
        };
3254
        let _ = self.engine_events.send(RoomActorEvent::Ready {
3255
            room_id: self.room_id.clone(),
3256
            generation: self.generation,
3257
        });
3258
        for id in self.open_ids.drain(..) {
3259
            if emit_command_result(&self.events, id, CommandKind::Room, Ok(()))
3260
                .await
3261
                .is_err()
3262
            {
3263
                return;
3264
            }
3265
        }
3266
3267
        let (typing_guard, mut typing_rx) = self.room.subscribe_to_typing_notifications();
3268
        let typing_sender = self.events.clone();
3269
        let typing_room_id = self.room_id.clone();
3270
        let mut room_tasks = JoinSet::new();
3271
        room_tasks.spawn(async move {
3272
            loop {
3273
                match typing_rx.recv().await {
3274
                    Ok(users) => {
3275
                        let state = TypingState {
3276
                            room_id: typing_room_id.clone(),
3277
                            users,
3278
                        };
3279
                        if typing_sender
3280
                            .send(Event::TypingChanged(state))
3281
                            .await
3282
                            .is_err()
3283
                        {
3284
                            tracing::debug!(
3285
                                room_id = %typing_room_id,
3286
                                "typing watcher stopped because the event receiver closed"
3287
                            );
3288
                            break;
3289
                        }
3290
                    }
3291
                    Err(tokio::sync::broadcast::error::RecvError::Lagged(skipped)) => {
3292
                        tracing::warn!(room_id = %typing_room_id, skipped, "typing watcher lagged");
3293
                    }
3294
                    Err(tokio::sync::broadcast::error::RecvError::Closed) => {
3295
                        tracing::debug!(room_id = %typing_room_id, "typing watcher stopped");
3296
                        break;
3297
                    }
3298
                }
3299
            }
3300
        });
3301
3302
        enum QueuedThreadCommand {
3303
            Open {
3304
                id: CommandId,
3305
            },
3306
            Execute {
3307
                id: CommandId,
3308
                command: TimelineCommand,
3309
            },
3310
        }
3311
3312
        struct OpeningThread {
3313
            generation: u64,
3314
            abort: AbortHandle,
3315
            task_id: TaskId,
3316
            queued: VecDeque<QueuedThreadCommand>,
3317
        }
3318
3319
        let mut thread_timelines = HashMap::<OwnedEventId, TimelineSession>::new();
3320
        let mut opening_threads = HashMap::<OwnedEventId, OpeningThread>::new();
3321
        let mut thread_task_ids = HashMap::<TaskId, OwnedEventId>::new();
3322
        let mut thread_open_tasks = JoinSet::new();
3323
        let mut next_thread_generation = 0_u64;
3324
        let mut close_id = None;
3325
        let thread_queue_cap = self.receiver.max_capacity();
3326
        let mut queued_thread_count = 0;
3327
3328
        'actor: loop {
3329
            tokio::select! {
3330
                request = self.receiver.recv() => {
3331
                    let Some(request) = request else {
3332
                        tracing::debug!(room_id = %self.room_id, "room actor stopped because its request channel closed");
3333
                        break;
3334
                    };
3335
                    match request {
3336
                        RoomActorRequest::Opened { id } => {
3337
                            if emit_command_result(
3338
                                &self.events,
3339
                                id,
3340
                                CommandKind::Room,
3341
                                Ok(()),
3342
                            )
3343
                            .await
3344
                            .is_err()
3345
                            {
3346
                                break;
3347
                            }
3348
                        }
3349
                        RoomActorRequest::Execute { id, command } => {
3350
                            match command {
3351
                                TimelineCommand::OpenThread { root_event_id, .. } => {
3352
                                    if thread_timelines.contains_key(&root_event_id) {
3353
                                        if emit_command_result(
3354
                                            &self.events,
3355
                                            id,
3356
                                            CommandKind::Timeline,
3357
                                            Ok(()),
3358
                                        )
3359
                                        .await
3360
                                        .is_err()
3361
                                        {
3362
                                            break 'actor;
3363
                                        }
3364
                                    } else if let Some(opening) =
3365
                                        opening_threads.get_mut(&root_event_id)
3366
                                    {
3367
                                        if queued_thread_count >= thread_queue_cap {
3368
                                            if emit_command_result(
3369
                                                &self.events,
3370
                                                id,
3371
                                                CommandKind::Timeline,
3372
                                                Err(CommandError::new(
3373
                                                    CommandErrorKind::QueueFull,
3374
                                                    "thread command queue is full",
3375
                                                )),
3376
                                            )
3377
                                            .await
3378
                                            .is_err()
3379
                                            {
3380
                                                break 'actor;
3381
                                            }
3382
                                        } else {
3383
                                            opening
3384
                                                .queued
3385
                                                .push_back(QueuedThreadCommand::Open { id });
3386
                                            queued_thread_count += 1;
3387
                                        }
3388
                                    } else if queued_thread_count >= thread_queue_cap {
3389
                                        if emit_command_result(
3390
                                            &self.events,
3391
                                            id,
3392
                                            CommandKind::Timeline,
3393
                                            Err(CommandError::new(
3394
                                                CommandErrorKind::QueueFull,
3395
                                                "thread command queue is full",
3396
                                            )),
3397
                                        )
3398
                                        .await
3399
                                        .is_err()
3400
                                        {
3401
                                            break 'actor;
3402
                                        }
3403
                                    } else {
3404
                                        let generation = next_thread_generation;
3405
                                        next_thread_generation =
3406
                                            next_thread_generation.wrapping_add(1);
3407
                                        let task_root = root_event_id.clone();
3408
                                        let room = self.room.clone();
3409
                                        let abort = thread_open_tasks.spawn(async move {
3410
                                            let result = room
3411
                                                .timeline_builder()
3412
                                                .with_focus(TimelineFocus::Thread {
3413
                                                    root_event_id: root_event_id.clone(),
3414
                                                })
3415
                                                .track_read_marker_and_receipts(
3416
                                                    TimelineReadReceiptTracking::AllEvents,
3417
                                                )
3418
                                                .build()
3419
                                                .await;
3420
                                            (root_event_id, generation, result)
3421
                                        });
3422
                                        let task_id = abort.id();
3423
                                        thread_task_ids.insert(task_id, task_root.clone());
3424
                                        opening_threads.insert(
3425
                                            task_root,
3426
                                            OpeningThread {
3427
                                                generation,
3428
                                                abort,
3429
                                                task_id,
3430
                                                queued: VecDeque::from([
3431
                                                    QueuedThreadCommand::Open { id },
3432
                                                ]),
3433
                                            },
3434
                                        );
3435
                                        queued_thread_count += 1;
3436
                                    }
3437
                                }
3438
                                TimelineCommand::CloseThread { root_event_id, .. } => {
3439
                                    if let Some(timeline) = thread_timelines.remove(&root_event_id) {
3440
                                        timeline
3441
                                            .close(
3442
                                                CommandErrorKind::TimelineNotOpen,
3443
                                                "thread closed while pagination was in progress",
3444
                                            )
3445
                                            .await;
3446
                                        if emit_command_result(
3447
                                            &self.events,
3448
                                            id,
3449
                                            CommandKind::Timeline,
3450
                                            Ok(()),
3451
                                        )
3452
                                        .await
3453
                                        .is_err()
3454
                                        {
3455
                                            break 'actor;
3456
                                        }
3457
                                    } else if let Some(opening) =
3458
                                        opening_threads.remove(&root_event_id)
3459
                                    {
3460
                                        thread_task_ids.remove(&opening.task_id);
3461
                                        queued_thread_count -= opening.queued.len();
3462
                                        opening.abort.abort();
3463
                                        for queued in opening.queued {
3464
                                            let queued_id = match queued {
3465
                                                QueuedThreadCommand::Open { id } => id,
3466
                                                QueuedThreadCommand::Execute { id, .. } => id,
3467
                                            };
3468
                                            let _ = emit_command_result(
3469
                                                &self.events,
3470
                                                queued_id,
3471
                                                CommandKind::Timeline,
3472
                                                Err(CommandError::new(
3473
                                                    CommandErrorKind::TimelineNotOpen,
3474
                                                    "thread was closed before it finished opening",
3475
                                                )),
3476
                                            )
3477
                                            .await;
3478
                                        }
3479
                                        if emit_command_result(
3480
                                            &self.events,
3481
                                            id,
3482
                                            CommandKind::Timeline,
3483
                                            Ok(()),
3484
                                        )
3485
                                        .await
3486
                                        .is_err()
3487
                                        {
3488
                                            break 'actor;
3489
                                        }
3490
                                    } else if emit_command_result(
3491
                                        &self.events,
3492
                                        id,
3493
                                        CommandKind::Timeline,
3494
                                        Err(CommandError::new(
3495
                                            CommandErrorKind::TimelineNotOpen,
3496
                                            "thread timeline is not open",
3497
                                        )),
3498
                                    )
3499
                                    .await
3500
                                    .is_err()
3501
                                    {
3502
                                        break 'actor;
3503
                                    }
3504
                                }
3505
                                TimelineCommand::Paginate { timeline_id, limit } => {
3506
                                    match timeline_id {
3507
                                        TimelineId::Room(_) => {
3508
                                            room_timeline.paginate(limit, Some(id)).await;
3509
                                        }
3510
                                        TimelineId::Thread { room_id, root_event_id } => {
3511
                                            if let Some(timeline) =
3512
                                                thread_timelines.get_mut(&root_event_id)
3513
                                            {
3514
                                                timeline.paginate(limit, Some(id)).await;
3515
                                            } else if let Some(opening) =
3516
                                                opening_threads.get_mut(&root_event_id)
3517
                                            {
3518
                                                if queued_thread_count >= thread_queue_cap {
3519
                                                    if emit_command_result(
3520
                                                        &self.events,
3521
                                                        id,
3522
                                                        CommandKind::Timeline,
3523
                                                        Err(CommandError::new(
3524
                                                            CommandErrorKind::QueueFull,
3525
                                                            "thread command queue is full",
3526
                                                        )),
3527
                                                    )
3528
                                                    .await
3529
                                                    .is_err()
3530
                                                    {
3531
                                                        break 'actor;
3532
                                                    }
3533
                                                } else {
3534
                                                    opening.queued.push_back(
3535
                                                        QueuedThreadCommand::Execute {
3536
                                                            id,
3537
                                                            command:
3538
                                                                TimelineCommand::Paginate {
3539
                                                                    timeline_id:
3540
                                                                        TimelineId::Thread {
3541
                                                                            room_id,
3542
                                                                            root_event_id,
3543
                                                                        },
3544
                                                                    limit,
3545
                                                                },
3546
                                                        },
3547
                                                    );
3548
                                                    queued_thread_count += 1;
3549
                                                }
3550
                                            } else if emit_command_result(
3551
                                                &self.events,
3552
                                                id,
3553
                                                CommandKind::Timeline,
3554
                                                Err(CommandError::new(
3555
                                                    CommandErrorKind::TimelineNotOpen,
3556
                                                    "timeline is not open",
3557
                                                )),
3558
                                            )
3559
                                            .await
3560
                                            .is_err()
3561
                                            {
3562
                                                break 'actor;
3563
                                            }
3564
                                        }
3565
                                    }
3566
                                }
3567
                                command => {
3568
                                    let thread_target = match command
3569
                                        .timeline_id()
3570
                                        .expect("non-lifecycle timeline command has a timeline id")
3571
                                    {
3572
                                        TimelineId::Room(_) => None,
3573
                                        TimelineId::Thread { root_event_id, .. } => {
3574
                                            Some(root_event_id.clone())
3575
                                        }
3576
                                    };
3577
                                    match thread_target {
3578
                                        None => {
3579
                                            let result = execute_timeline_command(
3580
                                                &mut room_timeline,
3581
                                                command,
3582
                                                None,
3583
                                            )
3584
                                            .await;
3585
                                            if emit_command_result(
3586
                                                &self.events,
3587
                                                id,
3588
                                                CommandKind::Timeline,
3589
                                                result,
3590
                                            )
3591
                                            .await
3592
                                            .is_err()
3593
                                            {
3594
                                                tracing::debug!(room_id = %self.room_id, "room actor stopped because the event receiver closed");
3595
                                                break;
3596
                                            }
3597
                                        }
3598
                                        Some(root_event_id) => {
3599
                                            if let Some(timeline) =
3600
                                                thread_timelines.get_mut(&root_event_id)
3601
                                            {
3602
                                                let thread_root =
3603
                                                    timeline.id.thread_root().cloned();
3604
                                                let result = execute_timeline_command(
3605
                                                    timeline,
3606
                                                    command,
3607
                                                    thread_root.as_ref(),
3608
                                                )
3609
                                                .await;
3610
                                                if emit_command_result(
3611
                                                    &self.events,
3612
                                                    id,
3613
                                                    CommandKind::Timeline,
3614
                                                    result,
3615
                                                )
3616
                                                .await
3617
                                                .is_err()
3618
                                                {
3619
                                                    tracing::debug!(room_id = %self.room_id, "room actor stopped because the event receiver closed");
3620
                                                    break;
3621
                                                }
3622
                                            } else if let Some(opening) =
3623
                                                opening_threads.get_mut(&root_event_id)
3624
                                            {
3625
                                                if queued_thread_count >= thread_queue_cap {
3626
                                                    if emit_command_result(
3627
                                                        &self.events,
3628
                                                        id,
3629
                                                        CommandKind::Timeline,
3630
                                                        Err(CommandError::new(
3631
                                                            CommandErrorKind::QueueFull,
3632
                                                            "thread command queue is full",
3633
                                                        )),
3634
                                                    )
3635
                                                    .await
3636
                                                    .is_err()
3637
                                                    {
3638
                                                        break 'actor;
3639
                                                    }
3640
                                                } else {
3641
                                                    opening.queued.push_back(
3642
                                                        QueuedThreadCommand::Execute { id, command },
3643
                                                    );
3644
                                                    queued_thread_count += 1;
3645
                                                }
3646
                                            } else if emit_command_result(
3647
                                                &self.events,
3648
                                                id,
3649
                                                CommandKind::Timeline,
3650
                                                Err(CommandError::new(
3651
                                                    CommandErrorKind::TimelineNotOpen,
3652
                                                    "timeline is not open",
3653
                                                )),
3654
                                            )
3655
                                            .await
3656
                                            .is_err()
3657
                                            {
3658
                                                tracing::debug!(room_id = %self.room_id, "room actor stopped because the event receiver closed");
3659
                                                break;
3660
                                            }
3661
                                        }
3662
                                    }
3663
                                }
3664
                            }
3665
                        }
3666
                        RoomActorRequest::Close { id } => {
3667
                            close_id = Some(id);
3668
                            break 'actor;
3669
                        }
3670
                    }
3671
                }
3672
                completed = thread_open_tasks.join_next(), if !thread_open_tasks.is_empty() => {
3673
                    match completed {
3674
                        Some(Ok((root_event_id, generation, result))) => {
3675
                            if opening_threads
3676
                                .get(&root_event_id)
3677
                                .is_none_or(|opening| opening.generation != generation)
3678
                            {
3679
                                continue;
3680
                            }
3681
                            let opening = opening_threads
3682
                                .remove(&root_event_id)
3683
                                .expect("thread generation was checked");
3684
                            thread_task_ids.remove(&opening.task_id);
3685
                            queued_thread_count -= opening.queued.len();
3686
                            match result {
3687
                                Ok(timeline) => {
3688
                                    let timeline_id = TimelineId::thread(
3689
                                        self.room_id.clone(),
3690
                                        root_event_id.clone(),
3691
                                    );
3692
                                    match TimelineSession::start(
3693
                                        timeline_id,
3694
                                        timeline,
3695
                                        self.events.clone(),
3696
                                        self.media.clone(),
3697
                                        current_user_id.clone(),
3698
                                        self.initial_history_limit,
3699
                                    )
3700
                                    .await
3701
                                    {
3702
                                        Ok(session) => {
3703
                                            thread_timelines
3704
                                                .insert(root_event_id.clone(), session);
3705
                                            let timeline = thread_timelines
3706
                                                .get_mut(&root_event_id)
3707
                                                .expect("thread session was inserted");
3708
                                            for queued in opening.queued {
3709
                                                match queued {
3710
                                                    QueuedThreadCommand::Open { id } => {
3711
                                                        if emit_command_result(
3712
                                                            &self.events,
3713
                                                            id,
3714
                                                            CommandKind::Timeline,
3715
                                                            Ok(()),
3716
                                                        )
3717
                                                        .await
3718
                                                        .is_err()
3719
                                                        {
3720
                                                            break 'actor;
3721
                                                        }
3722
                                                    }
3723
                                                    QueuedThreadCommand::Execute {
3724
                                                        id,
3725
                                                        command,
3726
                                                    } => match command {
3727
                                                        TimelineCommand::Paginate {
3728
                                                            limit,
3729
                                                            ..
3730
                                                        } => {
3731
                                                            timeline
3732
                                                                .paginate(limit, Some(id))
3733
                                                                .await;
3734
                                                        }
3735
                                                        command => {
3736
                                                            let thread_root = timeline
3737
                                                                .id
3738
                                                                .thread_root()
3739
                                                                .cloned();
3740
                                                            let result =
3741
                                                                execute_timeline_command(
3742
                                                                    timeline,
3743
                                                                    command,
3744
                                                                    thread_root.as_ref(),
3745
                                                                )
3746
                                                                .await;
3747
                                                            if emit_command_result(
3748
                                                                &self.events,
3749
                                                                id,
3750
                                                                CommandKind::Timeline,
3751
                                                                result,
3752
                                                            )
3753
                                                            .await
3754
                                                            .is_err()
3755
                                                            {
3756
                                                                break 'actor;
3757
                                                            }
3758
                                                        }
3759
                                                    },
3760
                                                }
3761
                                            }
3762
                                        }
3763
                                        Err(_) => break 'actor,
3764
                                    }
3765
                                }
3766
                                Err(error) => {
3767
                                    tracing::warn!(error = %error, room_id = %self.room_id, %root_event_id, "thread timeline build failed");
3768
                                    let message = format!("thread timeline build failed: {error}");
3769
                                    for queued in opening.queued {
3770
                                        let queued_id = match queued {
3771
                                            QueuedThreadCommand::Open { id } => id,
3772
                                            QueuedThreadCommand::Execute { id, .. } => id,
3773
                                        };
3774
                                        if emit_command_result(
3775
                                            &self.events,
3776
                                            queued_id,
3777
                                            CommandKind::Timeline,
3778
                                            Err(CommandError::new(
3779
                                                CommandErrorKind::Timeline,
3780
                                                message.clone(),
3781
                                            )),
3782
                                        )
3783
                                        .await
3784
                                        .is_err()
3785
                                        {
3786
                                            break 'actor;
3787
                                        }
3788
                                    }
3789
                                }
3790
                            }
3791
                        }
3792
                        Some(Err(error)) => {
3793
                            if let Some(root_event_id) = thread_task_ids.remove(&error.id()) {
3794
                                tracing::warn!(error = %error, room_id = %self.room_id, %root_event_id, "thread open task failed");
3795
                                if let Some(opening) = opening_threads.remove(&root_event_id) {
3796
                                    queued_thread_count -= opening.queued.len();
3797
                                    for queued in opening.queued {
3798
                                        let queued_id = match queued {
3799
                                            QueuedThreadCommand::Open { id } => id,
3800
                                            QueuedThreadCommand::Execute { id, .. } => id,
3801
                                        };
3802
                                        if emit_command_result(
3803
                                            &self.events,
3804
                                            queued_id,
3805
                                            CommandKind::Timeline,
3806
                                            Err(CommandError::new(
3807
                                                CommandErrorKind::Timeline,
3808
                                                "thread timeline build task failed",
3809
                                            )),
3810
                                        )
3811
                                        .await
3812
                                        .is_err()
3813
                                        {
3814
                                            break 'actor;
3815
                                        }
3816
                                    }
3817
                                }
3818
                            } else {
3819
                                tracing::debug!(error = %error, room_id = %self.room_id, "thread open task was aborted");
3820
                            }
3821
                        }
3822
                        None => {}
3823
                    }
3824
                }
3825
                completed = room_tasks.join_next(), if !room_tasks.is_empty() => {
3826
                    match completed {
3827
                        Some(Err(error)) => {
3828
                            tracing::warn!(error = %error, room_id = %self.room_id, "room actor task failed");
3829
                        }
3830
                        Some(Ok(())) => {
3831
                            tracing::warn!(room_id = %self.room_id, "room actor task stopped unexpectedly");
3832
                        }
3833
                        None => {}
3834
                    }
3835
                }
3836
            }
3837
        }
3838
3839
        drop(typing_guard);
3840
        room_tasks.abort_all();
3841
        while room_tasks.join_next().await.is_some() {}
3842
        thread_task_ids.clear();
3843
        let pending_opens = opening_threads
3844
            .drain()
3845
            .flat_map(|(_, opening)| opening.queued)
3846
            .collect::<Vec<_>>();
3847
        for queued in pending_opens {
3848
            let queued_id = match queued {
3849
                QueuedThreadCommand::Open { id } => id,
3850
                QueuedThreadCommand::Execute { id, .. } => id,
3851
            };
3852
            let _ = emit_command_result(
3853
                &self.events,
3854
                queued_id,
3855
                CommandKind::Timeline,
3856
                Err(CommandError::new(
3857
                    CommandErrorKind::RoomNotOpen,
3858
                    "room closed while thread timeline was opening",
3859
                )),
3860
            )
3861
            .await;
3862
        }
3863
        thread_open_tasks.abort_all();
3864
        while thread_open_tasks.join_next().await.is_some() {}
3865
        for (_, timeline) in thread_timelines {
3866
            timeline
3867
                .close(
3868
                    CommandErrorKind::RoomNotOpen,
3869
                    "room closed while thread pagination was in progress",
3870
                )
3871
                .await;
3872
        }
3873
        room_timeline
3874
            .close(
3875
                CommandErrorKind::RoomNotOpen,
3876
                "room closed while pagination was in progress",
3877
            )
3878
            .await;
3879
        if let Some(id) = close_id
3880
            && emit_command_result(&self.events, id, CommandKind::Room, Ok(()))
3881
                .await
3882
                .is_err()
3883
        {
3884
            tracing::debug!(room_id = %self.room_id, "room close acknowledgement was not delivered");
3885
        }
3886
    }
3887
}
3888
3889
async fn execute_timeline_command(
3890
    session: &mut TimelineSession,
3891
    command: TimelineCommand,
3892
    thread_root: Option<&OwnedEventId>,
3893
) -> Result<(), CommandError> {
3894
    let timeline = &session.timeline;
3895
    match command {
3896
        TimelineCommand::OpenThread { .. } | TimelineCommand::CloseThread { .. } => {
3897
            Err(CommandError::new(
3898
                CommandErrorKind::Other,
3899
                "thread lifecycle command was misrouted",
3900
            ))
3901
        }
3902
        TimelineCommand::Resync { .. } => session.resync().await.map_err(|_| {
3903
            CommandError::new(
3904
                CommandErrorKind::Timeline,
3905
                "timeline resync could not emit a reset",
3906
            )
3907
        }),
3908
        TimelineCommand::Paginate { .. } => Ok(()),
3909
        TimelineCommand::Send { content, .. } => {
3910
            let body = match &content {
3911
                MessageContent::Text { content, .. }
3912
                | MessageContent::Emote { content, .. }
3913
                | MessageContent::Reply { content, .. } => &content.body,
3914
            };
3915
            if body.trim().is_empty() {
3916
                return Err(CommandError::new(
3917
                    CommandErrorKind::EmptyBody,
3918
                    "message body is empty",
3919
                ));
3920
            }
3921
            match content {
3922
                MessageContent::Text { content, mentions } => timeline
3923
                    .send(
3924
                        message_content(content, mentions, false)
3925
                            .with_relation(None)
3926
                            .into(),
3927
                    )
3928
                    .await
3929
                    .map(|_| ())
3930
                    .map_err(timeline_error),
3931
                MessageContent::Emote { content, mentions } => timeline
3932
                    .send(
3933
                        message_content(content, mentions, true)
3934
                            .with_relation(None)
3935
                            .into(),
3936
                    )
3937
                    .await
3938
                    .map(|_| ())
3939
                    .map_err(timeline_error),
3940
                MessageContent::Reply {
3941
                    event_id,
3942
                    content,
3943
                    mentions,
3944
                    notify_replied_to,
3945
                } => {
3946
                    let content = timeline
3947
                        .room()
3948
                        .make_reply_event(
3949
                            message_content(content, mentions, false),
3950
                            SdkReply {
3951
                                event_id,
3952
                                enforce_thread: if thread_root.is_some() {
3953
                                    EnforceThread::Threaded(ReplyWithinThread::Yes)
3954
                                } else {
3955
                                    EnforceThread::MaybeThreaded
3956
                                },
3957
                                add_mentions: if notify_replied_to {
3958
                                    AddMentions::Yes
3959
                                } else {
3960
                                    AddMentions::No
3961
                                },
3962
                            },
3963
                        )
3964
                        .await
3965
                        .map_err(|source| {
3966
                            CommandError::with_source(
3967
                                CommandErrorKind::Timeline,
3968
                                "reply operation failed",
3969
                                source,
3970
                            )
3971
                        })?;
3972
                    if let Some(thread_root) = thread_root
3973
                        && !matches!(
3974
                            content.relates_to.as_ref(),
3975
                            Some(Relation::Thread(thread)) if &thread.event_id == thread_root
3976
                        )
3977
                    {
3978
                        return Err(CommandError::new(
3979
                            CommandErrorKind::Timeline,
3980
                            "reply target does not belong to the selected thread",
3981
                        ));
3982
                    }
3983
                    timeline
3984
                        .send(content.into())
3985
                        .await
3986
                        .map(|_| ())
3987
                        .map_err(timeline_error)
3988
                }
3989
            }
3990
        }
3991
        TimelineCommand::Edit {
3992
            event_id, content, ..
3993
        } => {
3994
            let (content, mentions, emote) = match content {
3995
                MessageEditContent::Text { content, mentions } => (content, mentions, false),
3996
                MessageEditContent::Emote { content, mentions } => (content, mentions, true),
3997
            };
3998
            if content.body.trim().is_empty() {
3999
                return Err(CommandError::new(
4000
                    CommandErrorKind::EmptyBody,
4001
                    "message body is empty",
4002
                ));
4003
            }
4004
            timeline
4005
                .edit(
4006
                    &TimelineEventItemId::EventId(event_id),
4007
                    EditedContent::RoomMessage(message_content(content, mentions, emote)),
4008
                )
4009
                .await
4010
                .map_err(timeline_error)
4011
        }
4012
        TimelineCommand::Redact {
4013
            event_id, reason, ..
4014
        } => timeline
4015
            .redact(&TimelineEventItemId::EventId(event_id), reason.as_deref())
4016
            .await
4017
            .map_err(timeline_error),
4018
        TimelineCommand::ToggleReaction { event_id, key, .. } => timeline
4019
            .toggle_reaction(&TimelineEventItemId::EventId(event_id), &key)
4020
            .await
4021
            .map(|_| ())
4022
            .map_err(timeline_error),
4023
    }
4024
}
4025
4026
fn message_content(
4027
    content: RichText,
4028
    mentions: MessageMentions,
4029
    emote: bool,
4030
) -> RoomMessageEventContentWithoutRelation {
4031
    let content = match content.formatted_body {
4032
        Some(formatted_body) if emote => {
4033
            RoomMessageEventContentWithoutRelation::emote_html(content.body, formatted_body)
4034
        }
4035
        Some(formatted_body) => {
4036
            RoomMessageEventContentWithoutRelation::text_html(content.body, formatted_body)
4037
        }
4038
        None if emote => RoomMessageEventContentWithoutRelation::emote_plain(content.body),
4039
        None => RoomMessageEventContentWithoutRelation::text_plain(content.body),
4040
    };
4041
    let content = content.add_mentions(Mentions::with_user_ids(mentions.user_ids));
4042
    if mentions.room {
4043
        content.add_mentions(Mentions::with_room_mention())
4044
    } else {
4045
        content
4046
    }
4047
}
4048
4049
async fn run_pagination(request: PaginationRequest) {
4050
    let PaginationRequest {
4051
        timeline,
4052
        events,
4053
        timeline_id,
4054
        reached_start,
4055
        limit,
4056
        command_id,
4057
        results,
4058
    } = request;
4059
    // One worker per timeline preserves pagination order without accumulating waiting tasks.
4060
    if reached_start.load(Ordering::Acquire) {
4061
        let _ = events
4062
            .send(Event::HistoryChanged {
4063
                timeline_id,
4064
                state: HistoryState::StartReached,
4065
            })
4066
            .await;
4067
        if let Some(id) = command_id {
4068
            let _ = results.send((id, Ok(())));
4069
        }
4070
        return;
4071
    }
4072
    if events
4073
        .send(Event::HistoryChanged {
4074
            timeline_id: timeline_id.clone(),
4075
            state: HistoryState::Loading,
4076
        })
4077
        .await
4078
        .is_err()
4079
    {
4080
        if let Some(id) = command_id {
4081
            let _ = results.send((
4082
                id,
4083
                Err(CommandError::new(
4084
                    CommandErrorKind::EventReceiverClosed,
4085
                    "event receiver has closed",
4086
                )),
4087
            ));
4088
        }
4089
        return;
4090
    }
4091
    let result = timeline
4092
        .paginate_backwards(limit.max(1))
4093
        .await
4094
        .map_err(timeline_error);
4095
    match result {
4096
        Ok(at_start) => {
4097
            reached_start.store(at_start, Ordering::Release);
4098
            let _ = events
4099
                .send(Event::HistoryChanged {
4100
                    timeline_id,
4101
                    state: if at_start {
4102
                        HistoryState::StartReached
4103
                    } else {
4104
                        HistoryState::Idle
4105
                    },
4106
                })
4107
                .await;
4108
            if let Some(id) = command_id {
4109
                let _ = results.send((id, Ok(())));
4110
            }
4111
        }
4112
        Err(error) => {
4113
            let _ = events
4114
                .send(Event::HistoryChanged {
4115
                    timeline_id,
4116
                    state: HistoryState::Failed(error.to_string()),
4117
                })
4118
                .await;
4119
            if let Some(id) = command_id {
4120
                let _ = results.send((id, Err(error)));
4121
            }
4122
        }
4123
    }
4124
}
4125
4126
async fn emit_command_result_with_room(
4127
    events: &EventSender,
4128
    id: CommandId,
4129
    room_id: OwnedRoomId,
4130
) -> Result<(), SendEventError> {
4131
    events
4132
        .send(Event::CommandSucceededWithRoom {
4133
            id,
4134
            command: CommandKind::Room,
4135
            room_id,
4136
        })
4137
        .await
4138
}
4139
4140
async fn emit_command_result(
4141
    events: &EventSender,
4142
    id: CommandId,
4143
    command: CommandKind,
4144
    result: Result<(), CommandError>,
4145
) -> Result<(), SendEventError> {
4146
    let event = match result {
4147
        Ok(()) => Event::CommandSucceeded { id, command },
4148
        Err(error) => {
4149
            tracing::warn!(?id, ?command, error = ?error, "room actor command failed");
4150
            Event::CommandFailed(CommandFailure {
4151
                id,
4152
                command,
4153
                kind: error.kind,
4154
                message: error.to_string(),
4155
            })
4156
        }
4157
    };
4158
    events.send(event).await
4159
}
4160
4161
fn send_error(source: SendEventError) -> CommandError {
4162
    CommandError::with_source(
4163
        CommandErrorKind::EventReceiverClosed,
4164
        "event receiver has closed",
4165
        source,
4166
    )
4167
}
4168
4169
fn timeline_error(source: matrix_sdk_ui::timeline::Error) -> CommandError {
4170
    CommandError::with_source(
4171
        CommandErrorKind::Timeline,
4172
        "timeline operation failed",
4173
        source,
4174
    )
4175
}