atagen

macltok

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