atagen

macltok

public
Star0
Watch0
fa9114fcf75f7bdf1a7cec3b3367d604cf7de472
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git

Blame: src/engine.rs

96fdf90aacd6atagen2026-09-08 08:24 UTC1
use std::{
96fdf90aacd6atagen2026-09-08 08:24 UTC2
    borrow::Cow,
96fdf90aacd6atagen2026-09-08 08:24 UTC3
    collections::{HashMap, HashSet, VecDeque},
96fdf90aacd6atagen2026-09-08 08:24 UTC4
    future::Future,
96fdf90aacd6atagen2026-09-08 08:24 UTC5
    io::Read as _,
96fdf90aacd6atagen2026-09-08 08:24 UTC6
    panic::AssertUnwindSafe,
fa9114fcf75fatagen2026-09-24 12:51 UTC7
    sync::Arc,
fa9114fcf75fatagen2026-09-24 12:51 UTC8
    time::Duration,
96fdf90aacd6atagen2026-09-08 08:24 UTC9
};
96fdf90aacd6atagen2026-09-08 08:24 UTC10
96fdf90aacd6atagen2026-09-08 08:24 UTC11
use futures_util::{FutureExt, StreamExt};
96fdf90aacd6atagen2026-09-08 08:24 UTC12
use matrix_sdk::{
96fdf90aacd6atagen2026-09-08 08:24 UTC13
    Client, Room, SessionChange,
96fdf90aacd6atagen2026-09-08 08:24 UTC14
    media::{MediaFormat, MediaRequestParameters, MediaThumbnailSettings},
96fdf90aacd6atagen2026-09-08 08:24 UTC15
    room::edit::EditedContent,
96fdf90aacd6atagen2026-09-08 08:24 UTC16
    room::reply::{EnforceThread, Reply as SdkReply},
96fdf90aacd6atagen2026-09-08 08:24 UTC17
    ruma::{
96fdf90aacd6atagen2026-09-08 08:24 UTC18
        OwnedDeviceId, OwnedEventId, OwnedRoomId, RoomId,
96fdf90aacd6atagen2026-09-08 08:24 UTC19
        api::client::receipt::create_receipt::v3::ReceiptType,
96fdf90aacd6atagen2026-09-08 08:24 UTC20
        events::{
96fdf90aacd6atagen2026-09-08 08:24 UTC21
            AnyGlobalAccountDataEventContent, AnyMessageLikeEventContent,
96fdf90aacd6atagen2026-09-08 08:24 UTC22
            GlobalAccountDataEventType, Mentions, RoomAccountDataEventType,
96fdf90aacd6atagen2026-09-08 08:24 UTC23
            receipt::ReceiptThread,
96fdf90aacd6atagen2026-09-08 08:24 UTC24
            room::message::{
96fdf90aacd6atagen2026-09-08 08:24 UTC25
                AddMentions, MessageType, Relation, ReplyWithinThread,
96fdf90aacd6atagen2026-09-08 08:24 UTC26
                RoomMessageEventContentWithoutRelation,
96fdf90aacd6atagen2026-09-08 08:24 UTC27
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC28
        },
96fdf90aacd6atagen2026-09-08 08:24 UTC29
        serde::Raw,
96fdf90aacd6atagen2026-09-08 08:24 UTC30
    },
96fdf90aacd6atagen2026-09-08 08:24 UTC31
    send_queue::{LocalEchoContent, RoomSendQueueUpdate, SendHandle},
96fdf90aacd6atagen2026-09-08 08:24 UTC32
};
96fdf90aacd6atagen2026-09-08 08:24 UTC33
use matrix_sdk_ui::{
96fdf90aacd6atagen2026-09-08 08:24 UTC34
    sync_service::{State as SyncState, SyncService},
96fdf90aacd6atagen2026-09-08 08:24 UTC35
    timeline::{
96fdf90aacd6atagen2026-09-08 08:24 UTC36
        RoomExt, Timeline, TimelineEventItemId, TimelineFocus, TimelineReadReceiptTracking,
96fdf90aacd6atagen2026-09-08 08:24 UTC37
    },
96fdf90aacd6atagen2026-09-08 08:24 UTC38
};
96fdf90aacd6atagen2026-09-08 08:24 UTC39
use thiserror::Error;
96fdf90aacd6atagen2026-09-08 08:24 UTC40
use tokio::{
96fdf90aacd6atagen2026-09-08 08:24 UTC41
    sync::{Mutex, mpsc, oneshot, watch},
96fdf90aacd6atagen2026-09-08 08:24 UTC42
    task::{AbortHandle, Id as TaskId, JoinHandle, JoinSet},
96fdf90aacd6atagen2026-09-08 08:24 UTC43
};
96fdf90aacd6atagen2026-09-08 08:24 UTC44
96fdf90aacd6atagen2026-09-08 08:24 UTC45
use crate::{
96fdf90aacd6atagen2026-09-08 08:24 UTC46
    AccountCommand, AttachmentUpload, ClientHandle, Command, CommandErrorKind, CommandFailure,
96fdf90aacd6atagen2026-09-08 08:24 UTC47
    CommandId, CommandKind, ConnectionState, CreateRoomOptions, EncryptionCommand, EngineErrorKind,
96fdf90aacd6atagen2026-09-08 08:24 UTC48
    EngineFailure, Event, EventStream, HistoryState, InteractiveAuth, InteractiveAuthChallenge,
96fdf90aacd6atagen2026-09-08 08:24 UTC49
    InteractiveAuthOutcome, LifecycleCommand, MediaCommand, MediaId, MediaSize, MessageContent,
96fdf90aacd6atagen2026-09-08 08:24 UTC50
    MessageEditContent, MessageMentions, NotificationCommand, PowerLevelChanges, RichText,
96fdf90aacd6atagen2026-09-08 08:24 UTC51
    RoomCommand, RoomNotificationMode, RoomPreset, RoomVisibility, TimelineCommand, TimelineId,
96fdf90aacd6atagen2026-09-08 08:24 UTC52
    TypingState,
96fdf90aacd6atagen2026-09-08 08:24 UTC53
    encryption::{VerificationController, encryption_state},
96fdf90aacd6atagen2026-09-08 08:24 UTC54
    error::{CommandError, classify_sdk_error},
96fdf90aacd6atagen2026-09-08 08:24 UTC55
    normalise::{MediaRegistration, MediaScope, normalise_timeline, normalise_timeline_diffs},
96fdf90aacd6atagen2026-09-08 08:24 UTC56
    transport::{
96fdf90aacd6atagen2026-09-08 08:24 UTC57
        ClientQueryError, CommandEnvelope, CommandResultError, DriverPort, DriverRequest,
96fdf90aacd6atagen2026-09-08 08:24 UTC58
        EventSender, SendEventError, transport_pair,
96fdf90aacd6atagen2026-09-08 08:24 UTC59
    },
96fdf90aacd6atagen2026-09-08 08:24 UTC60
    upload::prepare_upload,
96fdf90aacd6atagen2026-09-08 08:24 UTC61
};
96fdf90aacd6atagen2026-09-08 08:24 UTC62
use matrix_sdk::ruma::api::{Metadata, OutgoingRequest, auth_scheme::SendAccessToken};
96fdf90aacd6atagen2026-09-08 08:24 UTC63
use matrix_sdk_crypto::{AttachmentDecryptor, MediaEncryptionInfo};
96fdf90aacd6atagen2026-09-08 08:24 UTC64
96fdf90aacd6atagen2026-09-08 08:24 UTC65
#[derive(Clone, Debug, Eq, PartialEq)]
96fdf90aacd6atagen2026-09-08 08:24 UTC66
#[non_exhaustive]
96fdf90aacd6atagen2026-09-08 08:24 UTC67
pub struct EngineConfig {
96fdf90aacd6atagen2026-09-08 08:24 UTC68
    pub command_capacity: usize,
96fdf90aacd6atagen2026-09-08 08:24 UTC69
    pub event_capacity: usize,
96fdf90aacd6atagen2026-09-08 08:24 UTC70
    pub initial_history_limit: u16,
96fdf90aacd6atagen2026-09-08 08:24 UTC71
    pub max_media_bytes: usize,
96fdf90aacd6atagen2026-09-08 08:24 UTC72
    pub max_media_sources: usize,
96fdf90aacd6atagen2026-09-08 08:24 UTC73
}
96fdf90aacd6atagen2026-09-08 08:24 UTC74
96fdf90aacd6atagen2026-09-08 08:24 UTC75
impl EngineConfig {
96fdf90aacd6atagen2026-09-08 08:24 UTC76
    #[must_use]
96fdf90aacd6atagen2026-09-08 08:24 UTC77
    pub const fn with_command_capacity(mut self, capacity: usize) -> Self {
96fdf90aacd6atagen2026-09-08 08:24 UTC78
        self.command_capacity = capacity;
96fdf90aacd6atagen2026-09-08 08:24 UTC79
        self
96fdf90aacd6atagen2026-09-08 08:24 UTC80
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC81
96fdf90aacd6atagen2026-09-08 08:24 UTC82
    #[must_use]
96fdf90aacd6atagen2026-09-08 08:24 UTC83
    pub const fn with_event_capacity(mut self, capacity: usize) -> Self {
96fdf90aacd6atagen2026-09-08 08:24 UTC84
        self.event_capacity = capacity;
96fdf90aacd6atagen2026-09-08 08:24 UTC85
        self
96fdf90aacd6atagen2026-09-08 08:24 UTC86
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC87
96fdf90aacd6atagen2026-09-08 08:24 UTC88
    #[must_use]
96fdf90aacd6atagen2026-09-08 08:24 UTC89
    pub const fn with_initial_history_limit(mut self, limit: u16) -> Self {
96fdf90aacd6atagen2026-09-08 08:24 UTC90
        self.initial_history_limit = limit;
96fdf90aacd6atagen2026-09-08 08:24 UTC91
        self
96fdf90aacd6atagen2026-09-08 08:24 UTC92
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC93
96fdf90aacd6atagen2026-09-08 08:24 UTC94
    #[must_use]
96fdf90aacd6atagen2026-09-08 08:24 UTC95
    pub const fn with_max_media_bytes(mut self, bytes: usize) -> Self {
96fdf90aacd6atagen2026-09-08 08:24 UTC96
        self.max_media_bytes = bytes;
96fdf90aacd6atagen2026-09-08 08:24 UTC97
        self
96fdf90aacd6atagen2026-09-08 08:24 UTC98
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC99
96fdf90aacd6atagen2026-09-08 08:24 UTC100
    #[must_use]
96fdf90aacd6atagen2026-09-08 08:24 UTC101
    pub const fn with_max_media_sources(mut self, sources: usize) -> Self {
96fdf90aacd6atagen2026-09-08 08:24 UTC102
        self.max_media_sources = sources;
96fdf90aacd6atagen2026-09-08 08:24 UTC103
        self
96fdf90aacd6atagen2026-09-08 08:24 UTC104
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC105
}
96fdf90aacd6atagen2026-09-08 08:24 UTC106
96fdf90aacd6atagen2026-09-08 08:24 UTC107
impl Default for EngineConfig {
96fdf90aacd6atagen2026-09-08 08:24 UTC108
    fn default() -> Self {
96fdf90aacd6atagen2026-09-08 08:24 UTC109
        Self {
96fdf90aacd6atagen2026-09-08 08:24 UTC110
            command_capacity: 64,
96fdf90aacd6atagen2026-09-08 08:24 UTC111
            event_capacity: 128,
96fdf90aacd6atagen2026-09-08 08:24 UTC112
            initial_history_limit: 30,
96fdf90aacd6atagen2026-09-08 08:24 UTC113
            max_media_bytes: 20 * 1024 * 1024,
96fdf90aacd6atagen2026-09-08 08:24 UTC114
            max_media_sources: 2_048,
96fdf90aacd6atagen2026-09-08 08:24 UTC115
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC116
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC117
}
96fdf90aacd6atagen2026-09-08 08:24 UTC118
96fdf90aacd6atagen2026-09-08 08:24 UTC119
#[derive(Debug, Error)]
96fdf90aacd6atagen2026-09-08 08:24 UTC120
pub enum EngineError {
96fdf90aacd6atagen2026-09-08 08:24 UTC121
    #[error("the Matrix client is not authenticated")]
96fdf90aacd6atagen2026-09-08 08:24 UTC122
    Unauthenticated,
96fdf90aacd6atagen2026-09-08 08:24 UTC123
    #[error("command, event, and media source capacities must be nonzero")]
96fdf90aacd6atagen2026-09-08 08:24 UTC124
    ZeroCapacity,
96fdf90aacd6atagen2026-09-08 08:24 UTC125
    #[error("could not build the sync service")]
96fdf90aacd6atagen2026-09-08 08:24 UTC126
    SyncService(#[source] matrix_sdk_ui::sync_service::Error),
96fdf90aacd6atagen2026-09-08 08:24 UTC127
    #[error("the event receiver has closed")]
96fdf90aacd6atagen2026-09-08 08:24 UTC128
    EventReceiverClosed,
96fdf90aacd6atagen2026-09-08 08:24 UTC129
    #[error("engine task {task} panicked")]
96fdf90aacd6atagen2026-09-08 08:24 UTC130
    TaskPanicked { task: String },
96fdf90aacd6atagen2026-09-08 08:24 UTC131
}
96fdf90aacd6atagen2026-09-08 08:24 UTC132
96fdf90aacd6atagen2026-09-08 08:24 UTC133
impl EngineError {
96fdf90aacd6atagen2026-09-08 08:24 UTC134
    fn failure(&self) -> EngineFailure {
96fdf90aacd6atagen2026-09-08 08:24 UTC135
        EngineFailure {
96fdf90aacd6atagen2026-09-08 08:24 UTC136
            kind: match self {
96fdf90aacd6atagen2026-09-08 08:24 UTC137
                Self::EventReceiverClosed => EngineErrorKind::EventReceiverClosed,
96fdf90aacd6atagen2026-09-08 08:24 UTC138
                Self::Unauthenticated
96fdf90aacd6atagen2026-09-08 08:24 UTC139
                | Self::ZeroCapacity
96fdf90aacd6atagen2026-09-08 08:24 UTC140
                | Self::SyncService(_)
96fdf90aacd6atagen2026-09-08 08:24 UTC141
                | Self::TaskPanicked { .. } => EngineErrorKind::Other,
96fdf90aacd6atagen2026-09-08 08:24 UTC142
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC143
            message: self.to_string(),
96fdf90aacd6atagen2026-09-08 08:24 UTC144
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC145
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC146
}
96fdf90aacd6atagen2026-09-08 08:24 UTC147
96fdf90aacd6atagen2026-09-08 08:24 UTC148
type UploadWatchers = HashMap<
96fdf90aacd6atagen2026-09-08 08:24 UTC149
    OwnedRoomId,
96fdf90aacd6atagen2026-09-08 08:24 UTC150
    (
96fdf90aacd6atagen2026-09-08 08:24 UTC151
        AbortHandle,
96fdf90aacd6atagen2026-09-08 08:24 UTC152
        watch::Receiver<Option<Result<(), CommandErrorKind>>>,
96fdf90aacd6atagen2026-09-08 08:24 UTC153
    ),
96fdf90aacd6atagen2026-09-08 08:24 UTC154
>;
fa9114fcf75fatagen2026-09-24 12:51 UTC155
const SYNC_RESTART_MIN: Duration = Duration::from_secs(1);
fa9114fcf75fatagen2026-09-24 12:51 UTC156
const SYNC_RESTART_MAX: Duration = Duration::from_secs(60);
fa9114fcf75fatagen2026-09-24 12:51 UTC157
const SEND_QUEUE_RETRY: Duration = Duration::from_secs(5);
fa9114fcf75fatagen2026-09-24 12:51 UTC158
96fdf90aacd6atagen2026-09-08 08:24 UTC159
type MediaFlights = Arc<Mutex<HashMap<(MediaId, MediaSize), Vec<CommandId>>>>;
96fdf90aacd6atagen2026-09-08 08:24 UTC160
96fdf90aacd6atagen2026-09-08 08:24 UTC161
pub struct Engine {
96fdf90aacd6atagen2026-09-08 08:24 UTC162
    client: Client,
96fdf90aacd6atagen2026-09-08 08:24 UTC163
    sync: Arc<SyncService>,
96fdf90aacd6atagen2026-09-08 08:24 UTC164
    port: DriverPort,
96fdf90aacd6atagen2026-09-08 08:24 UTC165
    rooms: HashMap<OwnedRoomId, RoomActorState>,
96fdf90aacd6atagen2026-09-08 08:24 UTC166
    next_room_generation: u64,
96fdf90aacd6atagen2026-09-08 08:24 UTC167
    room_actor_events: mpsc::UnboundedReceiver<RoomActorEvent>,
96fdf90aacd6atagen2026-09-08 08:24 UTC168
    room_actor_event_sender: mpsc::UnboundedSender<RoomActorEvent>,
96fdf90aacd6atagen2026-09-08 08:24 UTC169
    upload_watchers: UploadWatchers,
96fdf90aacd6atagen2026-09-08 08:24 UTC170
    tasks: TaskRegistry,
96fdf90aacd6atagen2026-09-08 08:24 UTC171
    session_changes: Option<tokio::sync::broadcast::Receiver<SessionChange>>,
96fdf90aacd6atagen2026-09-08 08:24 UTC172
    initial_history_limit: u16,
96fdf90aacd6atagen2026-09-08 08:24 UTC173
    room_command_capacity: usize,
96fdf90aacd6atagen2026-09-08 08:24 UTC174
    max_media_bytes: usize,
96fdf90aacd6atagen2026-09-08 08:24 UTC175
    media: Arc<Mutex<MediaRegistry>>,
96fdf90aacd6atagen2026-09-08 08:24 UTC176
    notification_commands: mpsc::Sender<CommandJob<NotificationCommand>>,
96fdf90aacd6atagen2026-09-08 08:24 UTC177
    notification_command_receiver: Option<mpsc::Receiver<CommandJob<NotificationCommand>>>,
96fdf90aacd6atagen2026-09-08 08:24 UTC178
    encryption_commands: mpsc::Sender<EncryptionWork>,
96fdf90aacd6atagen2026-09-08 08:24 UTC179
    encryption_command_receiver: Option<mpsc::Receiver<EncryptionWork>>,
96fdf90aacd6atagen2026-09-08 08:24 UTC180
    verification: Arc<Mutex<VerificationController>>,
96fdf90aacd6atagen2026-09-08 08:24 UTC181
    room_commands: mpsc::Sender<RoomJob>,
96fdf90aacd6atagen2026-09-08 08:24 UTC182
    room_command_receiver: Option<mpsc::Receiver<RoomJob>>,
96fdf90aacd6atagen2026-09-08 08:24 UTC183
    global_account_gates: HashMap<String, oneshot::Receiver<()>>,
96fdf90aacd6atagen2026-09-08 08:24 UTC184
    room_account_gates: HashMap<(OwnedRoomId, String), oneshot::Receiver<()>>,
96fdf90aacd6atagen2026-09-08 08:24 UTC185
    media_flights: MediaFlights,
fa9114fcf75fatagen2026-09-24 12:51 UTC186
    stop: Arc<tokio::sync::Notify>,
96fdf90aacd6atagen2026-09-08 08:24 UTC187
}
96fdf90aacd6atagen2026-09-08 08:24 UTC188
96fdf90aacd6atagen2026-09-08 08:24 UTC189
struct CommandJob<T> {
96fdf90aacd6atagen2026-09-08 08:24 UTC190
    id: CommandId,
96fdf90aacd6atagen2026-09-08 08:24 UTC191
    command: T,
96fdf90aacd6atagen2026-09-08 08:24 UTC192
}
96fdf90aacd6atagen2026-09-08 08:24 UTC193
96fdf90aacd6atagen2026-09-08 08:24 UTC194
enum EncryptionWork {
96fdf90aacd6atagen2026-09-08 08:24 UTC195
    Command(CommandJob<EncryptionCommand>),
96fdf90aacd6atagen2026-09-08 08:24 UTC196
}
96fdf90aacd6atagen2026-09-08 08:24 UTC197
96fdf90aacd6atagen2026-09-08 08:24 UTC198
struct RoomJob {
96fdf90aacd6atagen2026-09-08 08:24 UTC199
    id: CommandId,
96fdf90aacd6atagen2026-09-08 08:24 UTC200
    command: RoomCommand,
96fdf90aacd6atagen2026-09-08 08:24 UTC201
    reply: Option<oneshot::Sender<Result<OwnedRoomId, CommandResultError>>>,
96fdf90aacd6atagen2026-09-08 08:24 UTC202
}
96fdf90aacd6atagen2026-09-08 08:24 UTC203
96fdf90aacd6atagen2026-09-08 08:24 UTC204
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
96fdf90aacd6atagen2026-09-08 08:24 UTC205
enum RoomMutationKey {
96fdf90aacd6atagen2026-09-08 08:24 UTC206
    Room(OwnedRoomId),
96fdf90aacd6atagen2026-09-08 08:24 UTC207
    Target(String),
96fdf90aacd6atagen2026-09-08 08:24 UTC208
    Direct(ruma::OwnedUserId),
96fdf90aacd6atagen2026-09-08 08:24 UTC209
    Independent(CommandId),
96fdf90aacd6atagen2026-09-08 08:24 UTC210
}
96fdf90aacd6atagen2026-09-08 08:24 UTC211
96fdf90aacd6atagen2026-09-08 08:24 UTC212
struct RoomJobCompletion {
96fdf90aacd6atagen2026-09-08 08:24 UTC213
    key: RoomMutationKey,
96fdf90aacd6atagen2026-09-08 08:24 UTC214
    id: CommandId,
96fdf90aacd6atagen2026-09-08 08:24 UTC215
    reply: Option<oneshot::Sender<Result<OwnedRoomId, CommandResultError>>>,
96fdf90aacd6atagen2026-09-08 08:24 UTC216
    result: Result<Option<OwnedRoomId>, CommandError>,
96fdf90aacd6atagen2026-09-08 08:24 UTC217
}
96fdf90aacd6atagen2026-09-08 08:24 UTC218
96fdf90aacd6atagen2026-09-08 08:24 UTC219
#[derive(Clone)]
96fdf90aacd6atagen2026-09-08 08:24 UTC220
enum TaskContext {
96fdf90aacd6atagen2026-09-08 08:24 UTC221
    Command {
96fdf90aacd6atagen2026-09-08 08:24 UTC222
        id: CommandId,
96fdf90aacd6atagen2026-09-08 08:24 UTC223
        kind: CommandKind,
96fdf90aacd6atagen2026-09-08 08:24 UTC224
    },
96fdf90aacd6atagen2026-09-08 08:24 UTC225
    Watcher(&'static str),
96fdf90aacd6atagen2026-09-08 08:24 UTC226
    RoomActor {
96fdf90aacd6atagen2026-09-08 08:24 UTC227
        room_id: OwnedRoomId,
96fdf90aacd6atagen2026-09-08 08:24 UTC228
        generation: u64,
96fdf90aacd6atagen2026-09-08 08:24 UTC229
    },
96fdf90aacd6atagen2026-09-08 08:24 UTC230
}
96fdf90aacd6atagen2026-09-08 08:24 UTC231
96fdf90aacd6atagen2026-09-08 08:24 UTC232
impl TaskContext {
96fdf90aacd6atagen2026-09-08 08:24 UTC233
    fn label(&self) -> String {
96fdf90aacd6atagen2026-09-08 08:24 UTC234
        match self {
96fdf90aacd6atagen2026-09-08 08:24 UTC235
            Self::Command { id, kind } => format!("{kind:?} command {}", id.get()),
96fdf90aacd6atagen2026-09-08 08:24 UTC236
            Self::Watcher(name) => (*name).to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC237
            Self::RoomActor {
96fdf90aacd6atagen2026-09-08 08:24 UTC238
                room_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC239
                generation,
96fdf90aacd6atagen2026-09-08 08:24 UTC240
            } => format!("room actor {room_id} generation {generation}"),
96fdf90aacd6atagen2026-09-08 08:24 UTC241
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC242
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC243
}
96fdf90aacd6atagen2026-09-08 08:24 UTC244
96fdf90aacd6atagen2026-09-08 08:24 UTC245
struct TaskRegistry {
96fdf90aacd6atagen2026-09-08 08:24 UTC246
    tasks: JoinSet<()>,
96fdf90aacd6atagen2026-09-08 08:24 UTC247
    contexts: HashMap<TaskId, TaskContext>,
96fdf90aacd6atagen2026-09-08 08:24 UTC248
}
96fdf90aacd6atagen2026-09-08 08:24 UTC249
96fdf90aacd6atagen2026-09-08 08:24 UTC250
impl TaskRegistry {
96fdf90aacd6atagen2026-09-08 08:24 UTC251
    fn new() -> Self {
96fdf90aacd6atagen2026-09-08 08:24 UTC252
        Self {
96fdf90aacd6atagen2026-09-08 08:24 UTC253
            tasks: JoinSet::new(),
96fdf90aacd6atagen2026-09-08 08:24 UTC254
            contexts: HashMap::new(),
96fdf90aacd6atagen2026-09-08 08:24 UTC255
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC256
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC257
96fdf90aacd6atagen2026-09-08 08:24 UTC258
    fn is_empty(&self) -> bool {
96fdf90aacd6atagen2026-09-08 08:24 UTC259
        self.tasks.is_empty()
96fdf90aacd6atagen2026-09-08 08:24 UTC260
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC261
96fdf90aacd6atagen2026-09-08 08:24 UTC262
    fn spawn(
96fdf90aacd6atagen2026-09-08 08:24 UTC263
        &mut self,
96fdf90aacd6atagen2026-09-08 08:24 UTC264
        context: TaskContext,
96fdf90aacd6atagen2026-09-08 08:24 UTC265
        future: impl Future<Output = ()> + Send + 'static,
96fdf90aacd6atagen2026-09-08 08:24 UTC266
    ) -> AbortHandle {
96fdf90aacd6atagen2026-09-08 08:24 UTC267
        let handle = self.tasks.spawn(future);
96fdf90aacd6atagen2026-09-08 08:24 UTC268
        self.contexts.insert(handle.id(), context);
96fdf90aacd6atagen2026-09-08 08:24 UTC269
        handle
96fdf90aacd6atagen2026-09-08 08:24 UTC270
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC271
96fdf90aacd6atagen2026-09-08 08:24 UTC272
    async fn join_next(&mut self) -> Option<(TaskContext, Result<(), tokio::task::JoinError>)> {
96fdf90aacd6atagen2026-09-08 08:24 UTC273
        let result = self.tasks.join_next_with_id().await?;
96fdf90aacd6atagen2026-09-08 08:24 UTC274
        let id = match &result {
96fdf90aacd6atagen2026-09-08 08:24 UTC275
            Ok((id, ())) => *id,
96fdf90aacd6atagen2026-09-08 08:24 UTC276
            Err(error) => error.id(),
96fdf90aacd6atagen2026-09-08 08:24 UTC277
        };
96fdf90aacd6atagen2026-09-08 08:24 UTC278
        let context = self
96fdf90aacd6atagen2026-09-08 08:24 UTC279
            .contexts
96fdf90aacd6atagen2026-09-08 08:24 UTC280
            .remove(&id)
96fdf90aacd6atagen2026-09-08 08:24 UTC281
            .expect("every registered task has context");
96fdf90aacd6atagen2026-09-08 08:24 UTC282
        Some((context, result.map(|(_, ())| ())))
96fdf90aacd6atagen2026-09-08 08:24 UTC283
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC284
96fdf90aacd6atagen2026-09-08 08:24 UTC285
    async fn shutdown(&mut self) {
96fdf90aacd6atagen2026-09-08 08:24 UTC286
        self.tasks.abort_all();
96fdf90aacd6atagen2026-09-08 08:24 UTC287
        while self.tasks.join_next().await.is_some() {}
96fdf90aacd6atagen2026-09-08 08:24 UTC288
        self.contexts.clear();
96fdf90aacd6atagen2026-09-08 08:24 UTC289
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC290
}
96fdf90aacd6atagen2026-09-08 08:24 UTC291
96fdf90aacd6atagen2026-09-08 08:24 UTC292
enum RoomActorState {
96fdf90aacd6atagen2026-09-08 08:24 UTC293
    Opening {
96fdf90aacd6atagen2026-09-08 08:24 UTC294
        generation: u64,
96fdf90aacd6atagen2026-09-08 08:24 UTC295
        sender: mpsc::Sender<RoomActorRequest>,
96fdf90aacd6atagen2026-09-08 08:24 UTC296
    },
96fdf90aacd6atagen2026-09-08 08:24 UTC297
    Open {
96fdf90aacd6atagen2026-09-08 08:24 UTC298
        generation: u64,
96fdf90aacd6atagen2026-09-08 08:24 UTC299
        sender: mpsc::Sender<RoomActorRequest>,
96fdf90aacd6atagen2026-09-08 08:24 UTC300
    },
96fdf90aacd6atagen2026-09-08 08:24 UTC301
    Closing {
96fdf90aacd6atagen2026-09-08 08:24 UTC302
        generation: u64,
96fdf90aacd6atagen2026-09-08 08:24 UTC303
        pending: VecDeque<DeferredRoomLifecycle>,
96fdf90aacd6atagen2026-09-08 08:24 UTC304
    },
96fdf90aacd6atagen2026-09-08 08:24 UTC305
}
96fdf90aacd6atagen2026-09-08 08:24 UTC306
96fdf90aacd6atagen2026-09-08 08:24 UTC307
enum DeferredRoomLifecycle {
96fdf90aacd6atagen2026-09-08 08:24 UTC308
    Open(CommandId),
96fdf90aacd6atagen2026-09-08 08:24 UTC309
    Close(CommandId),
96fdf90aacd6atagen2026-09-08 08:24 UTC310
}
96fdf90aacd6atagen2026-09-08 08:24 UTC311
96fdf90aacd6atagen2026-09-08 08:24 UTC312
impl RoomActorState {
96fdf90aacd6atagen2026-09-08 08:24 UTC313
    const fn generation(&self) -> u64 {
96fdf90aacd6atagen2026-09-08 08:24 UTC314
        match self {
96fdf90aacd6atagen2026-09-08 08:24 UTC315
            Self::Opening { generation, .. }
96fdf90aacd6atagen2026-09-08 08:24 UTC316
            | Self::Open { generation, .. }
96fdf90aacd6atagen2026-09-08 08:24 UTC317
            | Self::Closing { generation, .. } => *generation,
96fdf90aacd6atagen2026-09-08 08:24 UTC318
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC319
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC320
96fdf90aacd6atagen2026-09-08 08:24 UTC321
    fn sender(&self) -> Option<&mpsc::Sender<RoomActorRequest>> {
96fdf90aacd6atagen2026-09-08 08:24 UTC322
        match self {
96fdf90aacd6atagen2026-09-08 08:24 UTC323
            Self::Opening { sender, .. } | Self::Open { sender, .. } => Some(sender),
96fdf90aacd6atagen2026-09-08 08:24 UTC324
            Self::Closing { .. } => None,
96fdf90aacd6atagen2026-09-08 08:24 UTC325
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC326
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC327
}
96fdf90aacd6atagen2026-09-08 08:24 UTC328
96fdf90aacd6atagen2026-09-08 08:24 UTC329
enum RoomActorEvent {
96fdf90aacd6atagen2026-09-08 08:24 UTC330
    Ready {
96fdf90aacd6atagen2026-09-08 08:24 UTC331
        room_id: OwnedRoomId,
96fdf90aacd6atagen2026-09-08 08:24 UTC332
        generation: u64,
96fdf90aacd6atagen2026-09-08 08:24 UTC333
    },
96fdf90aacd6atagen2026-09-08 08:24 UTC334
    Exited {
96fdf90aacd6atagen2026-09-08 08:24 UTC335
        room_id: OwnedRoomId,
96fdf90aacd6atagen2026-09-08 08:24 UTC336
        generation: u64,
96fdf90aacd6atagen2026-09-08 08:24 UTC337
    },
96fdf90aacd6atagen2026-09-08 08:24 UTC338
}
96fdf90aacd6atagen2026-09-08 08:24 UTC339
96fdf90aacd6atagen2026-09-08 08:24 UTC340
enum EngineActivity {
96fdf90aacd6atagen2026-09-08 08:24 UTC341
    Request(Option<Box<DriverRequest>>),
96fdf90aacd6atagen2026-09-08 08:24 UTC342
    Task(Option<(TaskContext, Result<(), tokio::task::JoinError>)>),
96fdf90aacd6atagen2026-09-08 08:24 UTC343
    RoomActor(Option<RoomActorEvent>),
96fdf90aacd6atagen2026-09-08 08:24 UTC344
}
96fdf90aacd6atagen2026-09-08 08:24 UTC345
fa9114fcf75fatagen2026-09-24 12:51 UTC346
/// least recently seen goes first. stale order slots are skipped by stamp rather than hunted down
96fdf90aacd6atagen2026-09-08 08:24 UTC347
struct MediaRegistry {
fa9114fcf75fatagen2026-09-24 12:51 UTC348
    entries: HashMap<MediaId, (MediaRegistration, u64)>,
fa9114fcf75fatagen2026-09-24 12:51 UTC349
    order: VecDeque<(MediaId, u64)>,
fa9114fcf75fatagen2026-09-24 12:51 UTC350
    next_stamp: u64,
96fdf90aacd6atagen2026-09-08 08:24 UTC351
    limit: usize,
96fdf90aacd6atagen2026-09-08 08:24 UTC352
}
96fdf90aacd6atagen2026-09-08 08:24 UTC353
96fdf90aacd6atagen2026-09-08 08:24 UTC354
impl MediaRegistry {
96fdf90aacd6atagen2026-09-08 08:24 UTC355
    fn new(limit: usize) -> Self {
96fdf90aacd6atagen2026-09-08 08:24 UTC356
        Self {
96fdf90aacd6atagen2026-09-08 08:24 UTC357
            entries: HashMap::new(),
96fdf90aacd6atagen2026-09-08 08:24 UTC358
            order: VecDeque::new(),
fa9114fcf75fatagen2026-09-24 12:51 UTC359
            next_stamp: 0,
96fdf90aacd6atagen2026-09-08 08:24 UTC360
            limit,
96fdf90aacd6atagen2026-09-08 08:24 UTC361
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC362
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC363
fa9114fcf75fatagen2026-09-24 12:51 UTC364
    fn get(&self, id: &MediaId) -> Option<&MediaRegistration> {
fa9114fcf75fatagen2026-09-24 12:51 UTC365
        self.entries.get(id).map(|(media, _)| media)
fa9114fcf75fatagen2026-09-24 12:51 UTC366
    }
fa9114fcf75fatagen2026-09-24 12:51 UTC367
96fdf90aacd6atagen2026-09-08 08:24 UTC368
    fn insert_all(&mut self, media: Vec<MediaRegistration>) {
96fdf90aacd6atagen2026-09-08 08:24 UTC369
        for media in media {
fa9114fcf75fatagen2026-09-24 12:51 UTC370
            let stamp = self.next_stamp;
fa9114fcf75fatagen2026-09-24 12:51 UTC371
            self.next_stamp += 1;
fa9114fcf75fatagen2026-09-24 12:51 UTC372
            self.order.push_back((media.id.clone(), stamp));
fa9114fcf75fatagen2026-09-24 12:51 UTC373
            self.entries.insert(media.id.clone(), (media, stamp));
96fdf90aacd6atagen2026-09-08 08:24 UTC374
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC375
        while self.entries.len() > self.limit {
fa9114fcf75fatagen2026-09-24 12:51 UTC376
            let Some((id, stamp)) = self.order.pop_front() else {
fa9114fcf75fatagen2026-09-24 12:51 UTC377
                break;
fa9114fcf75fatagen2026-09-24 12:51 UTC378
            };
fa9114fcf75fatagen2026-09-24 12:51 UTC379
            if self
fa9114fcf75fatagen2026-09-24 12:51 UTC380
                .entries
fa9114fcf75fatagen2026-09-24 12:51 UTC381
                .get(&id)
fa9114fcf75fatagen2026-09-24 12:51 UTC382
                .is_some_and(|(_, current)| *current == stamp)
fa9114fcf75fatagen2026-09-24 12:51 UTC383
            {
96fdf90aacd6atagen2026-09-08 08:24 UTC384
                self.entries.remove(&id);
96fdf90aacd6atagen2026-09-08 08:24 UTC385
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC386
        }
fa9114fcf75fatagen2026-09-24 12:51 UTC387
        if self.order.len() > self.limit.saturating_mul(4) {
fa9114fcf75fatagen2026-09-24 12:51 UTC388
            let entries = &self.entries;
fa9114fcf75fatagen2026-09-24 12:51 UTC389
            self.order
fa9114fcf75fatagen2026-09-24 12:51 UTC390
                .retain(|(id, stamp)| entries.get(id).is_some_and(|(_, current)| current == stamp));
fa9114fcf75fatagen2026-09-24 12:51 UTC391
        }
fa9114fcf75fatagen2026-09-24 12:51 UTC392
    }
fa9114fcf75fatagen2026-09-24 12:51 UTC393
fa9114fcf75fatagen2026-09-24 12:51 UTC394
    fn release(&mut self, scope: &MediaScope) {
fa9114fcf75fatagen2026-09-24 12:51 UTC395
        self.entries.retain(|id, _| !scope.owns(id));
fa9114fcf75fatagen2026-09-24 12:51 UTC396
        let entries = &self.entries;
fa9114fcf75fatagen2026-09-24 12:51 UTC397
        self.order.retain(|(id, _)| entries.contains_key(id));
96fdf90aacd6atagen2026-09-08 08:24 UTC398
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC399
}
96fdf90aacd6atagen2026-09-08 08:24 UTC400
96fdf90aacd6atagen2026-09-08 08:24 UTC401
enum RoomActorRequest {
96fdf90aacd6atagen2026-09-08 08:24 UTC402
    Opened {
96fdf90aacd6atagen2026-09-08 08:24 UTC403
        id: CommandId,
96fdf90aacd6atagen2026-09-08 08:24 UTC404
    },
96fdf90aacd6atagen2026-09-08 08:24 UTC405
    Execute {
96fdf90aacd6atagen2026-09-08 08:24 UTC406
        id: CommandId,
96fdf90aacd6atagen2026-09-08 08:24 UTC407
        command: TimelineCommand,
96fdf90aacd6atagen2026-09-08 08:24 UTC408
    },
96fdf90aacd6atagen2026-09-08 08:24 UTC409
    Close {
96fdf90aacd6atagen2026-09-08 08:24 UTC410
        id: CommandId,
96fdf90aacd6atagen2026-09-08 08:24 UTC411
    },
96fdf90aacd6atagen2026-09-08 08:24 UTC412
}
96fdf90aacd6atagen2026-09-08 08:24 UTC413
96fdf90aacd6atagen2026-09-08 08:24 UTC414
struct RoomActor {
96fdf90aacd6atagen2026-09-08 08:24 UTC415
    room_id: OwnedRoomId,
96fdf90aacd6atagen2026-09-08 08:24 UTC416
    generation: u64,
96fdf90aacd6atagen2026-09-08 08:24 UTC417
    room: Room,
96fdf90aacd6atagen2026-09-08 08:24 UTC418
    receiver: mpsc::Receiver<RoomActorRequest>,
96fdf90aacd6atagen2026-09-08 08:24 UTC419
    events: EventSender,
96fdf90aacd6atagen2026-09-08 08:24 UTC420
    engine_events: mpsc::UnboundedSender<RoomActorEvent>,
96fdf90aacd6atagen2026-09-08 08:24 UTC421
    media: Arc<Mutex<MediaRegistry>>,
96fdf90aacd6atagen2026-09-08 08:24 UTC422
    initial_history_limit: u16,
96fdf90aacd6atagen2026-09-08 08:24 UTC423
    open_ids: Vec<CommandId>,
fa9114fcf75fatagen2026-09-24 12:51 UTC424
    background: JoinSet<(CommandId, Result<(), CommandError>)>,
fa9114fcf75fatagen2026-09-24 12:51 UTC425
    background_ids: HashMap<TaskId, CommandId>,
fa9114fcf75fatagen2026-09-24 12:51 UTC426
}
fa9114fcf75fatagen2026-09-24 12:51 UTC427
fa9114fcf75fatagen2026-09-24 12:51 UTC428
type BackgroundResult =
fa9114fcf75fatagen2026-09-24 12:51 UTC429
    Result<(TaskId, (CommandId, Result<(), CommandError>)), tokio::task::JoinError>;
fa9114fcf75fatagen2026-09-24 12:51 UTC430
type BackgroundWork = std::pin::Pin<Box<dyn Future<Output = Result<(), CommandError>> + Send>>;
fa9114fcf75fatagen2026-09-24 12:51 UTC431
fa9114fcf75fatagen2026-09-24 12:51 UTC432
/// network-bound work runs beside the actor so one slow redaction doesn't queue the room behind it
fa9114fcf75fatagen2026-09-24 12:51 UTC433
enum TimelineOutcome {
fa9114fcf75fatagen2026-09-24 12:51 UTC434
    Done(Result<(), CommandError>),
fa9114fcf75fatagen2026-09-24 12:51 UTC435
    Background(BackgroundWork),
96fdf90aacd6atagen2026-09-08 08:24 UTC436
}
96fdf90aacd6atagen2026-09-08 08:24 UTC437
96fdf90aacd6atagen2026-09-08 08:24 UTC438
struct TimelineSession {
96fdf90aacd6atagen2026-09-08 08:24 UTC439
    id: TimelineId,
96fdf90aacd6atagen2026-09-08 08:24 UTC440
    timeline: Arc<Timeline>,
96fdf90aacd6atagen2026-09-08 08:24 UTC441
    events: EventSender,
96fdf90aacd6atagen2026-09-08 08:24 UTC442
    watcher: JoinHandle<()>,
96fdf90aacd6atagen2026-09-08 08:24 UTC443
    media: Arc<Mutex<MediaRegistry>>,
96fdf90aacd6atagen2026-09-08 08:24 UTC444
    current_user_id: Option<ruma::OwnedUserId>,
96fdf90aacd6atagen2026-09-08 08:24 UTC445
    scope: MediaScope,
96fdf90aacd6atagen2026-09-08 08:24 UTC446
    pagination_ids: Arc<Mutex<HashSet<CommandId>>>,
96fdf90aacd6atagen2026-09-08 08:24 UTC447
    pagination_sender: mpsc::Sender<PaginationRequest>,
96fdf90aacd6atagen2026-09-08 08:24 UTC448
    pagination_worker: JoinHandle<()>,
96fdf90aacd6atagen2026-09-08 08:24 UTC449
    pagination_results: Option<mpsc::UnboundedSender<(CommandId, Result<(), CommandError>)>>,
96fdf90aacd6atagen2026-09-08 08:24 UTC450
    pagination_result_task: JoinHandle<()>,
96fdf90aacd6atagen2026-09-08 08:24 UTC451
}
96fdf90aacd6atagen2026-09-08 08:24 UTC452
96fdf90aacd6atagen2026-09-08 08:24 UTC453
struct PaginationRequest {
96fdf90aacd6atagen2026-09-08 08:24 UTC454
    timeline: Arc<Timeline>,
96fdf90aacd6atagen2026-09-08 08:24 UTC455
    events: EventSender,
96fdf90aacd6atagen2026-09-08 08:24 UTC456
    timeline_id: TimelineId,
96fdf90aacd6atagen2026-09-08 08:24 UTC457
    limit: u16,
96fdf90aacd6atagen2026-09-08 08:24 UTC458
    command_id: Option<CommandId>,
96fdf90aacd6atagen2026-09-08 08:24 UTC459
    results: mpsc::UnboundedSender<(CommandId, Result<(), CommandError>)>,
96fdf90aacd6atagen2026-09-08 08:24 UTC460
}
96fdf90aacd6atagen2026-09-08 08:24 UTC461
96fdf90aacd6atagen2026-09-08 08:24 UTC462
impl TimelineSession {
96fdf90aacd6atagen2026-09-08 08:24 UTC463
    async fn start(
96fdf90aacd6atagen2026-09-08 08:24 UTC464
        id: TimelineId,
96fdf90aacd6atagen2026-09-08 08:24 UTC465
        timeline: Timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC466
        events: EventSender,
96fdf90aacd6atagen2026-09-08 08:24 UTC467
        media: Arc<Mutex<MediaRegistry>>,
96fdf90aacd6atagen2026-09-08 08:24 UTC468
        current_user_id: Option<ruma::OwnedUserId>,
96fdf90aacd6atagen2026-09-08 08:24 UTC469
        initial_history_limit: u16,
96fdf90aacd6atagen2026-09-08 08:24 UTC470
    ) -> Result<Self, SendEventError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC471
        let timeline = Arc::new(timeline);
96fdf90aacd6atagen2026-09-08 08:24 UTC472
        let scope = MediaScope::new();
96fdf90aacd6atagen2026-09-08 08:24 UTC473
        let watcher = start_timeline_watcher(
96fdf90aacd6atagen2026-09-08 08:24 UTC474
            id.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC475
            timeline.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC476
            events.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC477
            media.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC478
            current_user_id.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC479
            scope.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC480
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC481
        .await?;
96fdf90aacd6atagen2026-09-08 08:24 UTC482
96fdf90aacd6atagen2026-09-08 08:24 UTC483
        let (pagination_sender, mut pagination_receiver) = mpsc::channel::<PaginationRequest>(32);
96fdf90aacd6atagen2026-09-08 08:24 UTC484
        let pagination_worker = tokio::spawn(async move {
96fdf90aacd6atagen2026-09-08 08:24 UTC485
            while let Some(request) = pagination_receiver.recv().await {
96fdf90aacd6atagen2026-09-08 08:24 UTC486
                let command_id = request.command_id;
96fdf90aacd6atagen2026-09-08 08:24 UTC487
                let results = request.results.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC488
                if AssertUnwindSafe(run_pagination(request))
96fdf90aacd6atagen2026-09-08 08:24 UTC489
                    .catch_unwind()
96fdf90aacd6atagen2026-09-08 08:24 UTC490
                    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC491
                    .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC492
                {
96fdf90aacd6atagen2026-09-08 08:24 UTC493
                    tracing::warn!(?command_id, "pagination task panicked");
96fdf90aacd6atagen2026-09-08 08:24 UTC494
                    if let Some(id) = command_id {
96fdf90aacd6atagen2026-09-08 08:24 UTC495
                        let _ = results.send((
96fdf90aacd6atagen2026-09-08 08:24 UTC496
                            id,
96fdf90aacd6atagen2026-09-08 08:24 UTC497
                            Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC498
                                CommandErrorKind::Timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC499
                                "pagination failed",
96fdf90aacd6atagen2026-09-08 08:24 UTC500
                            )),
96fdf90aacd6atagen2026-09-08 08:24 UTC501
                        ));
96fdf90aacd6atagen2026-09-08 08:24 UTC502
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC503
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC504
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC505
        });
96fdf90aacd6atagen2026-09-08 08:24 UTC506
        let pagination_ids = Arc::new(Mutex::new(HashSet::new()));
96fdf90aacd6atagen2026-09-08 08:24 UTC507
        let (pagination_results, mut result_receiver) = mpsc::unbounded_channel();
96fdf90aacd6atagen2026-09-08 08:24 UTC508
        let result_events = events.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC509
        let result_ids = pagination_ids.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC510
        let pagination_result_task = tokio::spawn(async move {
96fdf90aacd6atagen2026-09-08 08:24 UTC511
            while let Some((id, result)) = result_receiver.recv().await {
96fdf90aacd6atagen2026-09-08 08:24 UTC512
                if result_ids.lock().await.remove(&id) {
96fdf90aacd6atagen2026-09-08 08:24 UTC513
                    let _ = emit_command_result(&result_events, id, CommandKind::Timeline, result)
96fdf90aacd6atagen2026-09-08 08:24 UTC514
                        .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC515
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC516
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC517
        });
96fdf90aacd6atagen2026-09-08 08:24 UTC518
96fdf90aacd6atagen2026-09-08 08:24 UTC519
        let mut session = Self {
96fdf90aacd6atagen2026-09-08 08:24 UTC520
            id,
96fdf90aacd6atagen2026-09-08 08:24 UTC521
            timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC522
            events,
96fdf90aacd6atagen2026-09-08 08:24 UTC523
            watcher,
96fdf90aacd6atagen2026-09-08 08:24 UTC524
            media,
96fdf90aacd6atagen2026-09-08 08:24 UTC525
            current_user_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC526
            scope,
96fdf90aacd6atagen2026-09-08 08:24 UTC527
            pagination_sender,
96fdf90aacd6atagen2026-09-08 08:24 UTC528
            pagination_worker,
96fdf90aacd6atagen2026-09-08 08:24 UTC529
            pagination_ids,
96fdf90aacd6atagen2026-09-08 08:24 UTC530
            pagination_results: Some(pagination_results),
96fdf90aacd6atagen2026-09-08 08:24 UTC531
            pagination_result_task,
96fdf90aacd6atagen2026-09-08 08:24 UTC532
        };
96fdf90aacd6atagen2026-09-08 08:24 UTC533
        if initial_history_limit > 0 {
96fdf90aacd6atagen2026-09-08 08:24 UTC534
            session.paginate(initial_history_limit, None).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC535
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC536
        Ok(session)
96fdf90aacd6atagen2026-09-08 08:24 UTC537
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC538
96fdf90aacd6atagen2026-09-08 08:24 UTC539
    async fn resync(&mut self) -> Result<(), SendEventError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC540
        self.watcher.abort();
96fdf90aacd6atagen2026-09-08 08:24 UTC541
        let _ = (&mut self.watcher).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC542
        self.watcher = start_timeline_watcher(
96fdf90aacd6atagen2026-09-08 08:24 UTC543
            self.id.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC544
            self.timeline.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC545
            self.events.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC546
            self.media.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC547
            self.current_user_id.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC548
            self.scope.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC549
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC550
        .await?;
96fdf90aacd6atagen2026-09-08 08:24 UTC551
        Ok(())
96fdf90aacd6atagen2026-09-08 08:24 UTC552
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC553
96fdf90aacd6atagen2026-09-08 08:24 UTC554
    async fn paginate(&mut self, limit: u16, command_id: Option<CommandId>) {
96fdf90aacd6atagen2026-09-08 08:24 UTC555
        if let Some(id) = command_id {
96fdf90aacd6atagen2026-09-08 08:24 UTC556
            self.pagination_ids.lock().await.insert(id);
96fdf90aacd6atagen2026-09-08 08:24 UTC557
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC558
        let request = PaginationRequest {
96fdf90aacd6atagen2026-09-08 08:24 UTC559
            timeline: self.timeline.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC560
            events: self.events.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC561
            timeline_id: self.id.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC562
            limit,
96fdf90aacd6atagen2026-09-08 08:24 UTC563
            command_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC564
            results: self
96fdf90aacd6atagen2026-09-08 08:24 UTC565
                .pagination_results
96fdf90aacd6atagen2026-09-08 08:24 UTC566
                .as_ref()
96fdf90aacd6atagen2026-09-08 08:24 UTC567
                .expect("pagination session is open")
96fdf90aacd6atagen2026-09-08 08:24 UTC568
                .clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC569
        };
96fdf90aacd6atagen2026-09-08 08:24 UTC570
        if let Err(error) = self.pagination_sender.try_send(request) {
96fdf90aacd6atagen2026-09-08 08:24 UTC571
            let kind = match error {
96fdf90aacd6atagen2026-09-08 08:24 UTC572
                mpsc::error::TrySendError::Full(_) => CommandErrorKind::QueueFull,
96fdf90aacd6atagen2026-09-08 08:24 UTC573
                mpsc::error::TrySendError::Closed(_) => CommandErrorKind::TimelineNotOpen,
96fdf90aacd6atagen2026-09-08 08:24 UTC574
            };
96fdf90aacd6atagen2026-09-08 08:24 UTC575
            if let Some(id) = command_id {
96fdf90aacd6atagen2026-09-08 08:24 UTC576
                self.pagination_ids.lock().await.remove(&id);
96fdf90aacd6atagen2026-09-08 08:24 UTC577
                let _ = emit_command_result(
96fdf90aacd6atagen2026-09-08 08:24 UTC578
                    &self.events,
96fdf90aacd6atagen2026-09-08 08:24 UTC579
                    id,
96fdf90aacd6atagen2026-09-08 08:24 UTC580
                    CommandKind::Timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC581
                    Err(CommandError::new(kind, "pagination queue is unavailable")),
96fdf90aacd6atagen2026-09-08 08:24 UTC582
                )
96fdf90aacd6atagen2026-09-08 08:24 UTC583
                .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC584
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC585
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC586
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC587
96fdf90aacd6atagen2026-09-08 08:24 UTC588
    async fn close(mut self, kind: CommandErrorKind, message: &'static str) {
fa9114fcf75fatagen2026-09-24 12:51 UTC589
        self.media.lock().await.release(&self.scope);
96fdf90aacd6atagen2026-09-08 08:24 UTC590
        self.pagination_worker.abort();
96fdf90aacd6atagen2026-09-08 08:24 UTC591
        let _ = (&mut self.pagination_worker).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC592
        drop(self.pagination_results.take());
96fdf90aacd6atagen2026-09-08 08:24 UTC593
        let _ = (&mut self.pagination_result_task).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC594
        let pending = self.pagination_ids.lock().await.drain().collect::<Vec<_>>();
96fdf90aacd6atagen2026-09-08 08:24 UTC595
        for id in pending {
96fdf90aacd6atagen2026-09-08 08:24 UTC596
            let _ = emit_command_result(
96fdf90aacd6atagen2026-09-08 08:24 UTC597
                &self.events,
96fdf90aacd6atagen2026-09-08 08:24 UTC598
                id,
96fdf90aacd6atagen2026-09-08 08:24 UTC599
                CommandKind::Timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC600
                Err(CommandError::new(kind, message)),
96fdf90aacd6atagen2026-09-08 08:24 UTC601
            )
96fdf90aacd6atagen2026-09-08 08:24 UTC602
            .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC603
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC604
        self.watcher.abort();
96fdf90aacd6atagen2026-09-08 08:24 UTC605
        let _ = (&mut self.watcher).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC606
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC607
}
96fdf90aacd6atagen2026-09-08 08:24 UTC608
96fdf90aacd6atagen2026-09-08 08:24 UTC609
async fn start_timeline_watcher(
96fdf90aacd6atagen2026-09-08 08:24 UTC610
    id: TimelineId,
96fdf90aacd6atagen2026-09-08 08:24 UTC611
    timeline: Arc<Timeline>,
96fdf90aacd6atagen2026-09-08 08:24 UTC612
    events: EventSender,
96fdf90aacd6atagen2026-09-08 08:24 UTC613
    media: Arc<Mutex<MediaRegistry>>,
96fdf90aacd6atagen2026-09-08 08:24 UTC614
    current_user_id: Option<ruma::OwnedUserId>,
96fdf90aacd6atagen2026-09-08 08:24 UTC615
    scope: MediaScope,
96fdf90aacd6atagen2026-09-08 08:24 UTC616
) -> Result<JoinHandle<()>, SendEventError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC617
    let (items, mut stream) = timeline.subscribe().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC618
    let normalised = normalise_timeline(items.iter(), current_user_id.as_deref(), &scope);
96fdf90aacd6atagen2026-09-08 08:24 UTC619
    media.lock().await.insert_all(normalised.media);
96fdf90aacd6atagen2026-09-08 08:24 UTC620
    events
96fdf90aacd6atagen2026-09-08 08:24 UTC621
        .send(Event::TimelineReset {
96fdf90aacd6atagen2026-09-08 08:24 UTC622
            timeline_id: id.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC623
            items: normalised.items,
96fdf90aacd6atagen2026-09-08 08:24 UTC624
        })
96fdf90aacd6atagen2026-09-08 08:24 UTC625
        .await?;
96fdf90aacd6atagen2026-09-08 08:24 UTC626
    Ok(tokio::spawn(async move {
96fdf90aacd6atagen2026-09-08 08:24 UTC627
        while let Some(diffs) = stream.next().await {
96fdf90aacd6atagen2026-09-08 08:24 UTC628
            let normalised = normalise_timeline_diffs(diffs, current_user_id.as_deref(), &scope);
96fdf90aacd6atagen2026-09-08 08:24 UTC629
            media.lock().await.insert_all(normalised.media);
96fdf90aacd6atagen2026-09-08 08:24 UTC630
            if events
96fdf90aacd6atagen2026-09-08 08:24 UTC631
                .send(Event::TimelineChanged {
96fdf90aacd6atagen2026-09-08 08:24 UTC632
                    timeline_id: id.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC633
                    diffs: normalised.diffs,
96fdf90aacd6atagen2026-09-08 08:24 UTC634
                })
96fdf90aacd6atagen2026-09-08 08:24 UTC635
                .await
96fdf90aacd6atagen2026-09-08 08:24 UTC636
                .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC637
            {
96fdf90aacd6atagen2026-09-08 08:24 UTC638
                tracing::debug!(
96fdf90aacd6atagen2026-09-08 08:24 UTC639
                    ?id,
96fdf90aacd6atagen2026-09-08 08:24 UTC640
                    "timeline watcher stopped because the event receiver closed"
96fdf90aacd6atagen2026-09-08 08:24 UTC641
                );
96fdf90aacd6atagen2026-09-08 08:24 UTC642
                return;
96fdf90aacd6atagen2026-09-08 08:24 UTC643
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC644
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC645
        tracing::debug!(?id, "timeline watcher stopped");
96fdf90aacd6atagen2026-09-08 08:24 UTC646
    }))
96fdf90aacd6atagen2026-09-08 08:24 UTC647
}
96fdf90aacd6atagen2026-09-08 08:24 UTC648
96fdf90aacd6atagen2026-09-08 08:24 UTC649
impl Drop for TimelineSession {
96fdf90aacd6atagen2026-09-08 08:24 UTC650
    fn drop(&mut self) {
96fdf90aacd6atagen2026-09-08 08:24 UTC651
        self.watcher.abort();
96fdf90aacd6atagen2026-09-08 08:24 UTC652
        self.pagination_worker.abort();
96fdf90aacd6atagen2026-09-08 08:24 UTC653
        self.pagination_result_task.abort();
96fdf90aacd6atagen2026-09-08 08:24 UTC654
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC655
}
96fdf90aacd6atagen2026-09-08 08:24 UTC656
96fdf90aacd6atagen2026-09-08 08:24 UTC657
impl Engine {
96fdf90aacd6atagen2026-09-08 08:24 UTC658
    pub async fn from_client(
96fdf90aacd6atagen2026-09-08 08:24 UTC659
        client: Client,
96fdf90aacd6atagen2026-09-08 08:24 UTC660
        config: EngineConfig,
96fdf90aacd6atagen2026-09-08 08:24 UTC661
    ) -> Result<(Self, ClientHandle, EventStream), EngineError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC662
        if client.user_id().is_none() {
96fdf90aacd6atagen2026-09-08 08:24 UTC663
            return Err(EngineError::Unauthenticated);
96fdf90aacd6atagen2026-09-08 08:24 UTC664
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC665
        if config.command_capacity == 0
96fdf90aacd6atagen2026-09-08 08:24 UTC666
            || config.event_capacity == 0
96fdf90aacd6atagen2026-09-08 08:24 UTC667
            || config.max_media_sources == 0
96fdf90aacd6atagen2026-09-08 08:24 UTC668
        {
96fdf90aacd6atagen2026-09-08 08:24 UTC669
            return Err(EngineError::ZeroCapacity);
96fdf90aacd6atagen2026-09-08 08:24 UTC670
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC671
        let session_changes = client.subscribe_to_session_changes();
96fdf90aacd6atagen2026-09-08 08:24 UTC672
        let sync = Arc::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC673
            SyncService::builder(client.clone())
96fdf90aacd6atagen2026-09-08 08:24 UTC674
                .with_offline_mode()
96fdf90aacd6atagen2026-09-08 08:24 UTC675
                .build()
96fdf90aacd6atagen2026-09-08 08:24 UTC676
                .await
96fdf90aacd6atagen2026-09-08 08:24 UTC677
                .map_err(EngineError::SyncService)?,
96fdf90aacd6atagen2026-09-08 08:24 UTC678
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC679
        let (handle, port, events) = transport_pair(
96fdf90aacd6atagen2026-09-08 08:24 UTC680
            client.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC681
            sync.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC682
            config.command_capacity,
96fdf90aacd6atagen2026-09-08 08:24 UTC683
            config.event_capacity,
96fdf90aacd6atagen2026-09-08 08:24 UTC684
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC685
        client.send_queue().enable_upload_progress(true);
96fdf90aacd6atagen2026-09-08 08:24 UTC686
        let (room_actor_event_sender, room_actor_events) = mpsc::unbounded_channel();
96fdf90aacd6atagen2026-09-08 08:24 UTC687
        let (notification_commands, notification_command_receiver) =
96fdf90aacd6atagen2026-09-08 08:24 UTC688
            mpsc::channel(config.command_capacity);
96fdf90aacd6atagen2026-09-08 08:24 UTC689
        let (encryption_commands, encryption_command_receiver) =
96fdf90aacd6atagen2026-09-08 08:24 UTC690
            mpsc::channel(config.command_capacity);
96fdf90aacd6atagen2026-09-08 08:24 UTC691
        let (room_commands, room_command_receiver) = mpsc::channel(config.command_capacity);
96fdf90aacd6atagen2026-09-08 08:24 UTC692
        Ok((
96fdf90aacd6atagen2026-09-08 08:24 UTC693
            Self {
96fdf90aacd6atagen2026-09-08 08:24 UTC694
                client,
96fdf90aacd6atagen2026-09-08 08:24 UTC695
                sync,
96fdf90aacd6atagen2026-09-08 08:24 UTC696
                port,
96fdf90aacd6atagen2026-09-08 08:24 UTC697
                rooms: HashMap::new(),
96fdf90aacd6atagen2026-09-08 08:24 UTC698
                next_room_generation: 0,
96fdf90aacd6atagen2026-09-08 08:24 UTC699
                room_actor_events,
96fdf90aacd6atagen2026-09-08 08:24 UTC700
                room_actor_event_sender,
96fdf90aacd6atagen2026-09-08 08:24 UTC701
                upload_watchers: HashMap::new(),
96fdf90aacd6atagen2026-09-08 08:24 UTC702
                tasks: TaskRegistry::new(),
96fdf90aacd6atagen2026-09-08 08:24 UTC703
                session_changes: Some(session_changes),
96fdf90aacd6atagen2026-09-08 08:24 UTC704
                initial_history_limit: config.initial_history_limit,
96fdf90aacd6atagen2026-09-08 08:24 UTC705
                room_command_capacity: config.command_capacity,
96fdf90aacd6atagen2026-09-08 08:24 UTC706
                max_media_bytes: config.max_media_bytes,
96fdf90aacd6atagen2026-09-08 08:24 UTC707
                media: Arc::new(Mutex::new(MediaRegistry::new(config.max_media_sources))),
96fdf90aacd6atagen2026-09-08 08:24 UTC708
                notification_commands,
96fdf90aacd6atagen2026-09-08 08:24 UTC709
                notification_command_receiver: Some(notification_command_receiver),
96fdf90aacd6atagen2026-09-08 08:24 UTC710
                encryption_commands,
96fdf90aacd6atagen2026-09-08 08:24 UTC711
                encryption_command_receiver: Some(encryption_command_receiver),
96fdf90aacd6atagen2026-09-08 08:24 UTC712
                verification: Arc::new(Mutex::new(VerificationController::default())),
96fdf90aacd6atagen2026-09-08 08:24 UTC713
                room_commands,
96fdf90aacd6atagen2026-09-08 08:24 UTC714
                room_command_receiver: Some(room_command_receiver),
96fdf90aacd6atagen2026-09-08 08:24 UTC715
                global_account_gates: HashMap::new(),
96fdf90aacd6atagen2026-09-08 08:24 UTC716
                room_account_gates: HashMap::new(),
96fdf90aacd6atagen2026-09-08 08:24 UTC717
                media_flights: Arc::new(Mutex::new(HashMap::new())),
fa9114fcf75fatagen2026-09-24 12:51 UTC718
                stop: Arc::new(tokio::sync::Notify::new()),
96fdf90aacd6atagen2026-09-08 08:24 UTC719
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC720
            handle,
96fdf90aacd6atagen2026-09-08 08:24 UTC721
            events,
96fdf90aacd6atagen2026-09-08 08:24 UTC722
        ))
96fdf90aacd6atagen2026-09-08 08:24 UTC723
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC724
96fdf90aacd6atagen2026-09-08 08:24 UTC725
    pub async fn run(mut self) -> Result<(), EngineError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC726
        let sender = self.port.event_sender();
96fdf90aacd6atagen2026-09-08 08:24 UTC727
        let user_id = self
96fdf90aacd6atagen2026-09-08 08:24 UTC728
            .client
96fdf90aacd6atagen2026-09-08 08:24 UTC729
            .user_id()
96fdf90aacd6atagen2026-09-08 08:24 UTC730
            .expect("authentication was checked during construction")
96fdf90aacd6atagen2026-09-08 08:24 UTC731
            .to_owned();
96fdf90aacd6atagen2026-09-08 08:24 UTC732
        if sender.send(Event::Authenticated { user_id }).await.is_err() {
96fdf90aacd6atagen2026-09-08 08:24 UTC733
            self.shutdown().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC734
            return Err(EngineError::EventReceiverClosed);
96fdf90aacd6atagen2026-09-08 08:24 UTC735
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC736
        self.port
96fdf90aacd6atagen2026-09-08 08:24 UTC737
            .connection_sender()
96fdf90aacd6atagen2026-09-08 08:24 UTC738
            .send_replace(ConnectionState::Connecting);
96fdf90aacd6atagen2026-09-08 08:24 UTC739
        if sender
96fdf90aacd6atagen2026-09-08 08:24 UTC740
            .send(Event::ConnectionChanged(ConnectionState::Connecting))
96fdf90aacd6atagen2026-09-08 08:24 UTC741
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC742
            .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC743
        {
96fdf90aacd6atagen2026-09-08 08:24 UTC744
            self.shutdown().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC745
            return Err(EngineError::EventReceiverClosed);
96fdf90aacd6atagen2026-09-08 08:24 UTC746
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC747
        self.start_watchers();
96fdf90aacd6atagen2026-09-08 08:24 UTC748
        self.start_domain_workers();
96fdf90aacd6atagen2026-09-08 08:24 UTC749
        self.sync.start().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC750
        let mut terminal_error = None;
96fdf90aacd6atagen2026-09-08 08:24 UTC751
96fdf90aacd6atagen2026-09-08 08:24 UTC752
        loop {
96fdf90aacd6atagen2026-09-08 08:24 UTC753
            let activity = tokio::select! {
96fdf90aacd6atagen2026-09-08 08:24 UTC754
                biased;
96fdf90aacd6atagen2026-09-08 08:24 UTC755
                _ = sender.closed() => {
96fdf90aacd6atagen2026-09-08 08:24 UTC756
                    terminal_error = Some(EngineError::EventReceiverClosed);
96fdf90aacd6atagen2026-09-08 08:24 UTC757
                    break;
96fdf90aacd6atagen2026-09-08 08:24 UTC758
                }
fa9114fcf75fatagen2026-09-24 12:51 UTC759
                () = self.stop.notified() => break,
96fdf90aacd6atagen2026-09-08 08:24 UTC760
                request = self.port.recv() => EngineActivity::Request(request.map(Box::new)),
96fdf90aacd6atagen2026-09-08 08:24 UTC761
                completed = self.tasks.join_next(), if !self.tasks.is_empty() => {
96fdf90aacd6atagen2026-09-08 08:24 UTC762
                    EngineActivity::Task(completed)
96fdf90aacd6atagen2026-09-08 08:24 UTC763
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC764
                event = self.room_actor_events.recv() => EngineActivity::RoomActor(event),
96fdf90aacd6atagen2026-09-08 08:24 UTC765
            };
96fdf90aacd6atagen2026-09-08 08:24 UTC766
            match activity {
96fdf90aacd6atagen2026-09-08 08:24 UTC767
                EngineActivity::Request(Some(request)) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC768
                    let Some(envelope) = self.dispatch_request(*request).await else {
96fdf90aacd6atagen2026-09-08 08:24 UTC769
                        continue;
96fdf90aacd6atagen2026-09-08 08:24 UTC770
                    };
96fdf90aacd6atagen2026-09-08 08:24 UTC771
                    let id = envelope.id;
96fdf90aacd6atagen2026-09-08 08:24 UTC772
                    let command = envelope.command;
96fdf90aacd6atagen2026-09-08 08:24 UTC773
                    if matches!(command, Command::Lifecycle(LifecycleCommand::Shutdown)) {
96fdf90aacd6atagen2026-09-08 08:24 UTC774
                        let _ =
96fdf90aacd6atagen2026-09-08 08:24 UTC775
                            emit_command_result(&sender, id, CommandKind::Lifecycle, Ok(())).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC776
                        break;
96fdf90aacd6atagen2026-09-08 08:24 UTC777
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC778
                    let kind = command.kind();
96fdf90aacd6atagen2026-09-08 08:24 UTC779
                    tracing::debug!(?id, ?kind, "dispatching command");
96fdf90aacd6atagen2026-09-08 08:24 UTC780
                    let result = self.execute(id, command).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC781
                    if Self::emit_sync_result(&sender, id, kind, result)
96fdf90aacd6atagen2026-09-08 08:24 UTC782
                        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC783
                        .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC784
                    {
96fdf90aacd6atagen2026-09-08 08:24 UTC785
                        terminal_error = Some(EngineError::EventReceiverClosed);
96fdf90aacd6atagen2026-09-08 08:24 UTC786
                        break;
96fdf90aacd6atagen2026-09-08 08:24 UTC787
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC788
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC789
                EngineActivity::Request(None) => break,
96fdf90aacd6atagen2026-09-08 08:24 UTC790
                EngineActivity::Task(Some((context, result))) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC791
                    if let Err(error) = result
96fdf90aacd6atagen2026-09-08 08:24 UTC792
                        && error.is_panic()
96fdf90aacd6atagen2026-09-08 08:24 UTC793
                    {
96fdf90aacd6atagen2026-09-08 08:24 UTC794
                        match context {
96fdf90aacd6atagen2026-09-08 08:24 UTC795
                            TaskContext::Command { id, kind } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC796
                                if sender
96fdf90aacd6atagen2026-09-08 08:24 UTC797
                                    .send(Event::CommandFailed(CommandFailure {
96fdf90aacd6atagen2026-09-08 08:24 UTC798
                                        id,
96fdf90aacd6atagen2026-09-08 08:24 UTC799
                                        command: kind,
96fdf90aacd6atagen2026-09-08 08:24 UTC800
                                        kind: CommandErrorKind::Other,
96fdf90aacd6atagen2026-09-08 08:24 UTC801
                                        message: "command task panicked".to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC802
                                    }))
96fdf90aacd6atagen2026-09-08 08:24 UTC803
                                    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC804
                                    .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC805
                                {
96fdf90aacd6atagen2026-09-08 08:24 UTC806
                                    terminal_error = Some(EngineError::EventReceiverClosed);
96fdf90aacd6atagen2026-09-08 08:24 UTC807
                                    break;
96fdf90aacd6atagen2026-09-08 08:24 UTC808
                                }
96fdf90aacd6atagen2026-09-08 08:24 UTC809
                            }
96fdf90aacd6atagen2026-09-08 08:24 UTC810
                            context => {
96fdf90aacd6atagen2026-09-08 08:24 UTC811
                                terminal_error = Some(EngineError::TaskPanicked {
96fdf90aacd6atagen2026-09-08 08:24 UTC812
                                    task: context.label(),
96fdf90aacd6atagen2026-09-08 08:24 UTC813
                                });
96fdf90aacd6atagen2026-09-08 08:24 UTC814
                                break;
96fdf90aacd6atagen2026-09-08 08:24 UTC815
                            }
96fdf90aacd6atagen2026-09-08 08:24 UTC816
                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC817
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC818
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC819
                EngineActivity::Task(None) => {}
96fdf90aacd6atagen2026-09-08 08:24 UTC820
                EngineActivity::RoomActor(Some(event)) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC821
                    if let Err(error) = self.handle_room_actor_event(event).await {
96fdf90aacd6atagen2026-09-08 08:24 UTC822
                        terminal_error = Some(error);
96fdf90aacd6atagen2026-09-08 08:24 UTC823
                        break;
96fdf90aacd6atagen2026-09-08 08:24 UTC824
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC825
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC826
                EngineActivity::RoomActor(None) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC827
                    terminal_error = Some(EngineError::TaskPanicked {
96fdf90aacd6atagen2026-09-08 08:24 UTC828
                        task: "room actor event channel".to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC829
                    });
96fdf90aacd6atagen2026-09-08 08:24 UTC830
                    break;
96fdf90aacd6atagen2026-09-08 08:24 UTC831
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC832
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC833
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC834
96fdf90aacd6atagen2026-09-08 08:24 UTC835
        if let Some(error) = terminal_error.as_ref() {
96fdf90aacd6atagen2026-09-08 08:24 UTC836
            let _ = sender.send(Event::Failed(error.failure())).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC837
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC838
        self.shutdown().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC839
        let _ = sender.send(Event::Stopped).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC840
        if let Some(error) = terminal_error {
96fdf90aacd6atagen2026-09-08 08:24 UTC841
            Err(error)
96fdf90aacd6atagen2026-09-08 08:24 UTC842
        } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC843
            Ok(())
96fdf90aacd6atagen2026-09-08 08:24 UTC844
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC845
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC846
96fdf90aacd6atagen2026-09-08 08:24 UTC847
    async fn emit_sync_result(
96fdf90aacd6atagen2026-09-08 08:24 UTC848
        sender: &EventSender,
96fdf90aacd6atagen2026-09-08 08:24 UTC849
        id: CommandId,
96fdf90aacd6atagen2026-09-08 08:24 UTC850
        kind: CommandKind,
96fdf90aacd6atagen2026-09-08 08:24 UTC851
        result: Result<bool, CommandError>,
96fdf90aacd6atagen2026-09-08 08:24 UTC852
    ) -> Result<(), SendEventError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC853
        match result {
96fdf90aacd6atagen2026-09-08 08:24 UTC854
            Ok(true) => Ok(()),
96fdf90aacd6atagen2026-09-08 08:24 UTC855
            Ok(false) => emit_command_result(sender, id, kind, Ok(())).await,
96fdf90aacd6atagen2026-09-08 08:24 UTC856
            Err(error) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC857
                tracing::warn!(?id, ?kind, error = ?error, "command failed");
96fdf90aacd6atagen2026-09-08 08:24 UTC858
                emit_command_result(sender, id, kind, Err(error)).await?;
96fdf90aacd6atagen2026-09-08 08:24 UTC859
                Ok(())
96fdf90aacd6atagen2026-09-08 08:24 UTC860
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC861
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC862
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC863
96fdf90aacd6atagen2026-09-08 08:24 UTC864
    fn spawn_logout(&mut self, id: CommandId) {
96fdf90aacd6atagen2026-09-08 08:24 UTC865
        let events = self.port.event_sender();
96fdf90aacd6atagen2026-09-08 08:24 UTC866
        let client = self.client.clone();
fa9114fcf75fatagen2026-09-24 12:51 UTC867
        let stop = self.stop.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC868
        self.tasks.spawn(
96fdf90aacd6atagen2026-09-08 08:24 UTC869
            TaskContext::Command {
96fdf90aacd6atagen2026-09-08 08:24 UTC870
                id,
96fdf90aacd6atagen2026-09-08 08:24 UTC871
                kind: CommandKind::Account,
96fdf90aacd6atagen2026-09-08 08:24 UTC872
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC873
            async move {
96fdf90aacd6atagen2026-09-08 08:24 UTC874
                match client
96fdf90aacd6atagen2026-09-08 08:24 UTC875
                    .logout()
96fdf90aacd6atagen2026-09-08 08:24 UTC876
                    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC877
                    .map(|_| ())
96fdf90aacd6atagen2026-09-08 08:24 UTC878
                    .map_err(|source| CommandError::from_sdk("logout failed", source))
96fdf90aacd6atagen2026-09-08 08:24 UTC879
                {
96fdf90aacd6atagen2026-09-08 08:24 UTC880
                    Ok(()) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC881
                        let _ =
96fdf90aacd6atagen2026-09-08 08:24 UTC882
                            emit_command_result(&events, id, CommandKind::Account, Ok(())).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC883
                        let _ = events.send(Event::LoggedOut).await;
fa9114fcf75fatagen2026-09-24 12:51 UTC884
                        stop.notify_one();
96fdf90aacd6atagen2026-09-08 08:24 UTC885
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC886
                    Err(error) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC887
                        let _ = emit_command_result(&events, id, CommandKind::Account, Err(error))
96fdf90aacd6atagen2026-09-08 08:24 UTC888
                            .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC889
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC890
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC891
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC892
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC893
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC894
96fdf90aacd6atagen2026-09-08 08:24 UTC895
    async fn dispatch_request(&mut self, request: DriverRequest) -> Option<CommandEnvelope> {
96fdf90aacd6atagen2026-09-08 08:24 UTC896
        match request {
96fdf90aacd6atagen2026-09-08 08:24 UTC897
            DriverRequest::Command(command) => Some(command),
96fdf90aacd6atagen2026-09-08 08:24 UTC898
            DriverRequest::DeleteDevices {
96fdf90aacd6atagen2026-09-08 08:24 UTC899
                id,
96fdf90aacd6atagen2026-09-08 08:24 UTC900
                device_ids,
96fdf90aacd6atagen2026-09-08 08:24 UTC901
                auth,
96fdf90aacd6atagen2026-09-08 08:24 UTC902
                reply,
96fdf90aacd6atagen2026-09-08 08:24 UTC903
            } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC904
                self.spawn_delete_devices(id, device_ids, auth, Some(reply));
96fdf90aacd6atagen2026-09-08 08:24 UTC905
                None
96fdf90aacd6atagen2026-09-08 08:24 UTC906
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC907
            DriverRequest::RoomResult { id, command, reply } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC908
                if let Err(error) = self.spawn_room_command_with_reply(id, command, Some(reply)) {
96fdf90aacd6atagen2026-09-08 08:24 UTC909
                    tracing::warn!(?id, error = ?error, "room result request failed");
96fdf90aacd6atagen2026-09-08 08:24 UTC910
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC911
                None
96fdf90aacd6atagen2026-09-08 08:24 UTC912
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC913
            DriverRequest::SetGlobalAccountData {
96fdf90aacd6atagen2026-09-08 08:24 UTC914
                event_type,
96fdf90aacd6atagen2026-09-08 08:24 UTC915
                content,
96fdf90aacd6atagen2026-09-08 08:24 UTC916
                reply,
96fdf90aacd6atagen2026-09-08 08:24 UTC917
            } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC918
                self.spawn_global_account_data_write(event_type, content, reply);
96fdf90aacd6atagen2026-09-08 08:24 UTC919
                None
96fdf90aacd6atagen2026-09-08 08:24 UTC920
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC921
            DriverRequest::SetRoomAccountData {
96fdf90aacd6atagen2026-09-08 08:24 UTC922
                room_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC923
                event_type,
96fdf90aacd6atagen2026-09-08 08:24 UTC924
                content,
96fdf90aacd6atagen2026-09-08 08:24 UTC925
                reply,
96fdf90aacd6atagen2026-09-08 08:24 UTC926
            } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC927
                self.spawn_room_account_data_write(room_id, event_type, content, reply);
96fdf90aacd6atagen2026-09-08 08:24 UTC928
                None
96fdf90aacd6atagen2026-09-08 08:24 UTC929
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC930
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC931
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC932
96fdf90aacd6atagen2026-09-08 08:24 UTC933
    fn spawn_mark_read(&mut self, id: CommandId, timeline_id: TimelineId, event_id: OwnedEventId) {
96fdf90aacd6atagen2026-09-08 08:24 UTC934
        let events = self.port.event_sender();
96fdf90aacd6atagen2026-09-08 08:24 UTC935
        let client = self.client.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC936
        self.tasks.spawn(
96fdf90aacd6atagen2026-09-08 08:24 UTC937
            TaskContext::Command {
96fdf90aacd6atagen2026-09-08 08:24 UTC938
                id,
96fdf90aacd6atagen2026-09-08 08:24 UTC939
                kind: CommandKind::Room,
96fdf90aacd6atagen2026-09-08 08:24 UTC940
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC941
            async move {
96fdf90aacd6atagen2026-09-08 08:24 UTC942
                let result = mark_read(&client, &timeline_id, event_id).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC943
                let _ = emit_command_result(&events, id, CommandKind::Room, result).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC944
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC945
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC946
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC947
96fdf90aacd6atagen2026-09-08 08:24 UTC948
    fn spawn_typing(&mut self, id: CommandId, room_id: OwnedRoomId, typing: bool) {
96fdf90aacd6atagen2026-09-08 08:24 UTC949
        let events = self.port.event_sender();
96fdf90aacd6atagen2026-09-08 08:24 UTC950
        let client = self.client.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC951
        self.tasks.spawn(
96fdf90aacd6atagen2026-09-08 08:24 UTC952
            TaskContext::Command {
96fdf90aacd6atagen2026-09-08 08:24 UTC953
                id,
96fdf90aacd6atagen2026-09-08 08:24 UTC954
                kind: CommandKind::Room,
96fdf90aacd6atagen2026-09-08 08:24 UTC955
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC956
            async move {
96fdf90aacd6atagen2026-09-08 08:24 UTC957
                let result = set_typing(&client, &room_id, typing).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC958
                let _ = emit_command_result(&events, id, CommandKind::Room, result).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC959
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC960
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC961
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC962
96fdf90aacd6atagen2026-09-08 08:24 UTC963
    fn spawn_cancel_send(
96fdf90aacd6atagen2026-09-08 08:24 UTC964
        &mut self,
96fdf90aacd6atagen2026-09-08 08:24 UTC965
        id: CommandId,
96fdf90aacd6atagen2026-09-08 08:24 UTC966
        room_id: OwnedRoomId,
96fdf90aacd6atagen2026-09-08 08:24 UTC967
        transaction_id: matrix_sdk::ruma::OwnedTransactionId,
96fdf90aacd6atagen2026-09-08 08:24 UTC968
    ) {
96fdf90aacd6atagen2026-09-08 08:24 UTC969
        let events = self.port.event_sender();
96fdf90aacd6atagen2026-09-08 08:24 UTC970
        let client = self.client.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC971
        self.tasks.spawn(
96fdf90aacd6atagen2026-09-08 08:24 UTC972
            TaskContext::Command {
96fdf90aacd6atagen2026-09-08 08:24 UTC973
                id,
96fdf90aacd6atagen2026-09-08 08:24 UTC974
                kind: CommandKind::Room,
96fdf90aacd6atagen2026-09-08 08:24 UTC975
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC976
            async move {
96fdf90aacd6atagen2026-09-08 08:24 UTC977
                let result = cancel_send(&client, &room_id, &transaction_id).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC978
                let _ = emit_command_result(&events, id, CommandKind::Room, result).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC979
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC980
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC981
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC982
96fdf90aacd6atagen2026-09-08 08:24 UTC983
    fn spawn_retry_send(
96fdf90aacd6atagen2026-09-08 08:24 UTC984
        &mut self,
96fdf90aacd6atagen2026-09-08 08:24 UTC985
        id: CommandId,
96fdf90aacd6atagen2026-09-08 08:24 UTC986
        room_id: OwnedRoomId,
96fdf90aacd6atagen2026-09-08 08:24 UTC987
        transaction_id: matrix_sdk::ruma::OwnedTransactionId,
96fdf90aacd6atagen2026-09-08 08:24 UTC988
    ) {
96fdf90aacd6atagen2026-09-08 08:24 UTC989
        let events = self.port.event_sender();
96fdf90aacd6atagen2026-09-08 08:24 UTC990
        let client = self.client.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC991
        self.tasks.spawn(
96fdf90aacd6atagen2026-09-08 08:24 UTC992
            TaskContext::Command {
96fdf90aacd6atagen2026-09-08 08:24 UTC993
                id,
96fdf90aacd6atagen2026-09-08 08:24 UTC994
                kind: CommandKind::Room,
96fdf90aacd6atagen2026-09-08 08:24 UTC995
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC996
            async move {
96fdf90aacd6atagen2026-09-08 08:24 UTC997
                let result = retry_send(&client, &room_id, &transaction_id).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC998
                let _ = emit_command_result(&events, id, CommandKind::Room, result).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC999
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC1000
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC1001
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1002
96fdf90aacd6atagen2026-09-08 08:24 UTC1003
    fn spawn_delete_devices(
96fdf90aacd6atagen2026-09-08 08:24 UTC1004
        &mut self,
96fdf90aacd6atagen2026-09-08 08:24 UTC1005
        id: CommandId,
96fdf90aacd6atagen2026-09-08 08:24 UTC1006
        device_ids: Vec<OwnedDeviceId>,
96fdf90aacd6atagen2026-09-08 08:24 UTC1007
        auth: Option<InteractiveAuth>,
96fdf90aacd6atagen2026-09-08 08:24 UTC1008
        reply: Option<oneshot::Sender<Result<InteractiveAuthOutcome, CommandResultError>>>,
96fdf90aacd6atagen2026-09-08 08:24 UTC1009
    ) {
96fdf90aacd6atagen2026-09-08 08:24 UTC1010
        let events = self.port.event_sender();
96fdf90aacd6atagen2026-09-08 08:24 UTC1011
        let client = self.client.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC1012
        self.tasks.spawn(
96fdf90aacd6atagen2026-09-08 08:24 UTC1013
            TaskContext::Command {
96fdf90aacd6atagen2026-09-08 08:24 UTC1014
                id,
96fdf90aacd6atagen2026-09-08 08:24 UTC1015
                kind: CommandKind::Account,
96fdf90aacd6atagen2026-09-08 08:24 UTC1016
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC1017
            async move {
96fdf90aacd6atagen2026-09-08 08:24 UTC1018
                let auth = match auth {
96fdf90aacd6atagen2026-09-08 08:24 UTC1019
                    Some(auth) => match auth.into_sdk(
96fdf90aacd6atagen2026-09-08 08:24 UTC1020
                        client
96fdf90aacd6atagen2026-09-08 08:24 UTC1021
                            .user_id()
96fdf90aacd6atagen2026-09-08 08:24 UTC1022
                            .expect("authentication was checked during construction"),
96fdf90aacd6atagen2026-09-08 08:24 UTC1023
                    ) {
96fdf90aacd6atagen2026-09-08 08:24 UTC1024
                        Ok(auth) => Some(auth),
96fdf90aacd6atagen2026-09-08 08:24 UTC1025
                        Err(message) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1026
                            let failure = CommandFailure {
96fdf90aacd6atagen2026-09-08 08:24 UTC1027
                                id,
96fdf90aacd6atagen2026-09-08 08:24 UTC1028
                                command: CommandKind::Account,
96fdf90aacd6atagen2026-09-08 08:24 UTC1029
                                kind: CommandErrorKind::Other,
96fdf90aacd6atagen2026-09-08 08:24 UTC1030
                                message: format!(
96fdf90aacd6atagen2026-09-08 08:24 UTC1031
                                    "interactive authentication data is invalid, {message}"
96fdf90aacd6atagen2026-09-08 08:24 UTC1032
                                ),
96fdf90aacd6atagen2026-09-08 08:24 UTC1033
                            };
96fdf90aacd6atagen2026-09-08 08:24 UTC1034
                            if let Some(reply) = reply {
96fdf90aacd6atagen2026-09-08 08:24 UTC1035
                                let _ =
96fdf90aacd6atagen2026-09-08 08:24 UTC1036
                                    reply.send(Err(CommandResultError::Failed(failure.clone())));
96fdf90aacd6atagen2026-09-08 08:24 UTC1037
                            } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC1038
                                let _ = events.send(Event::CommandFailed(failure)).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC1039
                            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1040
                            return;
96fdf90aacd6atagen2026-09-08 08:24 UTC1041
                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC1042
                    },
96fdf90aacd6atagen2026-09-08 08:24 UTC1043
                    None => None,
96fdf90aacd6atagen2026-09-08 08:24 UTC1044
                };
96fdf90aacd6atagen2026-09-08 08:24 UTC1045
                let result = client.delete_devices(&device_ids, auth).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC1046
96fdf90aacd6atagen2026-09-08 08:24 UTC1047
                match result {
96fdf90aacd6atagen2026-09-08 08:24 UTC1048
                    Ok(_) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1049
                        if let Some(reply) = reply {
96fdf90aacd6atagen2026-09-08 08:24 UTC1050
                            let _ = reply.send(Ok(InteractiveAuthOutcome::Completed));
96fdf90aacd6atagen2026-09-08 08:24 UTC1051
                        } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC1052
                            let _ = emit_command_result(&events, id, CommandKind::Account, Ok(()))
96fdf90aacd6atagen2026-09-08 08:24 UTC1053
                                .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC1054
                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC1055
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1056
                    Err(source) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1057
                        if let Some(info) = source.as_uiaa_response() {
96fdf90aacd6atagen2026-09-08 08:24 UTC1058
                            let challenge = InteractiveAuthChallenge::from(info);
96fdf90aacd6atagen2026-09-08 08:24 UTC1059
                            if let Some(reply) = reply {
96fdf90aacd6atagen2026-09-08 08:24 UTC1060
                                let _ =
96fdf90aacd6atagen2026-09-08 08:24 UTC1061
                                    reply.send(Ok(InteractiveAuthOutcome::Challenge(challenge)));
96fdf90aacd6atagen2026-09-08 08:24 UTC1062
                            } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC1063
                                let _ = events
96fdf90aacd6atagen2026-09-08 08:24 UTC1064
                                    .send(Event::InteractiveAuthRequired {
96fdf90aacd6atagen2026-09-08 08:24 UTC1065
                                        id,
96fdf90aacd6atagen2026-09-08 08:24 UTC1066
                                        command: CommandKind::Account,
96fdf90aacd6atagen2026-09-08 08:24 UTC1067
                                        challenge: challenge.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC1068
                                    })
96fdf90aacd6atagen2026-09-08 08:24 UTC1069
                                    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC1070
                                let _ = events
96fdf90aacd6atagen2026-09-08 08:24 UTC1071
                                    .send(Event::CommandFailed(CommandFailure {
96fdf90aacd6atagen2026-09-08 08:24 UTC1072
                                        id,
96fdf90aacd6atagen2026-09-08 08:24 UTC1073
                                        command: CommandKind::Account,
96fdf90aacd6atagen2026-09-08 08:24 UTC1074
                                        kind: CommandErrorKind::InteractiveAuthRequired,
96fdf90aacd6atagen2026-09-08 08:24 UTC1075
                                        message: "interactive authentication is required"
96fdf90aacd6atagen2026-09-08 08:24 UTC1076
                                            .to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC1077
                                    }))
96fdf90aacd6atagen2026-09-08 08:24 UTC1078
                                    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC1079
                            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1080
                        } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC1081
                            let error = CommandError::from_http("deleting devices failed", source);
96fdf90aacd6atagen2026-09-08 08:24 UTC1082
                            let failure = CommandFailure {
96fdf90aacd6atagen2026-09-08 08:24 UTC1083
                                id,
96fdf90aacd6atagen2026-09-08 08:24 UTC1084
                                command: CommandKind::Account,
96fdf90aacd6atagen2026-09-08 08:24 UTC1085
                                kind: error.kind,
96fdf90aacd6atagen2026-09-08 08:24 UTC1086
                                message: error.to_string(),
96fdf90aacd6atagen2026-09-08 08:24 UTC1087
                            };
96fdf90aacd6atagen2026-09-08 08:24 UTC1088
                            if let Some(reply) = reply {
96fdf90aacd6atagen2026-09-08 08:24 UTC1089
                                let _ =
96fdf90aacd6atagen2026-09-08 08:24 UTC1090
                                    reply.send(Err(CommandResultError::Failed(failure.clone())));
96fdf90aacd6atagen2026-09-08 08:24 UTC1091
                            } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC1092
                                let _ = events.send(Event::CommandFailed(failure)).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC1093
                            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1094
                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC1095
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1096
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC1097
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC1098
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC1099
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1100
96fdf90aacd6atagen2026-09-08 08:24 UTC1101
    fn spawn_rename_device(
96fdf90aacd6atagen2026-09-08 08:24 UTC1102
        &mut self,
96fdf90aacd6atagen2026-09-08 08:24 UTC1103
        id: CommandId,
96fdf90aacd6atagen2026-09-08 08:24 UTC1104
        device_id: OwnedDeviceId,
96fdf90aacd6atagen2026-09-08 08:24 UTC1105
        display_name: String,
96fdf90aacd6atagen2026-09-08 08:24 UTC1106
    ) {
96fdf90aacd6atagen2026-09-08 08:24 UTC1107
        let events = self.port.event_sender();
96fdf90aacd6atagen2026-09-08 08:24 UTC1108
        let client = self.client.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC1109
        self.tasks.spawn(
96fdf90aacd6atagen2026-09-08 08:24 UTC1110
            TaskContext::Command {
96fdf90aacd6atagen2026-09-08 08:24 UTC1111
                id,
96fdf90aacd6atagen2026-09-08 08:24 UTC1112
                kind: CommandKind::Account,
96fdf90aacd6atagen2026-09-08 08:24 UTC1113
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC1114
            async move {
96fdf90aacd6atagen2026-09-08 08:24 UTC1115
                match client.rename_device(&device_id, &display_name).await {
96fdf90aacd6atagen2026-09-08 08:24 UTC1116
                    Ok(_) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1117
                        let _ = events
96fdf90aacd6atagen2026-09-08 08:24 UTC1118
                            .send(Event::CommandSucceeded {
96fdf90aacd6atagen2026-09-08 08:24 UTC1119
                                id,
96fdf90aacd6atagen2026-09-08 08:24 UTC1120
                                command: CommandKind::Account,
96fdf90aacd6atagen2026-09-08 08:24 UTC1121
                            })
96fdf90aacd6atagen2026-09-08 08:24 UTC1122
                            .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC1123
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1124
                    Err(source) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1125
                        let error = CommandError::from_http("renaming device failed", source);
96fdf90aacd6atagen2026-09-08 08:24 UTC1126
                        let _ = events
96fdf90aacd6atagen2026-09-08 08:24 UTC1127
                            .send(Event::CommandFailed(CommandFailure {
96fdf90aacd6atagen2026-09-08 08:24 UTC1128
                                id,
96fdf90aacd6atagen2026-09-08 08:24 UTC1129
                                command: CommandKind::Account,
96fdf90aacd6atagen2026-09-08 08:24 UTC1130
                                kind: error.kind,
96fdf90aacd6atagen2026-09-08 08:24 UTC1131
                                message: error.to_string(),
96fdf90aacd6atagen2026-09-08 08:24 UTC1132
                            }))
96fdf90aacd6atagen2026-09-08 08:24 UTC1133
                            .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC1134
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1135
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC1136
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC1137
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC1138
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1139
96fdf90aacd6atagen2026-09-08 08:24 UTC1140
    fn spawn_global_account_data_write(
96fdf90aacd6atagen2026-09-08 08:24 UTC1141
        &mut self,
96fdf90aacd6atagen2026-09-08 08:24 UTC1142
        event_type: GlobalAccountDataEventType,
96fdf90aacd6atagen2026-09-08 08:24 UTC1143
        content: Raw<AnyGlobalAccountDataEventContent>,
96fdf90aacd6atagen2026-09-08 08:24 UTC1144
        reply: oneshot::Sender<Result<(), ClientQueryError>>,
96fdf90aacd6atagen2026-09-08 08:24 UTC1145
    ) {
96fdf90aacd6atagen2026-09-08 08:24 UTC1146
        let (done, next) = oneshot::channel();
96fdf90aacd6atagen2026-09-08 08:24 UTC1147
        let previous = self
96fdf90aacd6atagen2026-09-08 08:24 UTC1148
            .global_account_gates
96fdf90aacd6atagen2026-09-08 08:24 UTC1149
            .insert(event_type.to_string(), next);
96fdf90aacd6atagen2026-09-08 08:24 UTC1150
        let client = self.client.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC1151
        self.tasks.spawn(
96fdf90aacd6atagen2026-09-08 08:24 UTC1152
            TaskContext::Watcher("global account data writer"),
96fdf90aacd6atagen2026-09-08 08:24 UTC1153
            async move {
96fdf90aacd6atagen2026-09-08 08:24 UTC1154
                if let Some(previous) = previous {
96fdf90aacd6atagen2026-09-08 08:24 UTC1155
                    let _ = previous.await;
96fdf90aacd6atagen2026-09-08 08:24 UTC1156
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC1157
                let result = client
96fdf90aacd6atagen2026-09-08 08:24 UTC1158
                    .account()
96fdf90aacd6atagen2026-09-08 08:24 UTC1159
                    .set_account_data_raw(event_type, content)
96fdf90aacd6atagen2026-09-08 08:24 UTC1160
                    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC1161
                    .map(|_| ())
96fdf90aacd6atagen2026-09-08 08:24 UTC1162
                    .map_err(ClientQueryError::Sdk);
96fdf90aacd6atagen2026-09-08 08:24 UTC1163
                let _ = reply.send(result);
96fdf90aacd6atagen2026-09-08 08:24 UTC1164
                let _ = done.send(());
96fdf90aacd6atagen2026-09-08 08:24 UTC1165
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC1166
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC1167
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1168
96fdf90aacd6atagen2026-09-08 08:24 UTC1169
    fn spawn_room_account_data_write(
96fdf90aacd6atagen2026-09-08 08:24 UTC1170
        &mut self,
96fdf90aacd6atagen2026-09-08 08:24 UTC1171
        room_id: OwnedRoomId,
96fdf90aacd6atagen2026-09-08 08:24 UTC1172
        event_type: RoomAccountDataEventType,
96fdf90aacd6atagen2026-09-08 08:24 UTC1173
        content: Raw<matrix_sdk::ruma::events::AnyRoomAccountDataEventContent>,
96fdf90aacd6atagen2026-09-08 08:24 UTC1174
        reply: oneshot::Sender<Result<(), ClientQueryError>>,
96fdf90aacd6atagen2026-09-08 08:24 UTC1175
    ) {
96fdf90aacd6atagen2026-09-08 08:24 UTC1176
        let (done, next) = oneshot::channel();
96fdf90aacd6atagen2026-09-08 08:24 UTC1177
        let previous = self
96fdf90aacd6atagen2026-09-08 08:24 UTC1178
            .room_account_gates
96fdf90aacd6atagen2026-09-08 08:24 UTC1179
            .insert((room_id.clone(), event_type.to_string()), next);
96fdf90aacd6atagen2026-09-08 08:24 UTC1180
        let client = self.client.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC1181
        self.tasks.spawn(
96fdf90aacd6atagen2026-09-08 08:24 UTC1182
            TaskContext::Watcher("room account data writer"),
96fdf90aacd6atagen2026-09-08 08:24 UTC1183
            async move {
96fdf90aacd6atagen2026-09-08 08:24 UTC1184
                if let Some(previous) = previous {
96fdf90aacd6atagen2026-09-08 08:24 UTC1185
                    let _ = previous.await;
96fdf90aacd6atagen2026-09-08 08:24 UTC1186
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC1187
                let result = match client.get_room(&room_id) {
96fdf90aacd6atagen2026-09-08 08:24 UTC1188
                    Some(room) => room
96fdf90aacd6atagen2026-09-08 08:24 UTC1189
                        .set_account_data_raw(event_type, content)
96fdf90aacd6atagen2026-09-08 08:24 UTC1190
                        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC1191
                        .map(|_| ())
96fdf90aacd6atagen2026-09-08 08:24 UTC1192
                        .map_err(ClientQueryError::Sdk),
96fdf90aacd6atagen2026-09-08 08:24 UTC1193
                    None => Err(ClientQueryError::RoomUnknown(room_id)),
96fdf90aacd6atagen2026-09-08 08:24 UTC1194
                };
96fdf90aacd6atagen2026-09-08 08:24 UTC1195
                let _ = reply.send(result);
96fdf90aacd6atagen2026-09-08 08:24 UTC1196
                let _ = done.send(());
96fdf90aacd6atagen2026-09-08 08:24 UTC1197
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC1198
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC1199
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1200
96fdf90aacd6atagen2026-09-08 08:24 UTC1201
    fn start_domain_workers(&mut self) {
96fdf90aacd6atagen2026-09-08 08:24 UTC1202
        let mut notification_commands = self
96fdf90aacd6atagen2026-09-08 08:24 UTC1203
            .notification_command_receiver
96fdf90aacd6atagen2026-09-08 08:24 UTC1204
            .take()
96fdf90aacd6atagen2026-09-08 08:24 UTC1205
            .expect("notification worker has not started");
96fdf90aacd6atagen2026-09-08 08:24 UTC1206
        let notification_client = self.client.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC1207
        let notification_events = self.port.event_sender();
96fdf90aacd6atagen2026-09-08 08:24 UTC1208
        self.tasks.spawn(
96fdf90aacd6atagen2026-09-08 08:24 UTC1209
            TaskContext::Watcher("notification command worker"),
96fdf90aacd6atagen2026-09-08 08:24 UTC1210
            async move {
96fdf90aacd6atagen2026-09-08 08:24 UTC1211
                while let Some(CommandJob { id, command }) = notification_commands.recv().await {
96fdf90aacd6atagen2026-09-08 08:24 UTC1212
                    let result = AssertUnwindSafe(execute_notification_command(
96fdf90aacd6atagen2026-09-08 08:24 UTC1213
                        &notification_client,
96fdf90aacd6atagen2026-09-08 08:24 UTC1214
                        command,
96fdf90aacd6atagen2026-09-08 08:24 UTC1215
                    ))
96fdf90aacd6atagen2026-09-08 08:24 UTC1216
                    .catch_unwind()
96fdf90aacd6atagen2026-09-08 08:24 UTC1217
                    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC1218
                    .unwrap_or_else(|_| {
96fdf90aacd6atagen2026-09-08 08:24 UTC1219
                        Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC1220
                            CommandErrorKind::Other,
96fdf90aacd6atagen2026-09-08 08:24 UTC1221
                            "notification command panicked",
96fdf90aacd6atagen2026-09-08 08:24 UTC1222
                        ))
96fdf90aacd6atagen2026-09-08 08:24 UTC1223
                    });
96fdf90aacd6atagen2026-09-08 08:24 UTC1224
                    let _ = emit_command_result(
96fdf90aacd6atagen2026-09-08 08:24 UTC1225
                        &notification_events,
96fdf90aacd6atagen2026-09-08 08:24 UTC1226
                        id,
96fdf90aacd6atagen2026-09-08 08:24 UTC1227
                        CommandKind::Notification,
96fdf90aacd6atagen2026-09-08 08:24 UTC1228
                        result,
96fdf90aacd6atagen2026-09-08 08:24 UTC1229
                    )
96fdf90aacd6atagen2026-09-08 08:24 UTC1230
                    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC1231
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC1232
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC1233
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC1234
96fdf90aacd6atagen2026-09-08 08:24 UTC1235
        let mut encryption_commands = self
96fdf90aacd6atagen2026-09-08 08:24 UTC1236
            .encryption_command_receiver
96fdf90aacd6atagen2026-09-08 08:24 UTC1237
            .take()
96fdf90aacd6atagen2026-09-08 08:24 UTC1238
            .expect("encryption worker has not started");
96fdf90aacd6atagen2026-09-08 08:24 UTC1239
        let encryption_client = self.client.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC1240
        let encryption_events = self.port.event_sender();
96fdf90aacd6atagen2026-09-08 08:24 UTC1241
        let verification = self.verification.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC1242
        self.tasks.spawn(
96fdf90aacd6atagen2026-09-08 08:24 UTC1243
            TaskContext::Watcher("encryption command worker"),
96fdf90aacd6atagen2026-09-08 08:24 UTC1244
            async move {
96fdf90aacd6atagen2026-09-08 08:24 UTC1245
                while let Some(work) = encryption_commands.recv().await {
96fdf90aacd6atagen2026-09-08 08:24 UTC1246
                    match work {
96fdf90aacd6atagen2026-09-08 08:24 UTC1247
                        EncryptionWork::Command(CommandJob { id, command }) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1248
                            if matches!(
96fdf90aacd6atagen2026-09-08 08:24 UTC1249
                                command,
96fdf90aacd6atagen2026-09-08 08:24 UTC1250
                                EncryptionCommand::Recover(_)
96fdf90aacd6atagen2026-09-08 08:24 UTC1251
                                    | EncryptionCommand::RecoverKeyBackup(_)
96fdf90aacd6atagen2026-09-08 08:24 UTC1252
                            ) {
96fdf90aacd6atagen2026-09-08 08:24 UTC1253
                                let result = AssertUnwindSafe(execute_encryption_command(
96fdf90aacd6atagen2026-09-08 08:24 UTC1254
                                    &encryption_client,
96fdf90aacd6atagen2026-09-08 08:24 UTC1255
                                    &encryption_events,
96fdf90aacd6atagen2026-09-08 08:24 UTC1256
                                    &mut VerificationController::default(),
96fdf90aacd6atagen2026-09-08 08:24 UTC1257
                                    command,
96fdf90aacd6atagen2026-09-08 08:24 UTC1258
                                ))
96fdf90aacd6atagen2026-09-08 08:24 UTC1259
                                .catch_unwind()
96fdf90aacd6atagen2026-09-08 08:24 UTC1260
                                .await
96fdf90aacd6atagen2026-09-08 08:24 UTC1261
                                .unwrap_or_else(|_| {
96fdf90aacd6atagen2026-09-08 08:24 UTC1262
                                    Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC1263
                                        CommandErrorKind::Other,
96fdf90aacd6atagen2026-09-08 08:24 UTC1264
                                        "encryption command panicked",
96fdf90aacd6atagen2026-09-08 08:24 UTC1265
                                    ))
96fdf90aacd6atagen2026-09-08 08:24 UTC1266
                                });
96fdf90aacd6atagen2026-09-08 08:24 UTC1267
                                let _ = emit_command_result(
96fdf90aacd6atagen2026-09-08 08:24 UTC1268
                                    &encryption_events,
96fdf90aacd6atagen2026-09-08 08:24 UTC1269
                                    id,
96fdf90aacd6atagen2026-09-08 08:24 UTC1270
                                    CommandKind::Encryption,
96fdf90aacd6atagen2026-09-08 08:24 UTC1271
                                    result,
96fdf90aacd6atagen2026-09-08 08:24 UTC1272
                                )
96fdf90aacd6atagen2026-09-08 08:24 UTC1273
                                .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC1274
                                continue;
96fdf90aacd6atagen2026-09-08 08:24 UTC1275
                            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1276
                            let is_key_backup = matches!(
96fdf90aacd6atagen2026-09-08 08:24 UTC1277
                                command,
96fdf90aacd6atagen2026-09-08 08:24 UTC1278
                                EncryptionCommand::CreateKeyBackup
96fdf90aacd6atagen2026-09-08 08:24 UTC1279
                                    | EncryptionCommand::EnableKeyBackup
96fdf90aacd6atagen2026-09-08 08:24 UTC1280
                                    | EncryptionCommand::DisableKeyBackup { .. }
96fdf90aacd6atagen2026-09-08 08:24 UTC1281
                            );
96fdf90aacd6atagen2026-09-08 08:24 UTC1282
                            let result = if is_key_backup {
96fdf90aacd6atagen2026-09-08 08:24 UTC1283
                                AssertUnwindSafe(execute_key_backup_command(
96fdf90aacd6atagen2026-09-08 08:24 UTC1284
                                    &encryption_client,
96fdf90aacd6atagen2026-09-08 08:24 UTC1285
                                    command,
96fdf90aacd6atagen2026-09-08 08:24 UTC1286
                                ))
96fdf90aacd6atagen2026-09-08 08:24 UTC1287
                                .catch_unwind()
96fdf90aacd6atagen2026-09-08 08:24 UTC1288
                                .await
96fdf90aacd6atagen2026-09-08 08:24 UTC1289
                                .unwrap_or_else(|_| {
96fdf90aacd6atagen2026-09-08 08:24 UTC1290
                                    Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC1291
                                        CommandErrorKind::Other,
96fdf90aacd6atagen2026-09-08 08:24 UTC1292
                                        "encryption command panicked",
96fdf90aacd6atagen2026-09-08 08:24 UTC1293
                                    ))
96fdf90aacd6atagen2026-09-08 08:24 UTC1294
                                })
96fdf90aacd6atagen2026-09-08 08:24 UTC1295
                            } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC1296
                                let mut verification = verification.lock().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC1297
                                let result = AssertUnwindSafe(execute_encryption_command(
96fdf90aacd6atagen2026-09-08 08:24 UTC1298
                                    &encryption_client,
96fdf90aacd6atagen2026-09-08 08:24 UTC1299
                                    &encryption_events,
96fdf90aacd6atagen2026-09-08 08:24 UTC1300
                                    &mut verification,
96fdf90aacd6atagen2026-09-08 08:24 UTC1301
                                    command,
96fdf90aacd6atagen2026-09-08 08:24 UTC1302
                                ))
96fdf90aacd6atagen2026-09-08 08:24 UTC1303
                                .catch_unwind()
96fdf90aacd6atagen2026-09-08 08:24 UTC1304
                                .await
96fdf90aacd6atagen2026-09-08 08:24 UTC1305
                                .unwrap_or_else(|_| {
96fdf90aacd6atagen2026-09-08 08:24 UTC1306
                                    Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC1307
                                        CommandErrorKind::Other,
96fdf90aacd6atagen2026-09-08 08:24 UTC1308
                                        "encryption command panicked",
96fdf90aacd6atagen2026-09-08 08:24 UTC1309
                                    ))
96fdf90aacd6atagen2026-09-08 08:24 UTC1310
                                });
96fdf90aacd6atagen2026-09-08 08:24 UTC1311
                                drop(verification);
96fdf90aacd6atagen2026-09-08 08:24 UTC1312
                                result
96fdf90aacd6atagen2026-09-08 08:24 UTC1313
                            };
96fdf90aacd6atagen2026-09-08 08:24 UTC1314
                            let _ = emit_command_result(
96fdf90aacd6atagen2026-09-08 08:24 UTC1315
                                &encryption_events,
96fdf90aacd6atagen2026-09-08 08:24 UTC1316
                                id,
96fdf90aacd6atagen2026-09-08 08:24 UTC1317
                                CommandKind::Encryption,
96fdf90aacd6atagen2026-09-08 08:24 UTC1318
                                result,
96fdf90aacd6atagen2026-09-08 08:24 UTC1319
                            )
96fdf90aacd6atagen2026-09-08 08:24 UTC1320
                            .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC1321
                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC1322
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1323
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC1324
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC1325
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC1326
96fdf90aacd6atagen2026-09-08 08:24 UTC1327
        let room_commands = self
96fdf90aacd6atagen2026-09-08 08:24 UTC1328
            .room_command_receiver
96fdf90aacd6atagen2026-09-08 08:24 UTC1329
            .take()
96fdf90aacd6atagen2026-09-08 08:24 UTC1330
            .expect("room command worker has not started");
96fdf90aacd6atagen2026-09-08 08:24 UTC1331
        let room_client = self.client.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC1332
        let room_events = self.port.event_sender();
96fdf90aacd6atagen2026-09-08 08:24 UTC1333
        self.tasks.spawn(
96fdf90aacd6atagen2026-09-08 08:24 UTC1334
            TaskContext::Watcher("room command worker"),
96fdf90aacd6atagen2026-09-08 08:24 UTC1335
            run_room_command_worker(
96fdf90aacd6atagen2026-09-08 08:24 UTC1336
                room_client,
96fdf90aacd6atagen2026-09-08 08:24 UTC1337
                room_events,
96fdf90aacd6atagen2026-09-08 08:24 UTC1338
                room_commands,
96fdf90aacd6atagen2026-09-08 08:24 UTC1339
                self.room_command_capacity,
96fdf90aacd6atagen2026-09-08 08:24 UTC1340
            ),
96fdf90aacd6atagen2026-09-08 08:24 UTC1341
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC1342
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1343
96fdf90aacd6atagen2026-09-08 08:24 UTC1344
    fn start_watchers(&mut self) {
96fdf90aacd6atagen2026-09-08 08:24 UTC1345
        let mut session_changes = self
96fdf90aacd6atagen2026-09-08 08:24 UTC1346
            .session_changes
96fdf90aacd6atagen2026-09-08 08:24 UTC1347
            .take()
96fdf90aacd6atagen2026-09-08 08:24 UTC1348
            .expect("session watcher has not started");
96fdf90aacd6atagen2026-09-08 08:24 UTC1349
        let session_sender = self.port.event_sender();
fa9114fcf75fatagen2026-09-24 12:51 UTC1350
        let stop = self.stop.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC1351
        self.tasks
96fdf90aacd6atagen2026-09-08 08:24 UTC1352
            .spawn(TaskContext::Watcher("session change watcher"), async move {
96fdf90aacd6atagen2026-09-08 08:24 UTC1353
                loop {
96fdf90aacd6atagen2026-09-08 08:24 UTC1354
                    let event = match session_changes.recv().await {
96fdf90aacd6atagen2026-09-08 08:24 UTC1355
                        Ok(SessionChange::TokensRefreshed) => Event::SessionTokensChanged,
fa9114fcf75fatagen2026-09-24 12:51 UTC1356
                        Ok(SessionChange::UnknownToken(data)) => {
fa9114fcf75fatagen2026-09-24 12:51 UTC1357
                            let _ = session_sender
fa9114fcf75fatagen2026-09-24 12:51 UTC1358
                                .send(Event::SessionAccessRejected {
fa9114fcf75fatagen2026-09-24 12:51 UTC1359
                                    soft_logout: data.soft_logout,
fa9114fcf75fatagen2026-09-24 12:51 UTC1360
                                })
fa9114fcf75fatagen2026-09-24 12:51 UTC1361
                                .await;
fa9114fcf75fatagen2026-09-24 12:51 UTC1362
                            stop.notify_one();
fa9114fcf75fatagen2026-09-24 12:51 UTC1363
                            break;
fa9114fcf75fatagen2026-09-24 12:51 UTC1364
                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC1365
                        Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1366
                            Event::SessionTokensChanged
96fdf90aacd6atagen2026-09-08 08:24 UTC1367
                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC1368
                        Err(tokio::sync::broadcast::error::RecvError::Closed) => break,
96fdf90aacd6atagen2026-09-08 08:24 UTC1369
                    };
96fdf90aacd6atagen2026-09-08 08:24 UTC1370
                    if session_sender.send(event).await.is_err() {
96fdf90aacd6atagen2026-09-08 08:24 UTC1371
                        break;
96fdf90aacd6atagen2026-09-08 08:24 UTC1372
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1373
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC1374
            });
96fdf90aacd6atagen2026-09-08 08:24 UTC1375
96fdf90aacd6atagen2026-09-08 08:24 UTC1376
        let mut sync_states = self.sync.state();
96fdf90aacd6atagen2026-09-08 08:24 UTC1377
        let state_sender = self.port.event_sender();
96fdf90aacd6atagen2026-09-08 08:24 UTC1378
        let connection_sender = self.port.connection_sender();
fa9114fcf75fatagen2026-09-24 12:51 UTC1379
        let sync = self.sync.clone();
fa9114fcf75fatagen2026-09-24 12:51 UTC1380
        let client = self.client.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC1381
        self.tasks.spawn(
96fdf90aacd6atagen2026-09-08 08:24 UTC1382
            TaskContext::Watcher("connection state watcher"),
96fdf90aacd6atagen2026-09-08 08:24 UTC1383
            async move {
fa9114fcf75fatagen2026-09-24 12:51 UTC1384
                let mut backoff = SYNC_RESTART_MIN;
96fdf90aacd6atagen2026-09-08 08:24 UTC1385
                while let Some(state) = sync_states.next().await {
fa9114fcf75fatagen2026-09-24 12:51 UTC1386
                    let (state, restart) = match state {
fa9114fcf75fatagen2026-09-24 12:51 UTC1387
                        SyncState::Idle => (ConnectionState::Idle, false),
fa9114fcf75fatagen2026-09-24 12:51 UTC1388
                        SyncState::Running => {
fa9114fcf75fatagen2026-09-24 12:51 UTC1389
                            backoff = SYNC_RESTART_MIN;
fa9114fcf75fatagen2026-09-24 12:51 UTC1390
                            // the sdk parks a room queue on any send error and never unparks it
fa9114fcf75fatagen2026-09-24 12:51 UTC1391
                            client.send_queue().set_enabled(true).await;
fa9114fcf75fatagen2026-09-24 12:51 UTC1392
                            (ConnectionState::Online, false)
fa9114fcf75fatagen2026-09-24 12:51 UTC1393
                        }
fa9114fcf75fatagen2026-09-24 12:51 UTC1394
                        SyncState::Offline => (ConnectionState::Offline, false),
fa9114fcf75fatagen2026-09-24 12:51 UTC1395
                        SyncState::Terminated => (ConnectionState::Offline, true),
96fdf90aacd6atagen2026-09-08 08:24 UTC1396
                        SyncState::Error(error) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1397
                            tracing::warn!(error = %error, "sync service reported an error");
fa9114fcf75fatagen2026-09-24 12:51 UTC1398
                            (ConnectionState::Offline, true)
96fdf90aacd6atagen2026-09-08 08:24 UTC1399
                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC1400
                    };
fa9114fcf75fatagen2026-09-24 12:51 UTC1401
                    if *connection_sender.borrow() != state {
fa9114fcf75fatagen2026-09-24 12:51 UTC1402
                        connection_sender.send_replace(state);
fa9114fcf75fatagen2026-09-24 12:51 UTC1403
                        if state_sender
fa9114fcf75fatagen2026-09-24 12:51 UTC1404
                            .send(Event::ConnectionChanged(state))
fa9114fcf75fatagen2026-09-24 12:51 UTC1405
                            .await
fa9114fcf75fatagen2026-09-24 12:51 UTC1406
                            .is_err()
fa9114fcf75fatagen2026-09-24 12:51 UTC1407
                        {
fa9114fcf75fatagen2026-09-24 12:51 UTC1408
                            break;
fa9114fcf75fatagen2026-09-24 12:51 UTC1409
                        }
fa9114fcf75fatagen2026-09-24 12:51 UTC1410
                    }
fa9114fcf75fatagen2026-09-24 12:51 UTC1411
                    if restart {
fa9114fcf75fatagen2026-09-24 12:51 UTC1412
                        tokio::time::sleep(backoff).await;
fa9114fcf75fatagen2026-09-24 12:51 UTC1413
                        backoff = (backoff * 2).min(SYNC_RESTART_MAX);
fa9114fcf75fatagen2026-09-24 12:51 UTC1414
                        sync.start().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC1415
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1416
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC1417
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC1418
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC1419
96fdf90aacd6atagen2026-09-08 08:24 UTC1420
        let client = self.client.clone();
fa9114fcf75fatagen2026-09-24 12:51 UTC1421
        let online = self.port.connection_sender().subscribe();
fa9114fcf75fatagen2026-09-24 12:51 UTC1422
        self.tasks
fa9114fcf75fatagen2026-09-24 12:51 UTC1423
            .spawn(TaskContext::Watcher("send queue watcher"), async move {
fa9114fcf75fatagen2026-09-24 12:51 UTC1424
                let mut errors = client.send_queue().subscribe_errors();
fa9114fcf75fatagen2026-09-24 12:51 UTC1425
                let mut retries = JoinSet::new();
fa9114fcf75fatagen2026-09-24 12:51 UTC1426
                loop {
fa9114fcf75fatagen2026-09-24 12:51 UTC1427
                    let error = tokio::select! {
fa9114fcf75fatagen2026-09-24 12:51 UTC1428
                        error = errors.recv() => error,
fa9114fcf75fatagen2026-09-24 12:51 UTC1429
                        Some(_) = retries.join_next(), if !retries.is_empty() => continue,
fa9114fcf75fatagen2026-09-24 12:51 UTC1430
                    };
fa9114fcf75fatagen2026-09-24 12:51 UTC1431
                    match error {
fa9114fcf75fatagen2026-09-24 12:51 UTC1432
                        Ok(error) if error.is_recoverable => {
fa9114fcf75fatagen2026-09-24 12:51 UTC1433
                            let client = client.clone();
fa9114fcf75fatagen2026-09-24 12:51 UTC1434
                            let online = online.clone();
fa9114fcf75fatagen2026-09-24 12:51 UTC1435
                            retries.spawn(async move {
fa9114fcf75fatagen2026-09-24 12:51 UTC1436
                                tokio::time::sleep(SEND_QUEUE_RETRY).await;
fa9114fcf75fatagen2026-09-24 12:51 UTC1437
                                if *online.borrow() == ConnectionState::Online
fa9114fcf75fatagen2026-09-24 12:51 UTC1438
                                    && let Some(room) = client.get_room(&error.room_id)
fa9114fcf75fatagen2026-09-24 12:51 UTC1439
                                {
fa9114fcf75fatagen2026-09-24 12:51 UTC1440
                                    room.send_queue().set_enabled(true);
fa9114fcf75fatagen2026-09-24 12:51 UTC1441
                                }
fa9114fcf75fatagen2026-09-24 12:51 UTC1442
                            });
fa9114fcf75fatagen2026-09-24 12:51 UTC1443
                        }
fa9114fcf75fatagen2026-09-24 12:51 UTC1444
                        Ok(_) | Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => {}
fa9114fcf75fatagen2026-09-24 12:51 UTC1445
                        Err(tokio::sync::broadcast::error::RecvError::Closed) => break,
fa9114fcf75fatagen2026-09-24 12:51 UTC1446
                    }
fa9114fcf75fatagen2026-09-24 12:51 UTC1447
                }
fa9114fcf75fatagen2026-09-24 12:51 UTC1448
            });
fa9114fcf75fatagen2026-09-24 12:51 UTC1449
fa9114fcf75fatagen2026-09-24 12:51 UTC1450
        let client = self.client.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC1451
        let encryption_sender = self.port.event_sender();
96fdf90aacd6atagen2026-09-08 08:24 UTC1452
        self.tasks.spawn(
96fdf90aacd6atagen2026-09-08 08:24 UTC1453
            TaskContext::Watcher("encryption state watcher"),
96fdf90aacd6atagen2026-09-08 08:24 UTC1454
            async move {
96fdf90aacd6atagen2026-09-08 08:24 UTC1455
            let Some(device_id) = client.device_id().map(ToOwned::to_owned) else {
96fdf90aacd6atagen2026-09-08 08:24 UTC1456
                tracing::warn!("encryption watcher stopped because the client has no device id");
96fdf90aacd6atagen2026-09-08 08:24 UTC1457
                return;
96fdf90aacd6atagen2026-09-08 08:24 UTC1458
            };
96fdf90aacd6atagen2026-09-08 08:24 UTC1459
            let recovery = client.encryption().recovery();
96fdf90aacd6atagen2026-09-08 08:24 UTC1460
            let backups = client.encryption().backups();
96fdf90aacd6atagen2026-09-08 08:24 UTC1461
            let mut recovery_stream = recovery.state_stream();
96fdf90aacd6atagen2026-09-08 08:24 UTC1462
            let mut backup_stream = backups.state_stream();
96fdf90aacd6atagen2026-09-08 08:24 UTC1463
            let mut verification_stream = client.encryption().verification_state();
96fdf90aacd6atagen2026-09-08 08:24 UTC1464
            let mut recovery_state = recovery.state();
96fdf90aacd6atagen2026-09-08 08:24 UTC1465
            let mut backup_state = backups.state();
96fdf90aacd6atagen2026-09-08 08:24 UTC1466
            let mut verification_state = verification_stream.get();
96fdf90aacd6atagen2026-09-08 08:24 UTC1467
            if encryption_sender
96fdf90aacd6atagen2026-09-08 08:24 UTC1468
                .send(Event::EncryptionChanged(encryption_state(
96fdf90aacd6atagen2026-09-08 08:24 UTC1469
                    device_id.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC1470
                    recovery_state,
96fdf90aacd6atagen2026-09-08 08:24 UTC1471
                    verification_state,
96fdf90aacd6atagen2026-09-08 08:24 UTC1472
                    backup_state,
96fdf90aacd6atagen2026-09-08 08:24 UTC1473
                )))
96fdf90aacd6atagen2026-09-08 08:24 UTC1474
                .await
96fdf90aacd6atagen2026-09-08 08:24 UTC1475
                .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC1476
            {
96fdf90aacd6atagen2026-09-08 08:24 UTC1477
                tracing::debug!(
96fdf90aacd6atagen2026-09-08 08:24 UTC1478
                    "encryption watcher stopped because the event receiver closed"
96fdf90aacd6atagen2026-09-08 08:24 UTC1479
                );
96fdf90aacd6atagen2026-09-08 08:24 UTC1480
                return;
96fdf90aacd6atagen2026-09-08 08:24 UTC1481
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1482
            loop {
96fdf90aacd6atagen2026-09-08 08:24 UTC1483
                tokio::select! {
96fdf90aacd6atagen2026-09-08 08:24 UTC1484
                    update = recovery_stream.next() => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1485
                        let Some(update) = update else {
96fdf90aacd6atagen2026-09-08 08:24 UTC1486
                            tracing::debug!("encryption watcher stopped because the recovery stream ended");
96fdf90aacd6atagen2026-09-08 08:24 UTC1487
                            break;
96fdf90aacd6atagen2026-09-08 08:24 UTC1488
                        };
96fdf90aacd6atagen2026-09-08 08:24 UTC1489
                        recovery_state = update;
96fdf90aacd6atagen2026-09-08 08:24 UTC1490
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1491
                    update = verification_stream.next() => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1492
                        let Some(update) = update else {
96fdf90aacd6atagen2026-09-08 08:24 UTC1493
                            tracing::debug!("encryption watcher stopped because the verification stream ended");
96fdf90aacd6atagen2026-09-08 08:24 UTC1494
                            break;
96fdf90aacd6atagen2026-09-08 08:24 UTC1495
                        };
96fdf90aacd6atagen2026-09-08 08:24 UTC1496
                        verification_state = update;
96fdf90aacd6atagen2026-09-08 08:24 UTC1497
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1498
                    update = backup_stream.next() => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1499
                        let Some(update) = update else {
96fdf90aacd6atagen2026-09-08 08:24 UTC1500
                            tracing::debug!("backup watcher stopped because the backup state stream ended");
96fdf90aacd6atagen2026-09-08 08:24 UTC1501
                            break;
96fdf90aacd6atagen2026-09-08 08:24 UTC1502
                        };
96fdf90aacd6atagen2026-09-08 08:24 UTC1503
                        backup_state = update.unwrap_or_else(|_| backups.state());
96fdf90aacd6atagen2026-09-08 08:24 UTC1504
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1505
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC1506
                if encryption_sender
96fdf90aacd6atagen2026-09-08 08:24 UTC1507
                    .send(Event::EncryptionChanged(encryption_state(
96fdf90aacd6atagen2026-09-08 08:24 UTC1508
                        device_id.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC1509
                        recovery_state,
96fdf90aacd6atagen2026-09-08 08:24 UTC1510
                        verification_state,
96fdf90aacd6atagen2026-09-08 08:24 UTC1511
                        backup_state,
96fdf90aacd6atagen2026-09-08 08:24 UTC1512
                    )))
96fdf90aacd6atagen2026-09-08 08:24 UTC1513
                    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC1514
                    .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC1515
                {
96fdf90aacd6atagen2026-09-08 08:24 UTC1516
                    tracing::debug!(
96fdf90aacd6atagen2026-09-08 08:24 UTC1517
                        "encryption watcher stopped because the event receiver closed"
96fdf90aacd6atagen2026-09-08 08:24 UTC1518
                    );
96fdf90aacd6atagen2026-09-08 08:24 UTC1519
                    break;
96fdf90aacd6atagen2026-09-08 08:24 UTC1520
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC1521
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1522
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC1523
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC1524
96fdf90aacd6atagen2026-09-08 08:24 UTC1525
        let client = self.client.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC1526
        let room_sender = self.port.event_sender();
96fdf90aacd6atagen2026-09-08 08:24 UTC1527
        self.tasks.spawn(
96fdf90aacd6atagen2026-09-08 08:24 UTC1528
            TaskContext::Watcher("room list watcher"),
96fdf90aacd6atagen2026-09-08 08:24 UTC1529
            async move {
96fdf90aacd6atagen2026-09-08 08:24 UTC1530
            let (_, mut room_stream) = client.rooms_stream();
96fdf90aacd6atagen2026-09-08 08:24 UTC1531
            let mut info_stream = client.room_info_notable_update_receiver();
96fdf90aacd6atagen2026-09-08 08:24 UTC1532
            if room_sender.send(Event::RoomsChanged).await.is_err() {
96fdf90aacd6atagen2026-09-08 08:24 UTC1533
                tracing::debug!("room watcher stopped because the event receiver closed");
96fdf90aacd6atagen2026-09-08 08:24 UTC1534
                return;
96fdf90aacd6atagen2026-09-08 08:24 UTC1535
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1536
            loop {
96fdf90aacd6atagen2026-09-08 08:24 UTC1537
                tokio::select! {
96fdf90aacd6atagen2026-09-08 08:24 UTC1538
                    update = room_stream.next() => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1539
                        if update.is_none() {
96fdf90aacd6atagen2026-09-08 08:24 UTC1540
                            tracing::debug!("room watcher stopped because the room stream ended");
96fdf90aacd6atagen2026-09-08 08:24 UTC1541
                            break;
96fdf90aacd6atagen2026-09-08 08:24 UTC1542
                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC1543
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1544
                    update = info_stream.recv() => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1545
                        if update.is_err() && info_stream.is_closed() {
96fdf90aacd6atagen2026-09-08 08:24 UTC1546
                            tracing::debug!("room watcher stopped because the room info stream ended");
96fdf90aacd6atagen2026-09-08 08:24 UTC1547
                            break;
96fdf90aacd6atagen2026-09-08 08:24 UTC1548
                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC1549
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1550
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC1551
                if room_sender.send(Event::RoomsChanged).await.is_err() {
96fdf90aacd6atagen2026-09-08 08:24 UTC1552
                    tracing::debug!("room watcher stopped because the event receiver closed");
96fdf90aacd6atagen2026-09-08 08:24 UTC1553
                    break;
96fdf90aacd6atagen2026-09-08 08:24 UTC1554
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC1555
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1556
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC1557
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC1558
96fdf90aacd6atagen2026-09-08 08:24 UTC1559
        let client = self.client.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC1560
        let notification_sender = self.port.event_sender();
96fdf90aacd6atagen2026-09-08 08:24 UTC1561
        self.tasks.spawn(
96fdf90aacd6atagen2026-09-08 08:24 UTC1562
            TaskContext::Watcher("notification settings watcher"),
96fdf90aacd6atagen2026-09-08 08:24 UTC1563
            async move {
96fdf90aacd6atagen2026-09-08 08:24 UTC1564
                let settings = client.notification_settings().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC1565
                let mut changes = settings.subscribe_to_changes();
96fdf90aacd6atagen2026-09-08 08:24 UTC1566
                while let Ok(()) | Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) =
96fdf90aacd6atagen2026-09-08 08:24 UTC1567
                    changes.recv().await
96fdf90aacd6atagen2026-09-08 08:24 UTC1568
                {
96fdf90aacd6atagen2026-09-08 08:24 UTC1569
                    if notification_sender
96fdf90aacd6atagen2026-09-08 08:24 UTC1570
                        .send(Event::NotificationSettingsChanged)
96fdf90aacd6atagen2026-09-08 08:24 UTC1571
                        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC1572
                        .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC1573
                    {
96fdf90aacd6atagen2026-09-08 08:24 UTC1574
                        break;
96fdf90aacd6atagen2026-09-08 08:24 UTC1575
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1576
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC1577
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC1578
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC1579
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1580
96fdf90aacd6atagen2026-09-08 08:24 UTC1581
    async fn execute(&mut self, id: CommandId, command: Command) -> Result<bool, CommandError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC1582
        match command {
96fdf90aacd6atagen2026-09-08 08:24 UTC1583
            Command::Account(AccountCommand::Logout) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1584
                self.spawn_logout(id);
96fdf90aacd6atagen2026-09-08 08:24 UTC1585
                Ok(true)
96fdf90aacd6atagen2026-09-08 08:24 UTC1586
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1587
            Command::Account(AccountCommand::RenameDevice {
96fdf90aacd6atagen2026-09-08 08:24 UTC1588
                device_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC1589
                display_name,
96fdf90aacd6atagen2026-09-08 08:24 UTC1590
            }) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1591
                self.spawn_rename_device(id, device_id, display_name);
96fdf90aacd6atagen2026-09-08 08:24 UTC1592
                Ok(true)
96fdf90aacd6atagen2026-09-08 08:24 UTC1593
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1594
            Command::Account(AccountCommand::DeleteDevices { device_ids, auth }) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1595
                self.spawn_delete_devices(id, device_ids, auth, None);
96fdf90aacd6atagen2026-09-08 08:24 UTC1596
                Ok(true)
96fdf90aacd6atagen2026-09-08 08:24 UTC1597
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1598
            Command::Room(RoomCommand::Open(room_id)) => self.open_room(id, room_id).await,
96fdf90aacd6atagen2026-09-08 08:24 UTC1599
            Command::Room(RoomCommand::Close(room_id)) => self.close_room(id, &room_id).await,
96fdf90aacd6atagen2026-09-08 08:24 UTC1600
            Command::Room(RoomCommand::SetTyping { room_id, typing }) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1601
                self.spawn_typing(id, room_id, typing);
96fdf90aacd6atagen2026-09-08 08:24 UTC1602
                Ok(true)
96fdf90aacd6atagen2026-09-08 08:24 UTC1603
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1604
            Command::Room(RoomCommand::Join { room, via }) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1605
                self.spawn_room_command(id, RoomCommand::Join { room, via })
96fdf90aacd6atagen2026-09-08 08:24 UTC1606
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1607
            Command::Room(RoomCommand::Leave(room_id)) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1608
                self.spawn_room_command(id, RoomCommand::Leave(room_id))
96fdf90aacd6atagen2026-09-08 08:24 UTC1609
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1610
            Command::Room(RoomCommand::Forget(room_id)) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1611
                self.spawn_room_command(id, RoomCommand::Forget(room_id))
96fdf90aacd6atagen2026-09-08 08:24 UTC1612
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1613
            Command::Room(RoomCommand::AcceptInvite(room_id)) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1614
                self.spawn_room_command(id, RoomCommand::AcceptInvite(room_id))
96fdf90aacd6atagen2026-09-08 08:24 UTC1615
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1616
            Command::Room(RoomCommand::RejectInvite(room_id)) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1617
                self.spawn_room_command(id, RoomCommand::RejectInvite(room_id))
96fdf90aacd6atagen2026-09-08 08:24 UTC1618
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1619
            Command::Room(command @ RoomCommand::InviteUser { .. })
96fdf90aacd6atagen2026-09-08 08:24 UTC1620
            | Command::Room(command @ RoomCommand::KickUser { .. })
96fdf90aacd6atagen2026-09-08 08:24 UTC1621
            | Command::Room(command @ RoomCommand::BanUser { .. })
96fdf90aacd6atagen2026-09-08 08:24 UTC1622
            | Command::Room(command @ RoomCommand::UnbanUser { .. })
96fdf90aacd6atagen2026-09-08 08:24 UTC1623
            | Command::Room(command @ RoomCommand::SetPowerLevels { .. })
96fdf90aacd6atagen2026-09-08 08:24 UTC1624
            | Command::Room(command @ RoomCommand::SetUserPowerLevel { .. }) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1625
                self.spawn_room_command(id, command)
96fdf90aacd6atagen2026-09-08 08:24 UTC1626
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1627
            Command::Room(RoomCommand::Create(options)) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1628
                self.spawn_room_command(id, RoomCommand::Create(options))
96fdf90aacd6atagen2026-09-08 08:24 UTC1629
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1630
            Command::Room(RoomCommand::CreateDirect(user_id)) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1631
                self.spawn_room_command(id, RoomCommand::CreateDirect(user_id))
96fdf90aacd6atagen2026-09-08 08:24 UTC1632
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1633
            Command::Room(RoomCommand::CancelSend {
96fdf90aacd6atagen2026-09-08 08:24 UTC1634
                room_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC1635
                transaction_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC1636
            }) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1637
                self.spawn_cancel_send(id, room_id, transaction_id);
96fdf90aacd6atagen2026-09-08 08:24 UTC1638
                Ok(true)
96fdf90aacd6atagen2026-09-08 08:24 UTC1639
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1640
            Command::Room(RoomCommand::RetrySend {
96fdf90aacd6atagen2026-09-08 08:24 UTC1641
                room_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC1642
                transaction_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC1643
            }) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1644
                self.spawn_retry_send(id, room_id, transaction_id);
96fdf90aacd6atagen2026-09-08 08:24 UTC1645
                Ok(true)
96fdf90aacd6atagen2026-09-08 08:24 UTC1646
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1647
            Command::Timeline(command) => self.route_timeline(id, command),
96fdf90aacd6atagen2026-09-08 08:24 UTC1648
            Command::Media(MediaCommand::Fetch { id: media_id, size }) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1649
                self.spawn_media_fetch(id, media_id, size);
96fdf90aacd6atagen2026-09-08 08:24 UTC1650
                Ok(true)
96fdf90aacd6atagen2026-09-08 08:24 UTC1651
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1652
            Command::Media(MediaCommand::Upload {
96fdf90aacd6atagen2026-09-08 08:24 UTC1653
                timeline_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC1654
                filename,
96fdf90aacd6atagen2026-09-08 08:24 UTC1655
                mimetype,
96fdf90aacd6atagen2026-09-08 08:24 UTC1656
                bytes,
96fdf90aacd6atagen2026-09-08 08:24 UTC1657
                caption,
96fdf90aacd6atagen2026-09-08 08:24 UTC1658
            }) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1659
                let mut upload = AttachmentUpload::new(timeline_id, filename, mimetype, bytes);
96fdf90aacd6atagen2026-09-08 08:24 UTC1660
                upload.caption = caption;
96fdf90aacd6atagen2026-09-08 08:24 UTC1661
                self.spawn_media_upload(id, upload).await.map(|()| true)
96fdf90aacd6atagen2026-09-08 08:24 UTC1662
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1663
            Command::Media(MediaCommand::UploadAttachment(upload)) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1664
                self.spawn_media_upload(id, upload).await.map(|()| true)
96fdf90aacd6atagen2026-09-08 08:24 UTC1665
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1666
            Command::Encryption(command) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1667
                self.encryption_commands
96fdf90aacd6atagen2026-09-08 08:24 UTC1668
                    .try_send(EncryptionWork::Command(CommandJob { id, command }))
96fdf90aacd6atagen2026-09-08 08:24 UTC1669
                    .map_err(|error| domain_queue_error(error, "encryption"))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC1670
                Ok(true)
96fdf90aacd6atagen2026-09-08 08:24 UTC1671
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1672
            Command::Notification(command) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1673
                self.notification_commands
96fdf90aacd6atagen2026-09-08 08:24 UTC1674
                    .try_send(CommandJob { id, command })
96fdf90aacd6atagen2026-09-08 08:24 UTC1675
                    .map_err(|error| domain_queue_error(error, "notification"))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC1676
                Ok(true)
96fdf90aacd6atagen2026-09-08 08:24 UTC1677
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1678
            Command::Lifecycle(LifecycleCommand::Shutdown) => Ok(false),
96fdf90aacd6atagen2026-09-08 08:24 UTC1679
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC1680
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1681
96fdf90aacd6atagen2026-09-08 08:24 UTC1682
    async fn open_room(
96fdf90aacd6atagen2026-09-08 08:24 UTC1683
        &mut self,
96fdf90aacd6atagen2026-09-08 08:24 UTC1684
        id: CommandId,
96fdf90aacd6atagen2026-09-08 08:24 UTC1685
        room_id: OwnedRoomId,
96fdf90aacd6atagen2026-09-08 08:24 UTC1686
    ) -> Result<bool, CommandError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC1687
        match self.rooms.get_mut(&room_id) {
96fdf90aacd6atagen2026-09-08 08:24 UTC1688
            Some(RoomActorState::Opening { sender, .. }) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1689
                send_room_actor_request(sender, RoomActorRequest::Opened { id })?;
96fdf90aacd6atagen2026-09-08 08:24 UTC1690
                return Ok(true);
96fdf90aacd6atagen2026-09-08 08:24 UTC1691
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1692
            Some(RoomActorState::Open { .. }) => return Ok(false),
96fdf90aacd6atagen2026-09-08 08:24 UTC1693
            Some(RoomActorState::Closing { pending, .. }) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1694
                if pending.len() >= self.room_command_capacity {
96fdf90aacd6atagen2026-09-08 08:24 UTC1695
                    return Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC1696
                        CommandErrorKind::RoomBusy,
96fdf90aacd6atagen2026-09-08 08:24 UTC1697
                        "room lifecycle queue is full",
96fdf90aacd6atagen2026-09-08 08:24 UTC1698
                    ));
96fdf90aacd6atagen2026-09-08 08:24 UTC1699
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC1700
                pending.push_back(DeferredRoomLifecycle::Open(id));
96fdf90aacd6atagen2026-09-08 08:24 UTC1701
                return Ok(true);
96fdf90aacd6atagen2026-09-08 08:24 UTC1702
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1703
            None => {}
96fdf90aacd6atagen2026-09-08 08:24 UTC1704
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC1705
        self.start_room_actor(room_id, vec![id]).await?;
96fdf90aacd6atagen2026-09-08 08:24 UTC1706
        Ok(true)
96fdf90aacd6atagen2026-09-08 08:24 UTC1707
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1708
96fdf90aacd6atagen2026-09-08 08:24 UTC1709
    async fn start_room_actor(
96fdf90aacd6atagen2026-09-08 08:24 UTC1710
        &mut self,
96fdf90aacd6atagen2026-09-08 08:24 UTC1711
        room_id: OwnedRoomId,
96fdf90aacd6atagen2026-09-08 08:24 UTC1712
        open_ids: Vec<CommandId>,
96fdf90aacd6atagen2026-09-08 08:24 UTC1713
    ) -> Result<(), CommandError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC1714
        let room = self.client.get_room(&room_id).ok_or_else(|| {
96fdf90aacd6atagen2026-09-08 08:24 UTC1715
            CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC1716
                CommandErrorKind::RoomUnknown,
96fdf90aacd6atagen2026-09-08 08:24 UTC1717
                "room is not available in the local store",
96fdf90aacd6atagen2026-09-08 08:24 UTC1718
            )
96fdf90aacd6atagen2026-09-08 08:24 UTC1719
        })?;
96fdf90aacd6atagen2026-09-08 08:24 UTC1720
        self.next_room_generation = self.next_room_generation.wrapping_add(1);
96fdf90aacd6atagen2026-09-08 08:24 UTC1721
        let generation = self.next_room_generation;
96fdf90aacd6atagen2026-09-08 08:24 UTC1722
        let (sender, receiver) = mpsc::channel(self.room_command_capacity);
96fdf90aacd6atagen2026-09-08 08:24 UTC1723
        let actor = RoomActor {
96fdf90aacd6atagen2026-09-08 08:24 UTC1724
            room_id: room_id.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC1725
            generation,
96fdf90aacd6atagen2026-09-08 08:24 UTC1726
            room,
96fdf90aacd6atagen2026-09-08 08:24 UTC1727
            receiver,
96fdf90aacd6atagen2026-09-08 08:24 UTC1728
            events: self.port.event_sender(),
96fdf90aacd6atagen2026-09-08 08:24 UTC1729
            engine_events: self.room_actor_event_sender.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC1730
            media: self.media.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC1731
            initial_history_limit: self.initial_history_limit,
96fdf90aacd6atagen2026-09-08 08:24 UTC1732
            open_ids,
fa9114fcf75fatagen2026-09-24 12:51 UTC1733
            background: JoinSet::new(),
fa9114fcf75fatagen2026-09-24 12:51 UTC1734
            background_ids: HashMap::new(),
96fdf90aacd6atagen2026-09-08 08:24 UTC1735
        };
96fdf90aacd6atagen2026-09-08 08:24 UTC1736
        let current_user_id = self.client.user_id().map(ToOwned::to_owned);
96fdf90aacd6atagen2026-09-08 08:24 UTC1737
        self.rooms.insert(
96fdf90aacd6atagen2026-09-08 08:24 UTC1738
            room_id.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC1739
            RoomActorState::Opening { generation, sender },
96fdf90aacd6atagen2026-09-08 08:24 UTC1740
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC1741
        let actor_room_id = room_id.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC1742
        let engine_events = self.room_actor_event_sender.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC1743
        self.tasks.spawn(
96fdf90aacd6atagen2026-09-08 08:24 UTC1744
            TaskContext::RoomActor {
96fdf90aacd6atagen2026-09-08 08:24 UTC1745
                room_id: room_id.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC1746
                generation,
96fdf90aacd6atagen2026-09-08 08:24 UTC1747
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC1748
            async move {
96fdf90aacd6atagen2026-09-08 08:24 UTC1749
                actor.run(current_user_id).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC1750
                let _ = engine_events.send(RoomActorEvent::Exited {
96fdf90aacd6atagen2026-09-08 08:24 UTC1751
                    room_id: actor_room_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC1752
                    generation,
96fdf90aacd6atagen2026-09-08 08:24 UTC1753
                });
96fdf90aacd6atagen2026-09-08 08:24 UTC1754
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC1755
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC1756
        self.update_room_subscriptions().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC1757
        Ok(())
96fdf90aacd6atagen2026-09-08 08:24 UTC1758
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1759
96fdf90aacd6atagen2026-09-08 08:24 UTC1760
    fn spawn_room_command(
96fdf90aacd6atagen2026-09-08 08:24 UTC1761
        &mut self,
96fdf90aacd6atagen2026-09-08 08:24 UTC1762
        id: CommandId,
96fdf90aacd6atagen2026-09-08 08:24 UTC1763
        command: RoomCommand,
96fdf90aacd6atagen2026-09-08 08:24 UTC1764
    ) -> Result<bool, CommandError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC1765
        self.spawn_room_command_with_reply(id, command, None)?;
96fdf90aacd6atagen2026-09-08 08:24 UTC1766
        Ok(true)
96fdf90aacd6atagen2026-09-08 08:24 UTC1767
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1768
96fdf90aacd6atagen2026-09-08 08:24 UTC1769
    fn spawn_room_command_with_reply(
96fdf90aacd6atagen2026-09-08 08:24 UTC1770
        &mut self,
96fdf90aacd6atagen2026-09-08 08:24 UTC1771
        id: CommandId,
96fdf90aacd6atagen2026-09-08 08:24 UTC1772
        command: RoomCommand,
96fdf90aacd6atagen2026-09-08 08:24 UTC1773
        reply: Option<oneshot::Sender<Result<OwnedRoomId, CommandResultError>>>,
96fdf90aacd6atagen2026-09-08 08:24 UTC1774
    ) -> Result<(), CommandError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC1775
        let job = RoomJob { id, command, reply };
96fdf90aacd6atagen2026-09-08 08:24 UTC1776
        self.room_commands.try_send(job).map_err(|error| {
96fdf90aacd6atagen2026-09-08 08:24 UTC1777
            let (job, kind, message) = match error {
96fdf90aacd6atagen2026-09-08 08:24 UTC1778
                mpsc::error::TrySendError::Full(job) => (
96fdf90aacd6atagen2026-09-08 08:24 UTC1779
                    job,
96fdf90aacd6atagen2026-09-08 08:24 UTC1780
                    CommandErrorKind::QueueFull,
96fdf90aacd6atagen2026-09-08 08:24 UTC1781
                    "room command queue is full",
96fdf90aacd6atagen2026-09-08 08:24 UTC1782
                ),
96fdf90aacd6atagen2026-09-08 08:24 UTC1783
                mpsc::error::TrySendError::Closed(job) => (
96fdf90aacd6atagen2026-09-08 08:24 UTC1784
                    job,
96fdf90aacd6atagen2026-09-08 08:24 UTC1785
                    CommandErrorKind::Other,
96fdf90aacd6atagen2026-09-08 08:24 UTC1786
                    "room command worker has stopped",
96fdf90aacd6atagen2026-09-08 08:24 UTC1787
                ),
96fdf90aacd6atagen2026-09-08 08:24 UTC1788
            };
96fdf90aacd6atagen2026-09-08 08:24 UTC1789
            let error = CommandError::new(kind, message);
96fdf90aacd6atagen2026-09-08 08:24 UTC1790
            if let Some(reply) = job.reply {
96fdf90aacd6atagen2026-09-08 08:24 UTC1791
                let _ = reply.send(Err(CommandResultError::Failed(CommandFailure {
96fdf90aacd6atagen2026-09-08 08:24 UTC1792
                    id: job.id,
96fdf90aacd6atagen2026-09-08 08:24 UTC1793
                    command: CommandKind::Room,
96fdf90aacd6atagen2026-09-08 08:24 UTC1794
                    kind: error.kind,
96fdf90aacd6atagen2026-09-08 08:24 UTC1795
                    message: error.to_string(),
96fdf90aacd6atagen2026-09-08 08:24 UTC1796
                })));
96fdf90aacd6atagen2026-09-08 08:24 UTC1797
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1798
            error
96fdf90aacd6atagen2026-09-08 08:24 UTC1799
        })
96fdf90aacd6atagen2026-09-08 08:24 UTC1800
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1801
96fdf90aacd6atagen2026-09-08 08:24 UTC1802
    fn route_timeline(
96fdf90aacd6atagen2026-09-08 08:24 UTC1803
        &mut self,
96fdf90aacd6atagen2026-09-08 08:24 UTC1804
        id: CommandId,
96fdf90aacd6atagen2026-09-08 08:24 UTC1805
        command: TimelineCommand,
96fdf90aacd6atagen2026-09-08 08:24 UTC1806
    ) -> Result<bool, CommandError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC1807
        let sender = self
96fdf90aacd6atagen2026-09-08 08:24 UTC1808
            .rooms
fa9114fcf75fatagen2026-09-24 12:51 UTC1809
            .get(command.room_id())
96fdf90aacd6atagen2026-09-08 08:24 UTC1810
            .and_then(RoomActorState::sender)
fa9114fcf75fatagen2026-09-24 12:51 UTC1811
            .cloned();
fa9114fcf75fatagen2026-09-24 12:51 UTC1812
        match (sender, command) {
fa9114fcf75fatagen2026-09-24 12:51 UTC1813
            (Some(sender), command) => {
fa9114fcf75fatagen2026-09-24 12:51 UTC1814
                send_room_actor_request(&sender, RoomActorRequest::Execute { id, command })?;
fa9114fcf75fatagen2026-09-24 12:51 UTC1815
            }
fa9114fcf75fatagen2026-09-24 12:51 UTC1816
            (
fa9114fcf75fatagen2026-09-24 12:51 UTC1817
                None,
fa9114fcf75fatagen2026-09-24 12:51 UTC1818
                TimelineCommand::MarkRead {
fa9114fcf75fatagen2026-09-24 12:51 UTC1819
                    timeline_id,
fa9114fcf75fatagen2026-09-24 12:51 UTC1820
                    event_id,
fa9114fcf75fatagen2026-09-24 12:51 UTC1821
                },
fa9114fcf75fatagen2026-09-24 12:51 UTC1822
            ) => self.spawn_mark_read(id, timeline_id, event_id),
fa9114fcf75fatagen2026-09-24 12:51 UTC1823
            (None, _) => {
fa9114fcf75fatagen2026-09-24 12:51 UTC1824
                return Err(CommandError::new(
fa9114fcf75fatagen2026-09-24 12:51 UTC1825
                    CommandErrorKind::RoomNotOpen,
fa9114fcf75fatagen2026-09-24 12:51 UTC1826
                    "room is not open",
fa9114fcf75fatagen2026-09-24 12:51 UTC1827
                ));
fa9114fcf75fatagen2026-09-24 12:51 UTC1828
            }
fa9114fcf75fatagen2026-09-24 12:51 UTC1829
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC1830
        Ok(true)
96fdf90aacd6atagen2026-09-08 08:24 UTC1831
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1832
96fdf90aacd6atagen2026-09-08 08:24 UTC1833
    async fn close_room(&mut self, id: CommandId, room_id: &RoomId) -> Result<bool, CommandError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC1834
        let Some(state) = self.rooms.get_mut(room_id) else {
96fdf90aacd6atagen2026-09-08 08:24 UTC1835
            return Ok(false);
96fdf90aacd6atagen2026-09-08 08:24 UTC1836
        };
96fdf90aacd6atagen2026-09-08 08:24 UTC1837
        if let RoomActorState::Closing { pending, .. } = state {
96fdf90aacd6atagen2026-09-08 08:24 UTC1838
            if pending.len() >= self.room_command_capacity {
96fdf90aacd6atagen2026-09-08 08:24 UTC1839
                return Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC1840
                    CommandErrorKind::RoomBusy,
96fdf90aacd6atagen2026-09-08 08:24 UTC1841
                    "room lifecycle queue is full",
96fdf90aacd6atagen2026-09-08 08:24 UTC1842
                ));
96fdf90aacd6atagen2026-09-08 08:24 UTC1843
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1844
            pending.push_back(DeferredRoomLifecycle::Close(id));
96fdf90aacd6atagen2026-09-08 08:24 UTC1845
            return Ok(true);
96fdf90aacd6atagen2026-09-08 08:24 UTC1846
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC1847
        let Some(sender) = state.sender().cloned() else {
96fdf90aacd6atagen2026-09-08 08:24 UTC1848
            return Ok(false);
96fdf90aacd6atagen2026-09-08 08:24 UTC1849
        };
96fdf90aacd6atagen2026-09-08 08:24 UTC1850
        let generation = state.generation();
96fdf90aacd6atagen2026-09-08 08:24 UTC1851
        send_room_actor_request(&sender, RoomActorRequest::Close { id })?;
96fdf90aacd6atagen2026-09-08 08:24 UTC1852
        self.rooms.insert(
96fdf90aacd6atagen2026-09-08 08:24 UTC1853
            room_id.to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC1854
            RoomActorState::Closing {
96fdf90aacd6atagen2026-09-08 08:24 UTC1855
                generation,
96fdf90aacd6atagen2026-09-08 08:24 UTC1856
                pending: VecDeque::new(),
96fdf90aacd6atagen2026-09-08 08:24 UTC1857
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC1858
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC1859
        self.update_room_subscriptions().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC1860
        Ok(true)
96fdf90aacd6atagen2026-09-08 08:24 UTC1861
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1862
96fdf90aacd6atagen2026-09-08 08:24 UTC1863
    async fn handle_room_actor_event(&mut self, event: RoomActorEvent) -> Result<(), EngineError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC1864
        match event {
96fdf90aacd6atagen2026-09-08 08:24 UTC1865
            RoomActorEvent::Ready {
96fdf90aacd6atagen2026-09-08 08:24 UTC1866
                room_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC1867
                generation,
96fdf90aacd6atagen2026-09-08 08:24 UTC1868
            } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1869
                let stale = self
96fdf90aacd6atagen2026-09-08 08:24 UTC1870
                    .rooms
96fdf90aacd6atagen2026-09-08 08:24 UTC1871
                    .get(&room_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC1872
                    .is_none_or(|state| state.generation() != generation);
96fdf90aacd6atagen2026-09-08 08:24 UTC1873
                if stale {
96fdf90aacd6atagen2026-09-08 08:24 UTC1874
                    return Ok(());
96fdf90aacd6atagen2026-09-08 08:24 UTC1875
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC1876
                let Some(state) = self.rooms.remove(&room_id) else {
96fdf90aacd6atagen2026-09-08 08:24 UTC1877
                    return Ok(());
96fdf90aacd6atagen2026-09-08 08:24 UTC1878
                };
96fdf90aacd6atagen2026-09-08 08:24 UTC1879
                match state {
96fdf90aacd6atagen2026-09-08 08:24 UTC1880
                    RoomActorState::Opening {
96fdf90aacd6atagen2026-09-08 08:24 UTC1881
                        generation: current,
96fdf90aacd6atagen2026-09-08 08:24 UTC1882
                        sender,
96fdf90aacd6atagen2026-09-08 08:24 UTC1883
                    } if current == generation => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1884
                        self.rooms
96fdf90aacd6atagen2026-09-08 08:24 UTC1885
                            .insert(room_id, RoomActorState::Open { generation, sender });
96fdf90aacd6atagen2026-09-08 08:24 UTC1886
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1887
                    state => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1888
                        self.rooms.insert(room_id, state);
96fdf90aacd6atagen2026-09-08 08:24 UTC1889
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1890
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC1891
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1892
            RoomActorEvent::Exited {
96fdf90aacd6atagen2026-09-08 08:24 UTC1893
                room_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC1894
                generation,
96fdf90aacd6atagen2026-09-08 08:24 UTC1895
            } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1896
                if self
96fdf90aacd6atagen2026-09-08 08:24 UTC1897
                    .rooms
96fdf90aacd6atagen2026-09-08 08:24 UTC1898
                    .get(&room_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC1899
                    .is_none_or(|state| state.generation() != generation)
96fdf90aacd6atagen2026-09-08 08:24 UTC1900
                {
96fdf90aacd6atagen2026-09-08 08:24 UTC1901
                    return Ok(());
96fdf90aacd6atagen2026-09-08 08:24 UTC1902
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC1903
                let state = self
96fdf90aacd6atagen2026-09-08 08:24 UTC1904
                    .rooms
96fdf90aacd6atagen2026-09-08 08:24 UTC1905
                    .remove(&room_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC1906
                    .expect("room generation was checked");
96fdf90aacd6atagen2026-09-08 08:24 UTC1907
                let pending = match state {
96fdf90aacd6atagen2026-09-08 08:24 UTC1908
                    RoomActorState::Closing { pending, .. } => pending,
96fdf90aacd6atagen2026-09-08 08:24 UTC1909
                    RoomActorState::Opening { .. } | RoomActorState::Open { .. } => VecDeque::new(),
96fdf90aacd6atagen2026-09-08 08:24 UTC1910
                };
96fdf90aacd6atagen2026-09-08 08:24 UTC1911
                for operation in pending {
96fdf90aacd6atagen2026-09-08 08:24 UTC1912
                    let (id, result) = match operation {
96fdf90aacd6atagen2026-09-08 08:24 UTC1913
                        DeferredRoomLifecycle::Open(id) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1914
                            (id, self.open_room(id, room_id.clone()).await)
96fdf90aacd6atagen2026-09-08 08:24 UTC1915
                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC1916
                        DeferredRoomLifecycle::Close(id) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1917
                            (id, self.close_room(id, &room_id).await)
96fdf90aacd6atagen2026-09-08 08:24 UTC1918
                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC1919
                    };
96fdf90aacd6atagen2026-09-08 08:24 UTC1920
                    match result {
96fdf90aacd6atagen2026-09-08 08:24 UTC1921
                        Ok(true) => {}
96fdf90aacd6atagen2026-09-08 08:24 UTC1922
                        Ok(false) => emit_command_result(
96fdf90aacd6atagen2026-09-08 08:24 UTC1923
                            &self.port.event_sender(),
96fdf90aacd6atagen2026-09-08 08:24 UTC1924
                            id,
96fdf90aacd6atagen2026-09-08 08:24 UTC1925
                            CommandKind::Room,
96fdf90aacd6atagen2026-09-08 08:24 UTC1926
                            Ok(()),
96fdf90aacd6atagen2026-09-08 08:24 UTC1927
                        )
96fdf90aacd6atagen2026-09-08 08:24 UTC1928
                        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC1929
                        .map_err(|_| EngineError::EventReceiverClosed)?,
96fdf90aacd6atagen2026-09-08 08:24 UTC1930
                        Err(error) => emit_command_result(
96fdf90aacd6atagen2026-09-08 08:24 UTC1931
                            &self.port.event_sender(),
96fdf90aacd6atagen2026-09-08 08:24 UTC1932
                            id,
96fdf90aacd6atagen2026-09-08 08:24 UTC1933
                            CommandKind::Room,
96fdf90aacd6atagen2026-09-08 08:24 UTC1934
                            Err(error),
96fdf90aacd6atagen2026-09-08 08:24 UTC1935
                        )
96fdf90aacd6atagen2026-09-08 08:24 UTC1936
                        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC1937
                        .map_err(|_| EngineError::EventReceiverClosed)?,
96fdf90aacd6atagen2026-09-08 08:24 UTC1938
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1939
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC1940
                self.update_room_subscriptions().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC1941
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC1942
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC1943
        Ok(())
96fdf90aacd6atagen2026-09-08 08:24 UTC1944
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1945
96fdf90aacd6atagen2026-09-08 08:24 UTC1946
    async fn update_room_subscriptions(&self) {
96fdf90aacd6atagen2026-09-08 08:24 UTC1947
        let room_ids = self
96fdf90aacd6atagen2026-09-08 08:24 UTC1948
            .rooms
96fdf90aacd6atagen2026-09-08 08:24 UTC1949
            .iter()
96fdf90aacd6atagen2026-09-08 08:24 UTC1950
            .filter(|(_, state)| !matches!(state, RoomActorState::Closing { .. }))
96fdf90aacd6atagen2026-09-08 08:24 UTC1951
            .map(|(room_id, _)| room_id.clone())
96fdf90aacd6atagen2026-09-08 08:24 UTC1952
            .collect::<Vec<_>>();
96fdf90aacd6atagen2026-09-08 08:24 UTC1953
        let room_refs = room_ids.iter().map(AsRef::as_ref).collect::<Vec<_>>();
96fdf90aacd6atagen2026-09-08 08:24 UTC1954
        self.sync
96fdf90aacd6atagen2026-09-08 08:24 UTC1955
            .room_list_service()
96fdf90aacd6atagen2026-09-08 08:24 UTC1956
            .subscribe_to_rooms(&room_refs)
96fdf90aacd6atagen2026-09-08 08:24 UTC1957
            .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC1958
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC1959
96fdf90aacd6atagen2026-09-08 08:24 UTC1960
    fn spawn_media_fetch(&mut self, command_id: CommandId, media_id: MediaId, size: MediaSize) {
96fdf90aacd6atagen2026-09-08 08:24 UTC1961
        let client = self.client.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC1962
        let media = self.media.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC1963
        let events = self.port.event_sender();
96fdf90aacd6atagen2026-09-08 08:24 UTC1964
        let max_media_bytes = self.max_media_bytes;
96fdf90aacd6atagen2026-09-08 08:24 UTC1965
        let flights = self.media_flights.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC1966
        self.tasks.spawn(
96fdf90aacd6atagen2026-09-08 08:24 UTC1967
            TaskContext::Command {
96fdf90aacd6atagen2026-09-08 08:24 UTC1968
                id: command_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC1969
                kind: CommandKind::Media,
96fdf90aacd6atagen2026-09-08 08:24 UTC1970
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC1971
            async move {
96fdf90aacd6atagen2026-09-08 08:24 UTC1972
                let leader = {
96fdf90aacd6atagen2026-09-08 08:24 UTC1973
                    let mut flights = flights.lock().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC1974
                    let waiters = flights.entry((media_id.clone(), size)).or_default();
96fdf90aacd6atagen2026-09-08 08:24 UTC1975
                    waiters.push(command_id);
96fdf90aacd6atagen2026-09-08 08:24 UTC1976
                    waiters.len() == 1
96fdf90aacd6atagen2026-09-08 08:24 UTC1977
                };
96fdf90aacd6atagen2026-09-08 08:24 UTC1978
                if !leader {
96fdf90aacd6atagen2026-09-08 08:24 UTC1979
                    return;
96fdf90aacd6atagen2026-09-08 08:24 UTC1980
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC1981
                let result = fetch_media(
96fdf90aacd6atagen2026-09-08 08:24 UTC1982
                    client,
96fdf90aacd6atagen2026-09-08 08:24 UTC1983
                    media,
96fdf90aacd6atagen2026-09-08 08:24 UTC1984
                    events.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC1985
                    max_media_bytes,
96fdf90aacd6atagen2026-09-08 08:24 UTC1986
                    media_id.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC1987
                    size,
96fdf90aacd6atagen2026-09-08 08:24 UTC1988
                )
96fdf90aacd6atagen2026-09-08 08:24 UTC1989
                .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC1990
                let waiters = {
96fdf90aacd6atagen2026-09-08 08:24 UTC1991
                    let mut flights = flights.lock().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC1992
                    flights.remove(&(media_id, size)).unwrap_or_default()
96fdf90aacd6atagen2026-09-08 08:24 UTC1993
                };
96fdf90aacd6atagen2026-09-08 08:24 UTC1994
                let result = match result {
96fdf90aacd6atagen2026-09-08 08:24 UTC1995
                    Ok(()) => Ok(()),
96fdf90aacd6atagen2026-09-08 08:24 UTC1996
                    Err(error) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC1997
                        let kind = error.kind;
96fdf90aacd6atagen2026-09-08 08:24 UTC1998
                        let message = error.to_string();
96fdf90aacd6atagen2026-09-08 08:24 UTC1999
                        Err((kind, message))
96fdf90aacd6atagen2026-09-08 08:24 UTC2000
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2001
                };
96fdf90aacd6atagen2026-09-08 08:24 UTC2002
                for waiter in waiters {
96fdf90aacd6atagen2026-09-08 08:24 UTC2003
                    let waiter_result = match &result {
96fdf90aacd6atagen2026-09-08 08:24 UTC2004
                        Ok(()) => Ok(()),
96fdf90aacd6atagen2026-09-08 08:24 UTC2005
                        Err((kind, message)) => Err(CommandError::new(*kind, message.clone())),
96fdf90aacd6atagen2026-09-08 08:24 UTC2006
                    };
96fdf90aacd6atagen2026-09-08 08:24 UTC2007
                    if emit_command_result(&events, waiter, CommandKind::Media, waiter_result)
96fdf90aacd6atagen2026-09-08 08:24 UTC2008
                        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2009
                        .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC2010
                    {
96fdf90aacd6atagen2026-09-08 08:24 UTC2011
                        tracing::debug!(?waiter, "media fetch result was not delivered");
96fdf90aacd6atagen2026-09-08 08:24 UTC2012
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2013
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC2014
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC2015
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC2016
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2017
96fdf90aacd6atagen2026-09-08 08:24 UTC2018
    async fn spawn_media_upload(
96fdf90aacd6atagen2026-09-08 08:24 UTC2019
        &mut self,
96fdf90aacd6atagen2026-09-08 08:24 UTC2020
        command_id: CommandId,
96fdf90aacd6atagen2026-09-08 08:24 UTC2021
        upload: AttachmentUpload,
96fdf90aacd6atagen2026-09-08 08:24 UTC2022
    ) -> Result<(), CommandError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC2023
        let room_id = upload.timeline_id.room_id().clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC2024
        let room = self.client.get_room(&room_id).ok_or_else(|| {
96fdf90aacd6atagen2026-09-08 08:24 UTC2025
            CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC2026
                CommandErrorKind::RoomUnknown,
96fdf90aacd6atagen2026-09-08 08:24 UTC2027
                "room is not available in the local store",
96fdf90aacd6atagen2026-09-08 08:24 UTC2028
            )
96fdf90aacd6atagen2026-09-08 08:24 UTC2029
        })?;
96fdf90aacd6atagen2026-09-08 08:24 UTC2030
        if self
96fdf90aacd6atagen2026-09-08 08:24 UTC2031
            .upload_watchers
96fdf90aacd6atagen2026-09-08 08:24 UTC2032
            .get(&room_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC2033
            .is_some_and(|(watcher, _)| watcher.is_finished())
96fdf90aacd6atagen2026-09-08 08:24 UTC2034
        {
96fdf90aacd6atagen2026-09-08 08:24 UTC2035
            self.upload_watchers.remove(&room_id);
96fdf90aacd6atagen2026-09-08 08:24 UTC2036
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC2037
        let ready = if let Some((_, ready)) = self.upload_watchers.get(&room_id) {
96fdf90aacd6atagen2026-09-08 08:24 UTC2038
            ready.clone()
96fdf90aacd6atagen2026-09-08 08:24 UTC2039
        } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC2040
            let (ready_sender, ready) = watch::channel(None);
96fdf90aacd6atagen2026-09-08 08:24 UTC2041
            let watched_room_id = room_id.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC2042
            let progress_room_id = room_id.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC2043
            let queued_room = room.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC2044
            let events = self.port.event_sender();
96fdf90aacd6atagen2026-09-08 08:24 UTC2045
            let watcher =
96fdf90aacd6atagen2026-09-08 08:24 UTC2046
                self.tasks
96fdf90aacd6atagen2026-09-08 08:24 UTC2047
                    .spawn(TaskContext::Watcher("media upload watcher"), async move {
96fdf90aacd6atagen2026-09-08 08:24 UTC2048
                        let (_, mut updates) = match queued_room.send_queue().subscribe().await {
96fdf90aacd6atagen2026-09-08 08:24 UTC2049
                            Ok(subscription) => subscription,
96fdf90aacd6atagen2026-09-08 08:24 UTC2050
                            Err(source) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC2051
                                tracing::warn!(room_id = %progress_room_id, error = %source, "reading media send queue failed");
96fdf90aacd6atagen2026-09-08 08:24 UTC2052
                                ready_sender.send_replace(Some(Err(CommandErrorKind::Store)));
96fdf90aacd6atagen2026-09-08 08:24 UTC2053
                                return;
96fdf90aacd6atagen2026-09-08 08:24 UTC2054
                            }
96fdf90aacd6atagen2026-09-08 08:24 UTC2055
                        };
96fdf90aacd6atagen2026-09-08 08:24 UTC2056
                        ready_sender.send_replace(Some(Ok(())));
96fdf90aacd6atagen2026-09-08 08:24 UTC2057
                        loop {
96fdf90aacd6atagen2026-09-08 08:24 UTC2058
                            match updates.recv().await {
96fdf90aacd6atagen2026-09-08 08:24 UTC2059
                                Ok(RoomSendQueueUpdate::MediaUpload {
96fdf90aacd6atagen2026-09-08 08:24 UTC2060
                                    related_to,
96fdf90aacd6atagen2026-09-08 08:24 UTC2061
                                    file,
96fdf90aacd6atagen2026-09-08 08:24 UTC2062
                                    progress,
96fdf90aacd6atagen2026-09-08 08:24 UTC2063
                                    ..
96fdf90aacd6atagen2026-09-08 08:24 UTC2064
                                }) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC2065
                                    if events
96fdf90aacd6atagen2026-09-08 08:24 UTC2066
                                        .send(Event::MediaUploadProgress {
96fdf90aacd6atagen2026-09-08 08:24 UTC2067
                                            room_id: progress_room_id.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC2068
                                            transaction_id: related_to,
96fdf90aacd6atagen2026-09-08 08:24 UTC2069
                                            uploaded_bytes: progress.current as u64,
96fdf90aacd6atagen2026-09-08 08:24 UTC2070
                                            total_bytes: progress.total as u64,
96fdf90aacd6atagen2026-09-08 08:24 UTC2071
                                            complete: file.is_some()
96fdf90aacd6atagen2026-09-08 08:24 UTC2072
                                                && progress.current == progress.total,
96fdf90aacd6atagen2026-09-08 08:24 UTC2073
                                        })
96fdf90aacd6atagen2026-09-08 08:24 UTC2074
                                        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2075
                                        .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC2076
                                    {
96fdf90aacd6atagen2026-09-08 08:24 UTC2077
                                        break;
96fdf90aacd6atagen2026-09-08 08:24 UTC2078
                                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2079
                                }
96fdf90aacd6atagen2026-09-08 08:24 UTC2080
                                Ok(RoomSendQueueUpdate::SendError {
96fdf90aacd6atagen2026-09-08 08:24 UTC2081
                                    transaction_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC2082
                                    error,
96fdf90aacd6atagen2026-09-08 08:24 UTC2083
                                    is_recoverable,
96fdf90aacd6atagen2026-09-08 08:24 UTC2084
                                }) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC2085
                                    let is_attachment = queued_room
96fdf90aacd6atagen2026-09-08 08:24 UTC2086
                                        .send_queue()
96fdf90aacd6atagen2026-09-08 08:24 UTC2087
                                        .subscribe()
96fdf90aacd6atagen2026-09-08 08:24 UTC2088
                                        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2089
                                        .is_ok_and(|(echoes, _)| {
96fdf90aacd6atagen2026-09-08 08:24 UTC2090
                                            echoes.iter().any(|echo| {
96fdf90aacd6atagen2026-09-08 08:24 UTC2091
                                                echo.transaction_id == transaction_id
96fdf90aacd6atagen2026-09-08 08:24 UTC2092
                                                    && is_attachment_echo(&echo.content)
96fdf90aacd6atagen2026-09-08 08:24 UTC2093
                                            })
96fdf90aacd6atagen2026-09-08 08:24 UTC2094
                                        });
96fdf90aacd6atagen2026-09-08 08:24 UTC2095
                                    if is_attachment
96fdf90aacd6atagen2026-09-08 08:24 UTC2096
                                        && events
96fdf90aacd6atagen2026-09-08 08:24 UTC2097
                                            .send(Event::MediaUploadFailed {
96fdf90aacd6atagen2026-09-08 08:24 UTC2098
                                                room_id: progress_room_id.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC2099
                                                transaction_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC2100
                                                kind: classify_sdk_error(&error),
96fdf90aacd6atagen2026-09-08 08:24 UTC2101
                                                recoverable: is_recoverable,
96fdf90aacd6atagen2026-09-08 08:24 UTC2102
                                            })
96fdf90aacd6atagen2026-09-08 08:24 UTC2103
                                            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2104
                                            .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC2105
                                    {
96fdf90aacd6atagen2026-09-08 08:24 UTC2106
                                        break;
96fdf90aacd6atagen2026-09-08 08:24 UTC2107
                                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2108
                                }
96fdf90aacd6atagen2026-09-08 08:24 UTC2109
                                Ok(_)
96fdf90aacd6atagen2026-09-08 08:24 UTC2110
                                | Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => {}
96fdf90aacd6atagen2026-09-08 08:24 UTC2111
                                Err(tokio::sync::broadcast::error::RecvError::Closed) => break,
96fdf90aacd6atagen2026-09-08 08:24 UTC2112
                            }
96fdf90aacd6atagen2026-09-08 08:24 UTC2113
                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC2114
                    });
96fdf90aacd6atagen2026-09-08 08:24 UTC2115
            self.upload_watchers
96fdf90aacd6atagen2026-09-08 08:24 UTC2116
                .insert(watched_room_id, (watcher, ready.clone()));
96fdf90aacd6atagen2026-09-08 08:24 UTC2117
            ready
96fdf90aacd6atagen2026-09-08 08:24 UTC2118
        };
96fdf90aacd6atagen2026-09-08 08:24 UTC2119
        let max_media_bytes = self.max_media_bytes;
96fdf90aacd6atagen2026-09-08 08:24 UTC2120
        let events = self.port.event_sender();
96fdf90aacd6atagen2026-09-08 08:24 UTC2121
        self.tasks.spawn(
96fdf90aacd6atagen2026-09-08 08:24 UTC2122
            TaskContext::Command {
96fdf90aacd6atagen2026-09-08 08:24 UTC2123
                id: command_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC2124
                kind: CommandKind::Media,
96fdf90aacd6atagen2026-09-08 08:24 UTC2125
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC2126
            async move {
96fdf90aacd6atagen2026-09-08 08:24 UTC2127
                let mut ready = ready;
96fdf90aacd6atagen2026-09-08 08:24 UTC2128
                let result = async {
96fdf90aacd6atagen2026-09-08 08:24 UTC2129
                    loop {
96fdf90aacd6atagen2026-09-08 08:24 UTC2130
                        if let Some(result) = *ready.borrow_and_update() {
96fdf90aacd6atagen2026-09-08 08:24 UTC2131
                            result.map_err(|kind| {
96fdf90aacd6atagen2026-09-08 08:24 UTC2132
                                CommandError::new(kind, "reading media send queue failed")
96fdf90aacd6atagen2026-09-08 08:24 UTC2133
                            })?;
96fdf90aacd6atagen2026-09-08 08:24 UTC2134
                            break;
96fdf90aacd6atagen2026-09-08 08:24 UTC2135
                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC2136
                        ready.changed().await.map_err(|_| {
96fdf90aacd6atagen2026-09-08 08:24 UTC2137
                            CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC2138
                                CommandErrorKind::Store,
96fdf90aacd6atagen2026-09-08 08:24 UTC2139
                                "media send queue watcher stopped",
96fdf90aacd6atagen2026-09-08 08:24 UTC2140
                            )
96fdf90aacd6atagen2026-09-08 08:24 UTC2141
                        })?;
96fdf90aacd6atagen2026-09-08 08:24 UTC2142
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2143
                    queue_media_upload(room, upload, max_media_bytes).await
96fdf90aacd6atagen2026-09-08 08:24 UTC2144
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC2145
                .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC2146
                if emit_command_result(&events, command_id, CommandKind::Media, result)
96fdf90aacd6atagen2026-09-08 08:24 UTC2147
                    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2148
                    .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC2149
                {
96fdf90aacd6atagen2026-09-08 08:24 UTC2150
                    tracing::debug!(?command_id, "media upload result was not delivered");
96fdf90aacd6atagen2026-09-08 08:24 UTC2151
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC2152
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC2153
        );
96fdf90aacd6atagen2026-09-08 08:24 UTC2154
        Ok(())
96fdf90aacd6atagen2026-09-08 08:24 UTC2155
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2156
96fdf90aacd6atagen2026-09-08 08:24 UTC2157
    async fn shutdown(&mut self) {
96fdf90aacd6atagen2026-09-08 08:24 UTC2158
        self.tasks.shutdown().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC2159
        self.verification.lock().await.stop().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC2160
        self.rooms.clear();
96fdf90aacd6atagen2026-09-08 08:24 UTC2161
        self.upload_watchers.clear();
fa9114fcf75fatagen2026-09-24 12:51 UTC2162
        let mut media = self.media.lock().await;
fa9114fcf75fatagen2026-09-24 12:51 UTC2163
        media.entries.clear();
fa9114fcf75fatagen2026-09-24 12:51 UTC2164
        media.order.clear();
fa9114fcf75fatagen2026-09-24 12:51 UTC2165
        drop(media);
96fdf90aacd6atagen2026-09-08 08:24 UTC2166
        self.sync.stop().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC2167
        self.port
96fdf90aacd6atagen2026-09-08 08:24 UTC2168
            .connection_sender()
96fdf90aacd6atagen2026-09-08 08:24 UTC2169
            .send_replace(ConnectionState::Stopped);
96fdf90aacd6atagen2026-09-08 08:24 UTC2170
        let _ = self
96fdf90aacd6atagen2026-09-08 08:24 UTC2171
            .port
96fdf90aacd6atagen2026-09-08 08:24 UTC2172
            .send(Event::ConnectionChanged(ConnectionState::Stopped))
96fdf90aacd6atagen2026-09-08 08:24 UTC2173
            .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC2174
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2175
}
96fdf90aacd6atagen2026-09-08 08:24 UTC2176
96fdf90aacd6atagen2026-09-08 08:24 UTC2177
async fn mark_read(
96fdf90aacd6atagen2026-09-08 08:24 UTC2178
    client: &Client,
96fdf90aacd6atagen2026-09-08 08:24 UTC2179
    timeline_id: &TimelineId,
96fdf90aacd6atagen2026-09-08 08:24 UTC2180
    event_id: OwnedEventId,
96fdf90aacd6atagen2026-09-08 08:24 UTC2181
) -> Result<(), CommandError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC2182
    let room = client
96fdf90aacd6atagen2026-09-08 08:24 UTC2183
        .get_room(timeline_id.room_id())
96fdf90aacd6atagen2026-09-08 08:24 UTC2184
        .ok_or_else(room_unknown)?;
96fdf90aacd6atagen2026-09-08 08:24 UTC2185
    let receipt_thread = match timeline_id {
96fdf90aacd6atagen2026-09-08 08:24 UTC2186
        TimelineId::Room(_) => ReceiptThread::Main,
96fdf90aacd6atagen2026-09-08 08:24 UTC2187
        TimelineId::Thread { root_event_id, .. } => ReceiptThread::Thread(root_event_id.clone()),
96fdf90aacd6atagen2026-09-08 08:24 UTC2188
    };
96fdf90aacd6atagen2026-09-08 08:24 UTC2189
    room.send_single_receipt(ReceiptType::Read, receipt_thread, event_id.clone())
96fdf90aacd6atagen2026-09-08 08:24 UTC2190
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2191
        .map_err(|source| CommandError::from_sdk("sending read receipt failed", source))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC2192
    if matches!(timeline_id, TimelineId::Room(_)) {
96fdf90aacd6atagen2026-09-08 08:24 UTC2193
        room.send_single_receipt(ReceiptType::FullyRead, ReceiptThread::Unthreaded, event_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC2194
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2195
            .map_err(|source| {
96fdf90aacd6atagen2026-09-08 08:24 UTC2196
                CommandError::from_sdk("sending fully read receipt failed", source)
96fdf90aacd6atagen2026-09-08 08:24 UTC2197
            })?;
96fdf90aacd6atagen2026-09-08 08:24 UTC2198
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2199
    Ok(())
96fdf90aacd6atagen2026-09-08 08:24 UTC2200
}
96fdf90aacd6atagen2026-09-08 08:24 UTC2201
96fdf90aacd6atagen2026-09-08 08:24 UTC2202
async fn set_typing(client: &Client, room_id: &RoomId, typing: bool) -> Result<(), CommandError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC2203
    let room = client.get_room(room_id).ok_or_else(room_unknown)?;
96fdf90aacd6atagen2026-09-08 08:24 UTC2204
    room.typing_notice(typing)
96fdf90aacd6atagen2026-09-08 08:24 UTC2205
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2206
        .map_err(|source| CommandError::from_sdk("typing update failed", source))
96fdf90aacd6atagen2026-09-08 08:24 UTC2207
}
96fdf90aacd6atagen2026-09-08 08:24 UTC2208
96fdf90aacd6atagen2026-09-08 08:24 UTC2209
fn is_attachment_echo(content: &LocalEchoContent) -> bool {
96fdf90aacd6atagen2026-09-08 08:24 UTC2210
    let LocalEchoContent::Event {
96fdf90aacd6atagen2026-09-08 08:24 UTC2211
        serialized_event, ..
96fdf90aacd6atagen2026-09-08 08:24 UTC2212
    } = content
96fdf90aacd6atagen2026-09-08 08:24 UTC2213
    else {
96fdf90aacd6atagen2026-09-08 08:24 UTC2214
        return false;
96fdf90aacd6atagen2026-09-08 08:24 UTC2215
    };
96fdf90aacd6atagen2026-09-08 08:24 UTC2216
    matches!(
96fdf90aacd6atagen2026-09-08 08:24 UTC2217
        serialized_event.deserialize(),
96fdf90aacd6atagen2026-09-08 08:24 UTC2218
        Ok(AnyMessageLikeEventContent::RoomMessage(content))
96fdf90aacd6atagen2026-09-08 08:24 UTC2219
            if matches!(
96fdf90aacd6atagen2026-09-08 08:24 UTC2220
                content.msgtype,
96fdf90aacd6atagen2026-09-08 08:24 UTC2221
                MessageType::Audio(_) | MessageType::File(_) | MessageType::Image(_) | MessageType::Video(_)
96fdf90aacd6atagen2026-09-08 08:24 UTC2222
            )
96fdf90aacd6atagen2026-09-08 08:24 UTC2223
    )
96fdf90aacd6atagen2026-09-08 08:24 UTC2224
}
96fdf90aacd6atagen2026-09-08 08:24 UTC2225
96fdf90aacd6atagen2026-09-08 08:24 UTC2226
async fn send_handle(
96fdf90aacd6atagen2026-09-08 08:24 UTC2227
    client: &Client,
96fdf90aacd6atagen2026-09-08 08:24 UTC2228
    room_id: &RoomId,
96fdf90aacd6atagen2026-09-08 08:24 UTC2229
    transaction_id: &matrix_sdk::ruma::TransactionId,
96fdf90aacd6atagen2026-09-08 08:24 UTC2230
) -> Result<SendHandle, CommandError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC2231
    let room = client.get_room(room_id).ok_or_else(room_unknown)?;
96fdf90aacd6atagen2026-09-08 08:24 UTC2232
    let (echoes, _) = room.send_queue().subscribe().await.map_err(|source| {
96fdf90aacd6atagen2026-09-08 08:24 UTC2233
        CommandError::with_source(CommandErrorKind::Store, "reading send queue failed", source)
96fdf90aacd6atagen2026-09-08 08:24 UTC2234
    })?;
96fdf90aacd6atagen2026-09-08 08:24 UTC2235
    echoes
96fdf90aacd6atagen2026-09-08 08:24 UTC2236
        .into_iter()
96fdf90aacd6atagen2026-09-08 08:24 UTC2237
        .find_map(|echo| {
96fdf90aacd6atagen2026-09-08 08:24 UTC2238
            if echo.transaction_id.as_str() != transaction_id.as_str() {
96fdf90aacd6atagen2026-09-08 08:24 UTC2239
                return None;
96fdf90aacd6atagen2026-09-08 08:24 UTC2240
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC2241
            match echo.content {
96fdf90aacd6atagen2026-09-08 08:24 UTC2242
                LocalEchoContent::Event { send_handle, .. } => Some(send_handle),
96fdf90aacd6atagen2026-09-08 08:24 UTC2243
                _ => None,
96fdf90aacd6atagen2026-09-08 08:24 UTC2244
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC2245
        })
96fdf90aacd6atagen2026-09-08 08:24 UTC2246
        .ok_or_else(|| {
96fdf90aacd6atagen2026-09-08 08:24 UTC2247
            CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC2248
                CommandErrorKind::Other,
96fdf90aacd6atagen2026-09-08 08:24 UTC2249
                "send is no longer pending or is not a message",
96fdf90aacd6atagen2026-09-08 08:24 UTC2250
            )
96fdf90aacd6atagen2026-09-08 08:24 UTC2251
        })
96fdf90aacd6atagen2026-09-08 08:24 UTC2252
}
96fdf90aacd6atagen2026-09-08 08:24 UTC2253
96fdf90aacd6atagen2026-09-08 08:24 UTC2254
async fn cancel_send(
96fdf90aacd6atagen2026-09-08 08:24 UTC2255
    client: &Client,
96fdf90aacd6atagen2026-09-08 08:24 UTC2256
    room_id: &RoomId,
96fdf90aacd6atagen2026-09-08 08:24 UTC2257
    transaction_id: &matrix_sdk::ruma::TransactionId,
96fdf90aacd6atagen2026-09-08 08:24 UTC2258
) -> Result<(), CommandError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC2259
    let handle = send_handle(client, room_id, transaction_id).await?;
96fdf90aacd6atagen2026-09-08 08:24 UTC2260
    match handle.abort().await {
96fdf90aacd6atagen2026-09-08 08:24 UTC2261
        Ok(true) => Ok(()),
96fdf90aacd6atagen2026-09-08 08:24 UTC2262
        Ok(false) => Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC2263
            CommandErrorKind::Other,
96fdf90aacd6atagen2026-09-08 08:24 UTC2264
            "send could not be cancelled because it has already been sent",
96fdf90aacd6atagen2026-09-08 08:24 UTC2265
        )),
96fdf90aacd6atagen2026-09-08 08:24 UTC2266
        Err(source) => Err(CommandError::with_source(
96fdf90aacd6atagen2026-09-08 08:24 UTC2267
            CommandErrorKind::Store,
96fdf90aacd6atagen2026-09-08 08:24 UTC2268
            "cancelling send failed",
96fdf90aacd6atagen2026-09-08 08:24 UTC2269
            source,
96fdf90aacd6atagen2026-09-08 08:24 UTC2270
        )),
96fdf90aacd6atagen2026-09-08 08:24 UTC2271
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2272
}
96fdf90aacd6atagen2026-09-08 08:24 UTC2273
96fdf90aacd6atagen2026-09-08 08:24 UTC2274
async fn retry_send(
96fdf90aacd6atagen2026-09-08 08:24 UTC2275
    client: &Client,
96fdf90aacd6atagen2026-09-08 08:24 UTC2276
    room_id: &RoomId,
96fdf90aacd6atagen2026-09-08 08:24 UTC2277
    transaction_id: &matrix_sdk::ruma::TransactionId,
96fdf90aacd6atagen2026-09-08 08:24 UTC2278
) -> Result<(), CommandError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC2279
    let handle = send_handle(client, room_id, transaction_id).await?;
96fdf90aacd6atagen2026-09-08 08:24 UTC2280
    handle.unwedge().await.map_err(|source| {
96fdf90aacd6atagen2026-09-08 08:24 UTC2281
        CommandError::with_source(CommandErrorKind::Store, "retrying send failed", source)
96fdf90aacd6atagen2026-09-08 08:24 UTC2282
    })?;
fa9114fcf75fatagen2026-09-24 12:51 UTC2283
    if let Some(room) = client.get_room(room_id) {
fa9114fcf75fatagen2026-09-24 12:51 UTC2284
        room.send_queue().set_enabled(true);
fa9114fcf75fatagen2026-09-24 12:51 UTC2285
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2286
    Ok(())
96fdf90aacd6atagen2026-09-08 08:24 UTC2287
}
96fdf90aacd6atagen2026-09-08 08:24 UTC2288
96fdf90aacd6atagen2026-09-08 08:24 UTC2289
fn send_room_actor_request(
96fdf90aacd6atagen2026-09-08 08:24 UTC2290
    sender: &mpsc::Sender<RoomActorRequest>,
96fdf90aacd6atagen2026-09-08 08:24 UTC2291
    request: RoomActorRequest,
96fdf90aacd6atagen2026-09-08 08:24 UTC2292
) -> Result<(), CommandError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC2293
    sender.try_send(request).map_err(|error| match error {
96fdf90aacd6atagen2026-09-08 08:24 UTC2294
        mpsc::error::TrySendError::Full(_) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC2295
            CommandError::new(CommandErrorKind::RoomBusy, "room command queue is full")
96fdf90aacd6atagen2026-09-08 08:24 UTC2296
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC2297
        mpsc::error::TrySendError::Closed(_) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC2298
            CommandError::new(CommandErrorKind::RoomNotOpen, "room is not open")
96fdf90aacd6atagen2026-09-08 08:24 UTC2299
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC2300
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC2301
}
96fdf90aacd6atagen2026-09-08 08:24 UTC2302
96fdf90aacd6atagen2026-09-08 08:24 UTC2303
fn domain_queue_error<T>(error: mpsc::error::TrySendError<T>, domain: &str) -> CommandError {
96fdf90aacd6atagen2026-09-08 08:24 UTC2304
    match error {
96fdf90aacd6atagen2026-09-08 08:24 UTC2305
        mpsc::error::TrySendError::Full(_) => CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC2306
            CommandErrorKind::QueueFull,
96fdf90aacd6atagen2026-09-08 08:24 UTC2307
            format!("{domain} command queue is full"),
96fdf90aacd6atagen2026-09-08 08:24 UTC2308
        ),
96fdf90aacd6atagen2026-09-08 08:24 UTC2309
        mpsc::error::TrySendError::Closed(_) => CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC2310
            CommandErrorKind::Other,
96fdf90aacd6atagen2026-09-08 08:24 UTC2311
            format!("{domain} command worker has stopped"),
96fdf90aacd6atagen2026-09-08 08:24 UTC2312
        ),
96fdf90aacd6atagen2026-09-08 08:24 UTC2313
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2314
}
96fdf90aacd6atagen2026-09-08 08:24 UTC2315
96fdf90aacd6atagen2026-09-08 08:24 UTC2316
async fn run_room_command_worker(
96fdf90aacd6atagen2026-09-08 08:24 UTC2317
    client: Client,
96fdf90aacd6atagen2026-09-08 08:24 UTC2318
    events: EventSender,
96fdf90aacd6atagen2026-09-08 08:24 UTC2319
    mut commands: mpsc::Receiver<RoomJob>,
96fdf90aacd6atagen2026-09-08 08:24 UTC2320
    queue_limit: usize,
96fdf90aacd6atagen2026-09-08 08:24 UTC2321
) {
96fdf90aacd6atagen2026-09-08 08:24 UTC2322
    let mut queues = HashMap::<RoomMutationKey, VecDeque<RoomJob>>::new();
96fdf90aacd6atagen2026-09-08 08:24 UTC2323
    let mut running = HashSet::<RoomMutationKey>::new();
96fdf90aacd6atagen2026-09-08 08:24 UTC2324
    let mut tasks = JoinSet::<RoomJobCompletion>::new();
96fdf90aacd6atagen2026-09-08 08:24 UTC2325
    let mut receiving = true;
96fdf90aacd6atagen2026-09-08 08:24 UTC2326
    let mut queued = 0_usize;
96fdf90aacd6atagen2026-09-08 08:24 UTC2327
    while receiving || !tasks.is_empty() {
96fdf90aacd6atagen2026-09-08 08:24 UTC2328
        tokio::select! {
96fdf90aacd6atagen2026-09-08 08:24 UTC2329
            biased;
96fdf90aacd6atagen2026-09-08 08:24 UTC2330
            completed = tasks.join_next(), if !tasks.is_empty() => {
96fdf90aacd6atagen2026-09-08 08:24 UTC2331
                let completion = match completed {
96fdf90aacd6atagen2026-09-08 08:24 UTC2332
                    Some(Ok(completion)) => completion,
96fdf90aacd6atagen2026-09-08 08:24 UTC2333
                    Some(Err(error)) => panic!("room command task failed: {error}"),
96fdf90aacd6atagen2026-09-08 08:24 UTC2334
                    None => continue,
96fdf90aacd6atagen2026-09-08 08:24 UTC2335
                };
96fdf90aacd6atagen2026-09-08 08:24 UTC2336
                let key = completion.key.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC2337
                emit_room_job_result(&events, completion).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC2338
                let next = queues.get_mut(&key).and_then(VecDeque::pop_front);
96fdf90aacd6atagen2026-09-08 08:24 UTC2339
                if next.is_some() {
96fdf90aacd6atagen2026-09-08 08:24 UTC2340
                    queued -= 1;
96fdf90aacd6atagen2026-09-08 08:24 UTC2341
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC2342
                if queues.get(&key).is_some_and(VecDeque::is_empty) {
96fdf90aacd6atagen2026-09-08 08:24 UTC2343
                    queues.remove(&key);
96fdf90aacd6atagen2026-09-08 08:24 UTC2344
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC2345
                if let Some(job) = next {
96fdf90aacd6atagen2026-09-08 08:24 UTC2346
                    spawn_room_job(&mut tasks, client.clone(), key, job);
96fdf90aacd6atagen2026-09-08 08:24 UTC2347
                } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC2348
                    running.remove(&key);
96fdf90aacd6atagen2026-09-08 08:24 UTC2349
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC2350
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC2351
            job = commands.recv(), if receiving => {
96fdf90aacd6atagen2026-09-08 08:24 UTC2352
                match job {
96fdf90aacd6atagen2026-09-08 08:24 UTC2353
                    Some(job) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC2354
                        let key = room_mutation_key(job.id, &job.command);
96fdf90aacd6atagen2026-09-08 08:24 UTC2355
                        if running.insert(key.clone()) {
96fdf90aacd6atagen2026-09-08 08:24 UTC2356
                            spawn_room_job(&mut tasks, client.clone(), key, job);
96fdf90aacd6atagen2026-09-08 08:24 UTC2357
                        } else if queued < queue_limit {
96fdf90aacd6atagen2026-09-08 08:24 UTC2358
                            queues.entry(key).or_default().push_back(job);
96fdf90aacd6atagen2026-09-08 08:24 UTC2359
                            queued += 1;
96fdf90aacd6atagen2026-09-08 08:24 UTC2360
                        } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC2361
                            let failure = CommandFailure {
96fdf90aacd6atagen2026-09-08 08:24 UTC2362
                                id: job.id,
96fdf90aacd6atagen2026-09-08 08:24 UTC2363
                                command: CommandKind::Room,
96fdf90aacd6atagen2026-09-08 08:24 UTC2364
                                kind: CommandErrorKind::QueueFull,
96fdf90aacd6atagen2026-09-08 08:24 UTC2365
                                message: "room command queue is full".to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC2366
                            };
96fdf90aacd6atagen2026-09-08 08:24 UTC2367
                            if let Some(reply) = job.reply {
96fdf90aacd6atagen2026-09-08 08:24 UTC2368
                                let _ = reply.send(Err(CommandResultError::Failed(failure)));
96fdf90aacd6atagen2026-09-08 08:24 UTC2369
                            } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC2370
                                let _ = events.send(Event::CommandFailed(failure)).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC2371
                            }
96fdf90aacd6atagen2026-09-08 08:24 UTC2372
                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC2373
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2374
                    None => receiving = false,
96fdf90aacd6atagen2026-09-08 08:24 UTC2375
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC2376
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC2377
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC2378
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2379
}
96fdf90aacd6atagen2026-09-08 08:24 UTC2380
96fdf90aacd6atagen2026-09-08 08:24 UTC2381
fn spawn_room_job(
96fdf90aacd6atagen2026-09-08 08:24 UTC2382
    tasks: &mut JoinSet<RoomJobCompletion>,
96fdf90aacd6atagen2026-09-08 08:24 UTC2383
    client: Client,
96fdf90aacd6atagen2026-09-08 08:24 UTC2384
    key: RoomMutationKey,
96fdf90aacd6atagen2026-09-08 08:24 UTC2385
    job: RoomJob,
96fdf90aacd6atagen2026-09-08 08:24 UTC2386
) {
96fdf90aacd6atagen2026-09-08 08:24 UTC2387
    tasks.spawn(async move {
96fdf90aacd6atagen2026-09-08 08:24 UTC2388
        let RoomJob { id, command, reply } = job;
96fdf90aacd6atagen2026-09-08 08:24 UTC2389
        let result = AssertUnwindSafe(execute_room_command(&client, command))
96fdf90aacd6atagen2026-09-08 08:24 UTC2390
            .catch_unwind()
96fdf90aacd6atagen2026-09-08 08:24 UTC2391
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2392
            .unwrap_or_else(|_| {
96fdf90aacd6atagen2026-09-08 08:24 UTC2393
                Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC2394
                    CommandErrorKind::Other,
96fdf90aacd6atagen2026-09-08 08:24 UTC2395
                    "room command panicked",
96fdf90aacd6atagen2026-09-08 08:24 UTC2396
                ))
96fdf90aacd6atagen2026-09-08 08:24 UTC2397
            });
96fdf90aacd6atagen2026-09-08 08:24 UTC2398
        RoomJobCompletion {
96fdf90aacd6atagen2026-09-08 08:24 UTC2399
            key,
96fdf90aacd6atagen2026-09-08 08:24 UTC2400
            id,
96fdf90aacd6atagen2026-09-08 08:24 UTC2401
            reply,
96fdf90aacd6atagen2026-09-08 08:24 UTC2402
            result,
96fdf90aacd6atagen2026-09-08 08:24 UTC2403
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC2404
    });
96fdf90aacd6atagen2026-09-08 08:24 UTC2405
}
96fdf90aacd6atagen2026-09-08 08:24 UTC2406
96fdf90aacd6atagen2026-09-08 08:24 UTC2407
fn room_mutation_key(id: CommandId, command: &RoomCommand) -> RoomMutationKey {
96fdf90aacd6atagen2026-09-08 08:24 UTC2408
    match command {
96fdf90aacd6atagen2026-09-08 08:24 UTC2409
        RoomCommand::Join { room, .. } if room.is_room_id() => RoomMutationKey::Room(
96fdf90aacd6atagen2026-09-08 08:24 UTC2410
            OwnedRoomId::try_from(room.clone()).expect("room ID variant was checked"),
96fdf90aacd6atagen2026-09-08 08:24 UTC2411
        ),
96fdf90aacd6atagen2026-09-08 08:24 UTC2412
        RoomCommand::Join { room, .. } => RoomMutationKey::Target(room.to_string()),
96fdf90aacd6atagen2026-09-08 08:24 UTC2413
        RoomCommand::Leave(room_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC2414
        | RoomCommand::Forget(room_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC2415
        | RoomCommand::AcceptInvite(room_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC2416
        | RoomCommand::RejectInvite(room_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC2417
        | RoomCommand::InviteUser { room_id, .. }
96fdf90aacd6atagen2026-09-08 08:24 UTC2418
        | RoomCommand::KickUser { room_id, .. }
96fdf90aacd6atagen2026-09-08 08:24 UTC2419
        | RoomCommand::BanUser { room_id, .. }
96fdf90aacd6atagen2026-09-08 08:24 UTC2420
        | RoomCommand::UnbanUser { room_id, .. }
96fdf90aacd6atagen2026-09-08 08:24 UTC2421
        | RoomCommand::SetPowerLevels { room_id, .. }
96fdf90aacd6atagen2026-09-08 08:24 UTC2422
        | RoomCommand::SetUserPowerLevel { room_id, .. } => RoomMutationKey::Room(room_id.clone()),
96fdf90aacd6atagen2026-09-08 08:24 UTC2423
        RoomCommand::CreateDirect(user_id) => RoomMutationKey::Direct(user_id.clone()),
96fdf90aacd6atagen2026-09-08 08:24 UTC2424
        _ => RoomMutationKey::Independent(id),
96fdf90aacd6atagen2026-09-08 08:24 UTC2425
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2426
}
96fdf90aacd6atagen2026-09-08 08:24 UTC2427
96fdf90aacd6atagen2026-09-08 08:24 UTC2428
async fn emit_room_job_result(events: &EventSender, completion: RoomJobCompletion) {
96fdf90aacd6atagen2026-09-08 08:24 UTC2429
    let RoomJobCompletion {
96fdf90aacd6atagen2026-09-08 08:24 UTC2430
        id, reply, result, ..
96fdf90aacd6atagen2026-09-08 08:24 UTC2431
    } = completion;
96fdf90aacd6atagen2026-09-08 08:24 UTC2432
    match result {
96fdf90aacd6atagen2026-09-08 08:24 UTC2433
        Ok(Some(room_id)) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC2434
            if let Some(reply) = reply {
96fdf90aacd6atagen2026-09-08 08:24 UTC2435
                let _ = reply.send(Ok(room_id.clone()));
96fdf90aacd6atagen2026-09-08 08:24 UTC2436
            } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC2437
                let _ = emit_command_result_with_room(events, id, room_id).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC2438
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC2439
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC2440
        Ok(None) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC2441
            if let Some(reply) = reply {
96fdf90aacd6atagen2026-09-08 08:24 UTC2442
                let _ = reply.send(Err(CommandResultError::Failed(CommandFailure {
96fdf90aacd6atagen2026-09-08 08:24 UTC2443
                    id,
96fdf90aacd6atagen2026-09-08 08:24 UTC2444
                    command: CommandKind::Room,
96fdf90aacd6atagen2026-09-08 08:24 UTC2445
                    kind: CommandErrorKind::Other,
96fdf90aacd6atagen2026-09-08 08:24 UTC2446
                    message: "room operation did not return a room ID".to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC2447
                })));
96fdf90aacd6atagen2026-09-08 08:24 UTC2448
            } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC2449
                let _ = emit_command_result(events, id, CommandKind::Room, Ok(())).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC2450
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC2451
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC2452
        Err(error) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC2453
            let failure = CommandFailure {
96fdf90aacd6atagen2026-09-08 08:24 UTC2454
                id,
96fdf90aacd6atagen2026-09-08 08:24 UTC2455
                command: CommandKind::Room,
96fdf90aacd6atagen2026-09-08 08:24 UTC2456
                kind: error.kind,
96fdf90aacd6atagen2026-09-08 08:24 UTC2457
                message: error.to_string(),
96fdf90aacd6atagen2026-09-08 08:24 UTC2458
            };
96fdf90aacd6atagen2026-09-08 08:24 UTC2459
            if let Some(reply) = reply {
96fdf90aacd6atagen2026-09-08 08:24 UTC2460
                let _ = reply.send(Err(CommandResultError::Failed(failure)));
96fdf90aacd6atagen2026-09-08 08:24 UTC2461
            } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC2462
                let _ = events.send(Event::CommandFailed(failure)).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC2463
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC2464
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC2465
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2466
}
96fdf90aacd6atagen2026-09-08 08:24 UTC2467
96fdf90aacd6atagen2026-09-08 08:24 UTC2468
async fn execute_room_command(
96fdf90aacd6atagen2026-09-08 08:24 UTC2469
    client: &Client,
96fdf90aacd6atagen2026-09-08 08:24 UTC2470
    command: RoomCommand,
96fdf90aacd6atagen2026-09-08 08:24 UTC2471
) -> Result<Option<OwnedRoomId>, CommandError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC2472
    match command {
96fdf90aacd6atagen2026-09-08 08:24 UTC2473
        RoomCommand::Join { room, via } => client
96fdf90aacd6atagen2026-09-08 08:24 UTC2474
            .join_room_by_id_or_alias(&room, &via)
96fdf90aacd6atagen2026-09-08 08:24 UTC2475
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2476
            .map(|room| Some(room.room_id().to_owned()))
96fdf90aacd6atagen2026-09-08 08:24 UTC2477
            .map_err(|source| CommandError::from_sdk("joining room failed", source)),
96fdf90aacd6atagen2026-09-08 08:24 UTC2478
        RoomCommand::Leave(room_id) => match client.get_room(&room_id) {
96fdf90aacd6atagen2026-09-08 08:24 UTC2479
            Some(room) => room
96fdf90aacd6atagen2026-09-08 08:24 UTC2480
                .leave()
96fdf90aacd6atagen2026-09-08 08:24 UTC2481
                .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2482
                .map(|_| None)
96fdf90aacd6atagen2026-09-08 08:24 UTC2483
                .map_err(|source| CommandError::from_sdk("leaving room failed", source)),
96fdf90aacd6atagen2026-09-08 08:24 UTC2484
            None => Err(room_unknown()),
96fdf90aacd6atagen2026-09-08 08:24 UTC2485
        },
96fdf90aacd6atagen2026-09-08 08:24 UTC2486
        RoomCommand::Forget(room_id) => match client.get_room(&room_id) {
96fdf90aacd6atagen2026-09-08 08:24 UTC2487
            Some(room) => room
96fdf90aacd6atagen2026-09-08 08:24 UTC2488
                .forget()
96fdf90aacd6atagen2026-09-08 08:24 UTC2489
                .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2490
                .map(|_| None)
96fdf90aacd6atagen2026-09-08 08:24 UTC2491
                .map_err(|source| CommandError::from_sdk("forgetting room failed", source)),
96fdf90aacd6atagen2026-09-08 08:24 UTC2492
            None => Err(room_unknown()),
96fdf90aacd6atagen2026-09-08 08:24 UTC2493
        },
96fdf90aacd6atagen2026-09-08 08:24 UTC2494
        RoomCommand::AcceptInvite(room_id) => match client.get_room(&room_id) {
96fdf90aacd6atagen2026-09-08 08:24 UTC2495
            Some(room) => room
96fdf90aacd6atagen2026-09-08 08:24 UTC2496
                .join()
96fdf90aacd6atagen2026-09-08 08:24 UTC2497
                .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2498
                .map(|_| None)
96fdf90aacd6atagen2026-09-08 08:24 UTC2499
                .map_err(|source| CommandError::from_sdk("accepting invite failed", source)),
96fdf90aacd6atagen2026-09-08 08:24 UTC2500
            None => Err(room_unknown()),
96fdf90aacd6atagen2026-09-08 08:24 UTC2501
        },
96fdf90aacd6atagen2026-09-08 08:24 UTC2502
        RoomCommand::RejectInvite(room_id) => match client.get_room(&room_id) {
96fdf90aacd6atagen2026-09-08 08:24 UTC2503
            Some(room) => room
96fdf90aacd6atagen2026-09-08 08:24 UTC2504
                .leave()
96fdf90aacd6atagen2026-09-08 08:24 UTC2505
                .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2506
                .map(|_| None)
96fdf90aacd6atagen2026-09-08 08:24 UTC2507
                .map_err(|source| CommandError::from_sdk("rejecting invite failed", source)),
96fdf90aacd6atagen2026-09-08 08:24 UTC2508
            None => Err(room_unknown()),
96fdf90aacd6atagen2026-09-08 08:24 UTC2509
        },
96fdf90aacd6atagen2026-09-08 08:24 UTC2510
        RoomCommand::InviteUser { room_id, user_id } => match client.get_room(&room_id) {
96fdf90aacd6atagen2026-09-08 08:24 UTC2511
            Some(room) => room
96fdf90aacd6atagen2026-09-08 08:24 UTC2512
                .invite_user_by_id(&user_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC2513
                .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2514
                .map(|()| None)
96fdf90aacd6atagen2026-09-08 08:24 UTC2515
                .map_err(|source| CommandError::from_sdk("inviting user failed", source)),
96fdf90aacd6atagen2026-09-08 08:24 UTC2516
            None => Err(room_unknown()),
96fdf90aacd6atagen2026-09-08 08:24 UTC2517
        },
96fdf90aacd6atagen2026-09-08 08:24 UTC2518
        RoomCommand::KickUser {
96fdf90aacd6atagen2026-09-08 08:24 UTC2519
            room_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC2520
            user_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC2521
            reason,
96fdf90aacd6atagen2026-09-08 08:24 UTC2522
        } => match client.get_room(&room_id) {
96fdf90aacd6atagen2026-09-08 08:24 UTC2523
            Some(room) => room
96fdf90aacd6atagen2026-09-08 08:24 UTC2524
                .kick_user(&user_id, reason.as_deref())
96fdf90aacd6atagen2026-09-08 08:24 UTC2525
                .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2526
                .map(|_| None)
96fdf90aacd6atagen2026-09-08 08:24 UTC2527
                .map_err(|source| CommandError::from_sdk("kicking user failed", source)),
96fdf90aacd6atagen2026-09-08 08:24 UTC2528
            None => Err(room_unknown()),
96fdf90aacd6atagen2026-09-08 08:24 UTC2529
        },
96fdf90aacd6atagen2026-09-08 08:24 UTC2530
        RoomCommand::BanUser {
96fdf90aacd6atagen2026-09-08 08:24 UTC2531
            room_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC2532
            user_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC2533
            reason,
96fdf90aacd6atagen2026-09-08 08:24 UTC2534
        } => match client.get_room(&room_id) {
96fdf90aacd6atagen2026-09-08 08:24 UTC2535
            Some(room) => room
96fdf90aacd6atagen2026-09-08 08:24 UTC2536
                .ban_user(&user_id, reason.as_deref())
96fdf90aacd6atagen2026-09-08 08:24 UTC2537
                .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2538
                .map(|_| None)
96fdf90aacd6atagen2026-09-08 08:24 UTC2539
                .map_err(|source| CommandError::from_sdk("banning user failed", source)),
96fdf90aacd6atagen2026-09-08 08:24 UTC2540
            None => Err(room_unknown()),
96fdf90aacd6atagen2026-09-08 08:24 UTC2541
        },
96fdf90aacd6atagen2026-09-08 08:24 UTC2542
        RoomCommand::UnbanUser {
96fdf90aacd6atagen2026-09-08 08:24 UTC2543
            room_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC2544
            user_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC2545
            reason,
96fdf90aacd6atagen2026-09-08 08:24 UTC2546
        } => match client.get_room(&room_id) {
96fdf90aacd6atagen2026-09-08 08:24 UTC2547
            Some(room) => room
96fdf90aacd6atagen2026-09-08 08:24 UTC2548
                .unban_user(&user_id, reason.as_deref())
96fdf90aacd6atagen2026-09-08 08:24 UTC2549
                .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2550
                .map(|_| None)
96fdf90aacd6atagen2026-09-08 08:24 UTC2551
                .map_err(|source| CommandError::from_sdk("unbanning user failed", source)),
96fdf90aacd6atagen2026-09-08 08:24 UTC2552
            None => Err(room_unknown()),
96fdf90aacd6atagen2026-09-08 08:24 UTC2553
        },
96fdf90aacd6atagen2026-09-08 08:24 UTC2554
        RoomCommand::SetPowerLevels { room_id, changes } => match client.get_room(&room_id) {
96fdf90aacd6atagen2026-09-08 08:24 UTC2555
            Some(room) => apply_power_level_changes(&room, changes)
96fdf90aacd6atagen2026-09-08 08:24 UTC2556
                .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2557
                .map(|_| None),
96fdf90aacd6atagen2026-09-08 08:24 UTC2558
            None => Err(room_unknown()),
96fdf90aacd6atagen2026-09-08 08:24 UTC2559
        },
96fdf90aacd6atagen2026-09-08 08:24 UTC2560
        RoomCommand::SetUserPowerLevel {
96fdf90aacd6atagen2026-09-08 08:24 UTC2561
            room_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC2562
            user_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC2563
            level,
96fdf90aacd6atagen2026-09-08 08:24 UTC2564
        } => match client.get_room(&room_id) {
96fdf90aacd6atagen2026-09-08 08:24 UTC2565
            Some(room) => match matrix_sdk::ruma::Int::try_from(level) {
96fdf90aacd6atagen2026-09-08 08:24 UTC2566
                Ok(level) => room
96fdf90aacd6atagen2026-09-08 08:24 UTC2567
                    .update_power_levels(vec![(&user_id, level)])
96fdf90aacd6atagen2026-09-08 08:24 UTC2568
                    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2569
                    .map(|_| None)
96fdf90aacd6atagen2026-09-08 08:24 UTC2570
                    .map_err(|source| {
96fdf90aacd6atagen2026-09-08 08:24 UTC2571
                        CommandError::from_sdk("updating user power level failed", source)
96fdf90aacd6atagen2026-09-08 08:24 UTC2572
                    }),
96fdf90aacd6atagen2026-09-08 08:24 UTC2573
                Err(_) => Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC2574
                    CommandErrorKind::Other,
96fdf90aacd6atagen2026-09-08 08:24 UTC2575
                    "power level is outside the Matrix integer range",
96fdf90aacd6atagen2026-09-08 08:24 UTC2576
                )),
96fdf90aacd6atagen2026-09-08 08:24 UTC2577
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC2578
            None => Err(room_unknown()),
96fdf90aacd6atagen2026-09-08 08:24 UTC2579
        },
96fdf90aacd6atagen2026-09-08 08:24 UTC2580
        RoomCommand::Create(options) => client
96fdf90aacd6atagen2026-09-08 08:24 UTC2581
            .create_room(create_room_request(options))
96fdf90aacd6atagen2026-09-08 08:24 UTC2582
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2583
            .map(|room| Some(room.room_id().to_owned()))
96fdf90aacd6atagen2026-09-08 08:24 UTC2584
            .map_err(|source| CommandError::from_sdk("creating room failed", source)),
fa9114fcf75fatagen2026-09-24 12:51 UTC2585
        RoomCommand::CreateDirect(user_id) => match client.get_dm_room(&user_id) {
fa9114fcf75fatagen2026-09-24 12:51 UTC2586
            Some(room) => Ok(Some(room.room_id().to_owned())),
fa9114fcf75fatagen2026-09-24 12:51 UTC2587
            None => client
fa9114fcf75fatagen2026-09-24 12:51 UTC2588
                .create_dm(&user_id)
fa9114fcf75fatagen2026-09-24 12:51 UTC2589
                .await
fa9114fcf75fatagen2026-09-24 12:51 UTC2590
                .map(|room| Some(room.room_id().to_owned()))
fa9114fcf75fatagen2026-09-24 12:51 UTC2591
                .map_err(|source| CommandError::from_sdk("creating direct room failed", source)),
fa9114fcf75fatagen2026-09-24 12:51 UTC2592
        },
96fdf90aacd6atagen2026-09-08 08:24 UTC2593
        _ => Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC2594
            CommandErrorKind::Other,
96fdf90aacd6atagen2026-09-08 08:24 UTC2595
            "unsupported room command",
96fdf90aacd6atagen2026-09-08 08:24 UTC2596
        )),
96fdf90aacd6atagen2026-09-08 08:24 UTC2597
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2598
}
96fdf90aacd6atagen2026-09-08 08:24 UTC2599
96fdf90aacd6atagen2026-09-08 08:24 UTC2600
fn room_unknown() -> CommandError {
96fdf90aacd6atagen2026-09-08 08:24 UTC2601
    CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC2602
        CommandErrorKind::RoomUnknown,
96fdf90aacd6atagen2026-09-08 08:24 UTC2603
        "room is not available in the local store",
96fdf90aacd6atagen2026-09-08 08:24 UTC2604
    )
96fdf90aacd6atagen2026-09-08 08:24 UTC2605
}
96fdf90aacd6atagen2026-09-08 08:24 UTC2606
96fdf90aacd6atagen2026-09-08 08:24 UTC2607
const fn sdk_notification_mode(
96fdf90aacd6atagen2026-09-08 08:24 UTC2608
    mode: RoomNotificationMode,
96fdf90aacd6atagen2026-09-08 08:24 UTC2609
) -> matrix_sdk::notification_settings::RoomNotificationMode {
96fdf90aacd6atagen2026-09-08 08:24 UTC2610
    match mode {
96fdf90aacd6atagen2026-09-08 08:24 UTC2611
        RoomNotificationMode::AllMessages => {
96fdf90aacd6atagen2026-09-08 08:24 UTC2612
            matrix_sdk::notification_settings::RoomNotificationMode::AllMessages
96fdf90aacd6atagen2026-09-08 08:24 UTC2613
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC2614
        RoomNotificationMode::MentionsAndKeywordsOnly => {
96fdf90aacd6atagen2026-09-08 08:24 UTC2615
            matrix_sdk::notification_settings::RoomNotificationMode::MentionsAndKeywordsOnly
96fdf90aacd6atagen2026-09-08 08:24 UTC2616
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC2617
        RoomNotificationMode::Mute => matrix_sdk::notification_settings::RoomNotificationMode::Mute,
96fdf90aacd6atagen2026-09-08 08:24 UTC2618
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2619
}
96fdf90aacd6atagen2026-09-08 08:24 UTC2620
96fdf90aacd6atagen2026-09-08 08:24 UTC2621
async fn execute_notification_command(
96fdf90aacd6atagen2026-09-08 08:24 UTC2622
    client: &Client,
96fdf90aacd6atagen2026-09-08 08:24 UTC2623
    command: NotificationCommand,
96fdf90aacd6atagen2026-09-08 08:24 UTC2624
) -> Result<(), CommandError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC2625
    use matrix_sdk::notification_settings::{IsEncrypted, IsOneToOne};
96fdf90aacd6atagen2026-09-08 08:24 UTC2626
96fdf90aacd6atagen2026-09-08 08:24 UTC2627
    let settings = client.notification_settings().await;
96fdf90aacd6atagen2026-09-08 08:24 UTC2628
    let result = match command {
96fdf90aacd6atagen2026-09-08 08:24 UTC2629
        NotificationCommand::SetRoomMode { room_id, mode } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC2630
            settings
96fdf90aacd6atagen2026-09-08 08:24 UTC2631
                .set_room_notification_mode(&room_id, sdk_notification_mode(mode))
96fdf90aacd6atagen2026-09-08 08:24 UTC2632
                .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2633
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC2634
        NotificationCommand::ResetRoomMode(room_id) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC2635
            settings.delete_user_defined_room_rules(&room_id).await
96fdf90aacd6atagen2026-09-08 08:24 UTC2636
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC2637
        NotificationCommand::SetDefaultMode {
96fdf90aacd6atagen2026-09-08 08:24 UTC2638
            encrypted,
96fdf90aacd6atagen2026-09-08 08:24 UTC2639
            one_to_one,
96fdf90aacd6atagen2026-09-08 08:24 UTC2640
            mode,
96fdf90aacd6atagen2026-09-08 08:24 UTC2641
        } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC2642
            settings
96fdf90aacd6atagen2026-09-08 08:24 UTC2643
                .set_default_room_notification_mode(
96fdf90aacd6atagen2026-09-08 08:24 UTC2644
                    IsEncrypted::from(encrypted),
96fdf90aacd6atagen2026-09-08 08:24 UTC2645
                    IsOneToOne::from(one_to_one),
96fdf90aacd6atagen2026-09-08 08:24 UTC2646
                    sdk_notification_mode(mode),
96fdf90aacd6atagen2026-09-08 08:24 UTC2647
                )
96fdf90aacd6atagen2026-09-08 08:24 UTC2648
                .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2649
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC2650
        NotificationCommand::AddKeyword(keyword) => settings.add_keyword(keyword).await,
96fdf90aacd6atagen2026-09-08 08:24 UTC2651
        NotificationCommand::RemoveKeyword(keyword) => settings.remove_keyword(&keyword).await,
96fdf90aacd6atagen2026-09-08 08:24 UTC2652
    };
96fdf90aacd6atagen2026-09-08 08:24 UTC2653
    result.map_err(|source| {
96fdf90aacd6atagen2026-09-08 08:24 UTC2654
        CommandError::with_source(
96fdf90aacd6atagen2026-09-08 08:24 UTC2655
            CommandErrorKind::Server,
96fdf90aacd6atagen2026-09-08 08:24 UTC2656
            "updating notification settings failed",
96fdf90aacd6atagen2026-09-08 08:24 UTC2657
            source,
96fdf90aacd6atagen2026-09-08 08:24 UTC2658
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC2659
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC2660
}
96fdf90aacd6atagen2026-09-08 08:24 UTC2661
96fdf90aacd6atagen2026-09-08 08:24 UTC2662
async fn execute_encryption_command(
96fdf90aacd6atagen2026-09-08 08:24 UTC2663
    client: &Client,
96fdf90aacd6atagen2026-09-08 08:24 UTC2664
    events: &EventSender,
96fdf90aacd6atagen2026-09-08 08:24 UTC2665
    verification: &mut VerificationController,
96fdf90aacd6atagen2026-09-08 08:24 UTC2666
    command: EncryptionCommand,
96fdf90aacd6atagen2026-09-08 08:24 UTC2667
) -> Result<(), CommandError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC2668
    match command {
96fdf90aacd6atagen2026-09-08 08:24 UTC2669
        EncryptionCommand::Recover(key) => client
96fdf90aacd6atagen2026-09-08 08:24 UTC2670
            .encryption()
96fdf90aacd6atagen2026-09-08 08:24 UTC2671
            .recovery()
96fdf90aacd6atagen2026-09-08 08:24 UTC2672
            .recover(&key)
96fdf90aacd6atagen2026-09-08 08:24 UTC2673
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2674
            .map_err(|source| CommandError::from_recovery("account recovery failed", source)),
96fdf90aacd6atagen2026-09-08 08:24 UTC2675
        EncryptionCommand::StartVerification(target) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC2676
            verification.start(client, events.clone(), target).await
96fdf90aacd6atagen2026-09-08 08:24 UTC2677
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC2678
        EncryptionCommand::Verify(action) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC2679
            verification.apply(client, events.clone(), action).await
96fdf90aacd6atagen2026-09-08 08:24 UTC2680
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC2681
        command => execute_key_backup_command(client, command).await,
96fdf90aacd6atagen2026-09-08 08:24 UTC2682
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2683
}
96fdf90aacd6atagen2026-09-08 08:24 UTC2684
96fdf90aacd6atagen2026-09-08 08:24 UTC2685
async fn execute_key_backup_command(
96fdf90aacd6atagen2026-09-08 08:24 UTC2686
    client: &Client,
96fdf90aacd6atagen2026-09-08 08:24 UTC2687
    command: EncryptionCommand,
96fdf90aacd6atagen2026-09-08 08:24 UTC2688
) -> Result<(), CommandError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC2689
    match command {
96fdf90aacd6atagen2026-09-08 08:24 UTC2690
        EncryptionCommand::CreateKeyBackup => client
96fdf90aacd6atagen2026-09-08 08:24 UTC2691
            .encryption()
96fdf90aacd6atagen2026-09-08 08:24 UTC2692
            .backups()
96fdf90aacd6atagen2026-09-08 08:24 UTC2693
            .create()
96fdf90aacd6atagen2026-09-08 08:24 UTC2694
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2695
            .map_err(|source| CommandError::from_sdk("creating key backup failed", source)),
96fdf90aacd6atagen2026-09-08 08:24 UTC2696
        EncryptionCommand::EnableKeyBackup => client
96fdf90aacd6atagen2026-09-08 08:24 UTC2697
            .encryption()
96fdf90aacd6atagen2026-09-08 08:24 UTC2698
            .recovery()
96fdf90aacd6atagen2026-09-08 08:24 UTC2699
            .enable_backup()
96fdf90aacd6atagen2026-09-08 08:24 UTC2700
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2701
            .map_err(|source| CommandError::from_recovery("enabling key backup failed", source)),
96fdf90aacd6atagen2026-09-08 08:24 UTC2702
        EncryptionCommand::DisableKeyBackup { delete_from_server } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC2703
            let backups = client.encryption().backups();
96fdf90aacd6atagen2026-09-08 08:24 UTC2704
            if delete_from_server {
96fdf90aacd6atagen2026-09-08 08:24 UTC2705
                backups.disable_and_delete().await
96fdf90aacd6atagen2026-09-08 08:24 UTC2706
            } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC2707
                backups.disable().await
96fdf90aacd6atagen2026-09-08 08:24 UTC2708
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC2709
            .map_err(|source| CommandError::from_sdk("disabling key backup failed", source))
96fdf90aacd6atagen2026-09-08 08:24 UTC2710
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC2711
        EncryptionCommand::RecoverKeyBackup(key) => client
96fdf90aacd6atagen2026-09-08 08:24 UTC2712
            .encryption()
96fdf90aacd6atagen2026-09-08 08:24 UTC2713
            .recovery()
96fdf90aacd6atagen2026-09-08 08:24 UTC2714
            .recover_and_fix_backup(&key)
96fdf90aacd6atagen2026-09-08 08:24 UTC2715
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2716
            .map_err(|source| CommandError::from_recovery("recovering key backup failed", source)),
96fdf90aacd6atagen2026-09-08 08:24 UTC2717
        _ => unreachable!("only key backup commands are spawned here"),
96fdf90aacd6atagen2026-09-08 08:24 UTC2718
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2719
}
96fdf90aacd6atagen2026-09-08 08:24 UTC2720
96fdf90aacd6atagen2026-09-08 08:24 UTC2721
async fn apply_power_level_changes(
96fdf90aacd6atagen2026-09-08 08:24 UTC2722
    room: &Room,
96fdf90aacd6atagen2026-09-08 08:24 UTC2723
    changes: PowerLevelChanges,
96fdf90aacd6atagen2026-09-08 08:24 UTC2724
) -> Result<(), CommandError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC2725
    use matrix_sdk::ruma::events::{
96fdf90aacd6atagen2026-09-08 08:24 UTC2726
        MessageLikeEventType, StateEventType, room::power_levels::RoomPowerLevelsEventContent,
96fdf90aacd6atagen2026-09-08 08:24 UTC2727
    };
96fdf90aacd6atagen2026-09-08 08:24 UTC2728
96fdf90aacd6atagen2026-09-08 08:24 UTC2729
    let mut levels = room
96fdf90aacd6atagen2026-09-08 08:24 UTC2730
        .power_levels()
96fdf90aacd6atagen2026-09-08 08:24 UTC2731
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2732
        .map_err(|source| CommandError::from_sdk("reading power levels failed", source.into()))?;
96fdf90aacd6atagen2026-09-08 08:24 UTC2733
    if let Some(value) = changes.ban {
96fdf90aacd6atagen2026-09-08 08:24 UTC2734
        levels.ban = matrix_power_level(value)?;
96fdf90aacd6atagen2026-09-08 08:24 UTC2735
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2736
    if let Some(value) = changes.invite {
96fdf90aacd6atagen2026-09-08 08:24 UTC2737
        levels.invite = matrix_power_level(value)?;
96fdf90aacd6atagen2026-09-08 08:24 UTC2738
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2739
    if let Some(value) = changes.kick {
96fdf90aacd6atagen2026-09-08 08:24 UTC2740
        levels.kick = matrix_power_level(value)?;
96fdf90aacd6atagen2026-09-08 08:24 UTC2741
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2742
    if let Some(value) = changes.redact {
96fdf90aacd6atagen2026-09-08 08:24 UTC2743
        levels.redact = matrix_power_level(value)?;
96fdf90aacd6atagen2026-09-08 08:24 UTC2744
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2745
    if let Some(value) = changes.events_default {
96fdf90aacd6atagen2026-09-08 08:24 UTC2746
        levels.events_default = matrix_power_level(value)?;
96fdf90aacd6atagen2026-09-08 08:24 UTC2747
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2748
    if let Some(value) = changes.state_default {
96fdf90aacd6atagen2026-09-08 08:24 UTC2749
        levels.state_default = matrix_power_level(value)?;
96fdf90aacd6atagen2026-09-08 08:24 UTC2750
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2751
    if let Some(value) = changes.users_default {
96fdf90aacd6atagen2026-09-08 08:24 UTC2752
        levels.users_default = matrix_power_level(value)?;
96fdf90aacd6atagen2026-09-08 08:24 UTC2753
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2754
    if let Some(value) = changes.room_notification {
96fdf90aacd6atagen2026-09-08 08:24 UTC2755
        levels.notifications.room = matrix_power_level(value)?;
96fdf90aacd6atagen2026-09-08 08:24 UTC2756
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2757
    for (event_type, value) in [
96fdf90aacd6atagen2026-09-08 08:24 UTC2758
        (StateEventType::RoomName.into(), changes.room_name),
96fdf90aacd6atagen2026-09-08 08:24 UTC2759
        (StateEventType::RoomAvatar.into(), changes.room_avatar),
96fdf90aacd6atagen2026-09-08 08:24 UTC2760
        (StateEventType::RoomTopic.into(), changes.room_topic),
96fdf90aacd6atagen2026-09-08 08:24 UTC2761
        (StateEventType::SpaceChild.into(), changes.space_child),
96fdf90aacd6atagen2026-09-08 08:24 UTC2762
        (MessageLikeEventType::Beacon.into(), changes.beacon),
96fdf90aacd6atagen2026-09-08 08:24 UTC2763
        (StateEventType::BeaconInfo.into(), changes.beacon_info),
96fdf90aacd6atagen2026-09-08 08:24 UTC2764
    ] {
96fdf90aacd6atagen2026-09-08 08:24 UTC2765
        if let Some(value) = value {
96fdf90aacd6atagen2026-09-08 08:24 UTC2766
            levels.events.insert(event_type, matrix_power_level(value)?);
96fdf90aacd6atagen2026-09-08 08:24 UTC2767
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC2768
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2769
    let content = RoomPowerLevelsEventContent::try_from(levels).map_err(|source| {
96fdf90aacd6atagen2026-09-08 08:24 UTC2770
        CommandError::with_source(
96fdf90aacd6atagen2026-09-08 08:24 UTC2771
            CommandErrorKind::Other,
96fdf90aacd6atagen2026-09-08 08:24 UTC2772
            "normalizing power levels failed",
96fdf90aacd6atagen2026-09-08 08:24 UTC2773
            source,
96fdf90aacd6atagen2026-09-08 08:24 UTC2774
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC2775
    })?;
96fdf90aacd6atagen2026-09-08 08:24 UTC2776
    room.send_state_event(content)
96fdf90aacd6atagen2026-09-08 08:24 UTC2777
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2778
        .map(|_| ())
96fdf90aacd6atagen2026-09-08 08:24 UTC2779
        .map_err(|source| CommandError::from_sdk("updating power levels failed", source))
96fdf90aacd6atagen2026-09-08 08:24 UTC2780
}
96fdf90aacd6atagen2026-09-08 08:24 UTC2781
96fdf90aacd6atagen2026-09-08 08:24 UTC2782
fn matrix_power_level(value: i64) -> Result<matrix_sdk::ruma::Int, CommandError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC2783
    value.try_into().map_err(|source| {
96fdf90aacd6atagen2026-09-08 08:24 UTC2784
        CommandError::with_source(
96fdf90aacd6atagen2026-09-08 08:24 UTC2785
            CommandErrorKind::Other,
96fdf90aacd6atagen2026-09-08 08:24 UTC2786
            "power level is outside the Matrix integer range",
96fdf90aacd6atagen2026-09-08 08:24 UTC2787
            source,
96fdf90aacd6atagen2026-09-08 08:24 UTC2788
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC2789
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC2790
}
96fdf90aacd6atagen2026-09-08 08:24 UTC2791
96fdf90aacd6atagen2026-09-08 08:24 UTC2792
fn create_room_request(
96fdf90aacd6atagen2026-09-08 08:24 UTC2793
    options: CreateRoomOptions,
96fdf90aacd6atagen2026-09-08 08:24 UTC2794
) -> matrix_sdk::ruma::api::client::room::create_room::v3::Request {
96fdf90aacd6atagen2026-09-08 08:24 UTC2795
    use matrix_sdk::ruma::api::client::room::{
96fdf90aacd6atagen2026-09-08 08:24 UTC2796
        Visibility as SdkVisibility,
96fdf90aacd6atagen2026-09-08 08:24 UTC2797
        create_room::v3::{Request, RoomPreset as SdkPreset},
96fdf90aacd6atagen2026-09-08 08:24 UTC2798
    };
96fdf90aacd6atagen2026-09-08 08:24 UTC2799
    let mut request = Request::new();
96fdf90aacd6atagen2026-09-08 08:24 UTC2800
    request.name = options.name;
96fdf90aacd6atagen2026-09-08 08:24 UTC2801
    request.topic = options.topic;
96fdf90aacd6atagen2026-09-08 08:24 UTC2802
    request.room_alias_name = options.alias_localpart;
96fdf90aacd6atagen2026-09-08 08:24 UTC2803
    request.invite = options.invite;
96fdf90aacd6atagen2026-09-08 08:24 UTC2804
    request.is_direct = options.direct;
96fdf90aacd6atagen2026-09-08 08:24 UTC2805
    request.visibility = match options.visibility {
96fdf90aacd6atagen2026-09-08 08:24 UTC2806
        RoomVisibility::Private => SdkVisibility::Private,
96fdf90aacd6atagen2026-09-08 08:24 UTC2807
        RoomVisibility::Public => SdkVisibility::Public,
96fdf90aacd6atagen2026-09-08 08:24 UTC2808
    };
96fdf90aacd6atagen2026-09-08 08:24 UTC2809
    request.preset = options.preset.map(|preset| match preset {
96fdf90aacd6atagen2026-09-08 08:24 UTC2810
        RoomPreset::PrivateChat => SdkPreset::PrivateChat,
96fdf90aacd6atagen2026-09-08 08:24 UTC2811
        RoomPreset::TrustedPrivateChat => SdkPreset::TrustedPrivateChat,
96fdf90aacd6atagen2026-09-08 08:24 UTC2812
        RoomPreset::PublicChat => SdkPreset::PublicChat,
96fdf90aacd6atagen2026-09-08 08:24 UTC2813
    });
96fdf90aacd6atagen2026-09-08 08:24 UTC2814
    request
96fdf90aacd6atagen2026-09-08 08:24 UTC2815
}
96fdf90aacd6atagen2026-09-08 08:24 UTC2816
96fdf90aacd6atagen2026-09-08 08:24 UTC2817
async fn queue_media_upload(
96fdf90aacd6atagen2026-09-08 08:24 UTC2818
    room: Room,
96fdf90aacd6atagen2026-09-08 08:24 UTC2819
    upload: AttachmentUpload,
96fdf90aacd6atagen2026-09-08 08:24 UTC2820
    max_media_bytes: usize,
96fdf90aacd6atagen2026-09-08 08:24 UTC2821
) -> Result<(), CommandError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC2822
    let (upload, mime, mut config) = prepare_upload(upload, max_media_bytes)?;
96fdf90aacd6atagen2026-09-08 08:24 UTC2823
    if let TimelineId::Thread { root_event_id, .. } = &upload.timeline_id {
96fdf90aacd6atagen2026-09-08 08:24 UTC2824
        config.reply = Some(SdkReply {
96fdf90aacd6atagen2026-09-08 08:24 UTC2825
            event_id: root_event_id.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC2826
            enforce_thread: EnforceThread::Threaded(ReplyWithinThread::No),
96fdf90aacd6atagen2026-09-08 08:24 UTC2827
            add_mentions: AddMentions::No,
96fdf90aacd6atagen2026-09-08 08:24 UTC2828
        });
96fdf90aacd6atagen2026-09-08 08:24 UTC2829
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2830
    room.send_queue()
96fdf90aacd6atagen2026-09-08 08:24 UTC2831
        .send_attachment(upload.filename, mime, upload.bytes, config)
96fdf90aacd6atagen2026-09-08 08:24 UTC2832
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2833
        .map(|_| ())
96fdf90aacd6atagen2026-09-08 08:24 UTC2834
        .map_err(|source| {
96fdf90aacd6atagen2026-09-08 08:24 UTC2835
            CommandError::with_source(
96fdf90aacd6atagen2026-09-08 08:24 UTC2836
                CommandErrorKind::MediaUnavailable,
96fdf90aacd6atagen2026-09-08 08:24 UTC2837
                "media upload could not be queued",
96fdf90aacd6atagen2026-09-08 08:24 UTC2838
                source,
96fdf90aacd6atagen2026-09-08 08:24 UTC2839
            )
96fdf90aacd6atagen2026-09-08 08:24 UTC2840
        })
96fdf90aacd6atagen2026-09-08 08:24 UTC2841
}
96fdf90aacd6atagen2026-09-08 08:24 UTC2842
96fdf90aacd6atagen2026-09-08 08:24 UTC2843
async fn fetch_media(
96fdf90aacd6atagen2026-09-08 08:24 UTC2844
    client: Client,
96fdf90aacd6atagen2026-09-08 08:24 UTC2845
    registry: Arc<Mutex<MediaRegistry>>,
96fdf90aacd6atagen2026-09-08 08:24 UTC2846
    events: EventSender,
96fdf90aacd6atagen2026-09-08 08:24 UTC2847
    max_media_bytes: usize,
96fdf90aacd6atagen2026-09-08 08:24 UTC2848
    media_id: MediaId,
96fdf90aacd6atagen2026-09-08 08:24 UTC2849
    size: MediaSize,
96fdf90aacd6atagen2026-09-08 08:24 UTC2850
) -> Result<(), CommandError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC2851
    let media = registry
96fdf90aacd6atagen2026-09-08 08:24 UTC2852
        .lock()
96fdf90aacd6atagen2026-09-08 08:24 UTC2853
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2854
        .get(&media_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC2855
        .cloned()
96fdf90aacd6atagen2026-09-08 08:24 UTC2856
        .ok_or_else(|| {
96fdf90aacd6atagen2026-09-08 08:24 UTC2857
            CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC2858
                CommandErrorKind::MediaUnknown,
96fdf90aacd6atagen2026-09-08 08:24 UTC2859
                "media is not registered in a retained timeline",
96fdf90aacd6atagen2026-09-08 08:24 UTC2860
            )
96fdf90aacd6atagen2026-09-08 08:24 UTC2861
        })?;
96fdf90aacd6atagen2026-09-08 08:24 UTC2862
    let (source, format, mimetype, declared_size) = match size {
96fdf90aacd6atagen2026-09-08 08:24 UTC2863
        MediaSize::Full => (
96fdf90aacd6atagen2026-09-08 08:24 UTC2864
            media.full.source,
96fdf90aacd6atagen2026-09-08 08:24 UTC2865
            MediaFormat::File,
96fdf90aacd6atagen2026-09-08 08:24 UTC2866
            media.full.mimetype,
96fdf90aacd6atagen2026-09-08 08:24 UTC2867
            media.full.declared_size,
96fdf90aacd6atagen2026-09-08 08:24 UTC2868
        ),
96fdf90aacd6atagen2026-09-08 08:24 UTC2869
        MediaSize::Thumbnail => {
96fdf90aacd6atagen2026-09-08 08:24 UTC2870
            if let Some(thumbnail) = media.thumbnail {
96fdf90aacd6atagen2026-09-08 08:24 UTC2871
                (
96fdf90aacd6atagen2026-09-08 08:24 UTC2872
                    thumbnail.source,
96fdf90aacd6atagen2026-09-08 08:24 UTC2873
                    MediaFormat::File,
96fdf90aacd6atagen2026-09-08 08:24 UTC2874
                    thumbnail.mimetype,
96fdf90aacd6atagen2026-09-08 08:24 UTC2875
                    thumbnail.declared_size,
96fdf90aacd6atagen2026-09-08 08:24 UTC2876
                )
96fdf90aacd6atagen2026-09-08 08:24 UTC2877
            } else if matches!(
96fdf90aacd6atagen2026-09-08 08:24 UTC2878
                media.kind,
96fdf90aacd6atagen2026-09-08 08:24 UTC2879
                crate::MediaKind::Image | crate::MediaKind::Video
96fdf90aacd6atagen2026-09-08 08:24 UTC2880
            ) && matches!(
96fdf90aacd6atagen2026-09-08 08:24 UTC2881
                &media.full.source,
96fdf90aacd6atagen2026-09-08 08:24 UTC2882
                matrix_sdk::ruma::events::room::MediaSource::Plain(_)
96fdf90aacd6atagen2026-09-08 08:24 UTC2883
            ) {
96fdf90aacd6atagen2026-09-08 08:24 UTC2884
                (
96fdf90aacd6atagen2026-09-08 08:24 UTC2885
                    media.full.source,
96fdf90aacd6atagen2026-09-08 08:24 UTC2886
                    MediaFormat::Thumbnail(MediaThumbnailSettings::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC2887
                        800_u32.into(),
96fdf90aacd6atagen2026-09-08 08:24 UTC2888
                        800_u32.into(),
96fdf90aacd6atagen2026-09-08 08:24 UTC2889
                    )),
96fdf90aacd6atagen2026-09-08 08:24 UTC2890
                    media.full.mimetype,
96fdf90aacd6atagen2026-09-08 08:24 UTC2891
                    None,
96fdf90aacd6atagen2026-09-08 08:24 UTC2892
                )
96fdf90aacd6atagen2026-09-08 08:24 UTC2893
            } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC2894
                return Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC2895
                    CommandErrorKind::MediaUnavailable,
96fdf90aacd6atagen2026-09-08 08:24 UTC2896
                    "the requested media size is unavailable",
96fdf90aacd6atagen2026-09-08 08:24 UTC2897
                ));
96fdf90aacd6atagen2026-09-08 08:24 UTC2898
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC2899
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC2900
    };
96fdf90aacd6atagen2026-09-08 08:24 UTC2901
    if declared_size.is_some_and(|size| size > max_media_bytes as u64) {
96fdf90aacd6atagen2026-09-08 08:24 UTC2902
        return Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC2903
            CommandErrorKind::MediaTooLarge,
96fdf90aacd6atagen2026-09-08 08:24 UTC2904
            "media exceeds the configured byte limit",
96fdf90aacd6atagen2026-09-08 08:24 UTC2905
        ));
96fdf90aacd6atagen2026-09-08 08:24 UTC2906
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2907
    let request = MediaRequestParameters { source, format };
96fdf90aacd6atagen2026-09-08 08:24 UTC2908
    if let Some(cached) = cached_media(&client, &request).await? {
96fdf90aacd6atagen2026-09-08 08:24 UTC2909
        if cached.len() > max_media_bytes {
96fdf90aacd6atagen2026-09-08 08:24 UTC2910
            remove_oversize_media(&client, &request).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC2911
            return Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC2912
                CommandErrorKind::MediaTooLarge,
96fdf90aacd6atagen2026-09-08 08:24 UTC2913
                "cached media exceeds the configured byte limit",
96fdf90aacd6atagen2026-09-08 08:24 UTC2914
            ));
96fdf90aacd6atagen2026-09-08 08:24 UTC2915
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC2916
        events
96fdf90aacd6atagen2026-09-08 08:24 UTC2917
            .send(Event::MediaReady {
96fdf90aacd6atagen2026-09-08 08:24 UTC2918
                media_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC2919
                size,
fa9114fcf75fatagen2026-09-24 12:51 UTC2920
                bytes: Arc::from(cached),
96fdf90aacd6atagen2026-09-08 08:24 UTC2921
                mimetype: mimetype.unwrap_or_else(|| "application/octet-stream".into()),
96fdf90aacd6atagen2026-09-08 08:24 UTC2922
            })
96fdf90aacd6atagen2026-09-08 08:24 UTC2923
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2924
            .map_err(send_error)?;
96fdf90aacd6atagen2026-09-08 08:24 UTC2925
        return Ok(());
96fdf90aacd6atagen2026-09-08 08:24 UTC2926
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2927
    let bytes = download_bounded(&client, &request, max_media_bytes).await?;
fa9114fcf75fatagen2026-09-24 12:51 UTC2928
    // the sdk caches plaintext under the encrypted key, so we do too or nobody can read anything
96fdf90aacd6atagen2026-09-08 08:24 UTC2929
    let bytes = maybe_decrypt_media(bytes, &request, max_media_bytes)?;
fa9114fcf75fatagen2026-09-24 12:51 UTC2930
    cache_media(&client, &request, &bytes).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC2931
    events
96fdf90aacd6atagen2026-09-08 08:24 UTC2932
        .send(Event::MediaReady {
96fdf90aacd6atagen2026-09-08 08:24 UTC2933
            media_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC2934
            size,
96fdf90aacd6atagen2026-09-08 08:24 UTC2935
            bytes: Arc::from(bytes),
96fdf90aacd6atagen2026-09-08 08:24 UTC2936
            mimetype: mimetype.unwrap_or_else(|| "application/octet-stream".into()),
96fdf90aacd6atagen2026-09-08 08:24 UTC2937
        })
96fdf90aacd6atagen2026-09-08 08:24 UTC2938
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2939
        .map_err(send_error)
96fdf90aacd6atagen2026-09-08 08:24 UTC2940
}
96fdf90aacd6atagen2026-09-08 08:24 UTC2941
async fn cached_media(
96fdf90aacd6atagen2026-09-08 08:24 UTC2942
    client: &Client,
96fdf90aacd6atagen2026-09-08 08:24 UTC2943
    request: &MediaRequestParameters,
96fdf90aacd6atagen2026-09-08 08:24 UTC2944
) -> Result<Option<Vec<u8>>, CommandError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC2945
    client
96fdf90aacd6atagen2026-09-08 08:24 UTC2946
        .media_store()
96fdf90aacd6atagen2026-09-08 08:24 UTC2947
        .lock()
96fdf90aacd6atagen2026-09-08 08:24 UTC2948
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2949
        .map_err(|source| {
96fdf90aacd6atagen2026-09-08 08:24 UTC2950
            CommandError::with_source(
96fdf90aacd6atagen2026-09-08 08:24 UTC2951
                CommandErrorKind::Store,
96fdf90aacd6atagen2026-09-08 08:24 UTC2952
                "reading media cache failed",
96fdf90aacd6atagen2026-09-08 08:24 UTC2953
                source,
96fdf90aacd6atagen2026-09-08 08:24 UTC2954
            )
96fdf90aacd6atagen2026-09-08 08:24 UTC2955
        })?
96fdf90aacd6atagen2026-09-08 08:24 UTC2956
        .get_media_content(request)
96fdf90aacd6atagen2026-09-08 08:24 UTC2957
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC2958
        .map_err(|source| {
96fdf90aacd6atagen2026-09-08 08:24 UTC2959
            CommandError::with_source(
96fdf90aacd6atagen2026-09-08 08:24 UTC2960
                CommandErrorKind::Store,
96fdf90aacd6atagen2026-09-08 08:24 UTC2961
                "reading media cache failed",
96fdf90aacd6atagen2026-09-08 08:24 UTC2962
                source,
96fdf90aacd6atagen2026-09-08 08:24 UTC2963
            )
96fdf90aacd6atagen2026-09-08 08:24 UTC2964
        })
96fdf90aacd6atagen2026-09-08 08:24 UTC2965
}
96fdf90aacd6atagen2026-09-08 08:24 UTC2966
96fdf90aacd6atagen2026-09-08 08:24 UTC2967
async fn remove_oversize_media(client: &Client, request: &MediaRequestParameters) {
96fdf90aacd6atagen2026-09-08 08:24 UTC2968
    if let Ok(store) = client.media_store().lock().await {
96fdf90aacd6atagen2026-09-08 08:24 UTC2969
        let _ = store.remove_media_content(request).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC2970
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2971
}
96fdf90aacd6atagen2026-09-08 08:24 UTC2972
96fdf90aacd6atagen2026-09-08 08:24 UTC2973
async fn cache_media(client: &Client, request: &MediaRequestParameters, bytes: &[u8]) {
96fdf90aacd6atagen2026-09-08 08:24 UTC2974
    if let Ok(store) = client.media_store().lock().await {
96fdf90aacd6atagen2026-09-08 08:24 UTC2975
        let _ = store
96fdf90aacd6atagen2026-09-08 08:24 UTC2976
            .add_media_content(
96fdf90aacd6atagen2026-09-08 08:24 UTC2977
                request,
96fdf90aacd6atagen2026-09-08 08:24 UTC2978
                bytes.to_vec(),
96fdf90aacd6atagen2026-09-08 08:24 UTC2979
                matrix_sdk::media::store::IgnoreMediaRetentionPolicy::No,
96fdf90aacd6atagen2026-09-08 08:24 UTC2980
            )
96fdf90aacd6atagen2026-09-08 08:24 UTC2981
            .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC2982
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC2983
}
96fdf90aacd6atagen2026-09-08 08:24 UTC2984
96fdf90aacd6atagen2026-09-08 08:24 UTC2985
fn maybe_decrypt_media(
96fdf90aacd6atagen2026-09-08 08:24 UTC2986
    bytes: Vec<u8>,
96fdf90aacd6atagen2026-09-08 08:24 UTC2987
    request: &MediaRequestParameters,
96fdf90aacd6atagen2026-09-08 08:24 UTC2988
    max_media_bytes: usize,
96fdf90aacd6atagen2026-09-08 08:24 UTC2989
) -> Result<Vec<u8>, CommandError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC2990
    let matrix_sdk::ruma::events::room::MediaSource::Encrypted(file) = &request.source else {
96fdf90aacd6atagen2026-09-08 08:24 UTC2991
        return Ok(bytes);
96fdf90aacd6atagen2026-09-08 08:24 UTC2992
    };
96fdf90aacd6atagen2026-09-08 08:24 UTC2993
    let info: MediaEncryptionInfo = (**file).clone().into();
96fdf90aacd6atagen2026-09-08 08:24 UTC2994
    let mut cursor = std::io::Cursor::new(bytes);
96fdf90aacd6atagen2026-09-08 08:24 UTC2995
    let mut decryptor = AttachmentDecryptor::new(&mut cursor, info).map_err(|source| {
96fdf90aacd6atagen2026-09-08 08:24 UTC2996
        CommandError::with_source(
96fdf90aacd6atagen2026-09-08 08:24 UTC2997
            CommandErrorKind::MediaUnavailable,
96fdf90aacd6atagen2026-09-08 08:24 UTC2998
            "media decryption failed",
96fdf90aacd6atagen2026-09-08 08:24 UTC2999
            source,
96fdf90aacd6atagen2026-09-08 08:24 UTC3000
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC3001
    })?;
96fdf90aacd6atagen2026-09-08 08:24 UTC3002
    let mut decrypted = Vec::new();
96fdf90aacd6atagen2026-09-08 08:24 UTC3003
    let mut chunk = [0_u8; 8192];
96fdf90aacd6atagen2026-09-08 08:24 UTC3004
    loop {
96fdf90aacd6atagen2026-09-08 08:24 UTC3005
        match decryptor.read(&mut chunk) {
96fdf90aacd6atagen2026-09-08 08:24 UTC3006
            Ok(0) => break,
96fdf90aacd6atagen2026-09-08 08:24 UTC3007
            Ok(read) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3008
                if read > max_media_bytes.saturating_sub(decrypted.len()) {
96fdf90aacd6atagen2026-09-08 08:24 UTC3009
                    return Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC3010
                        CommandErrorKind::MediaTooLarge,
96fdf90aacd6atagen2026-09-08 08:24 UTC3011
                        "decrypted media exceeds the configured byte limit",
96fdf90aacd6atagen2026-09-08 08:24 UTC3012
                    ));
96fdf90aacd6atagen2026-09-08 08:24 UTC3013
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC3014
                decrypted.extend_from_slice(&chunk[..read]);
96fdf90aacd6atagen2026-09-08 08:24 UTC3015
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC3016
            Err(source) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3017
                return Err(CommandError::with_source(
96fdf90aacd6atagen2026-09-08 08:24 UTC3018
                    CommandErrorKind::MediaUnavailable,
96fdf90aacd6atagen2026-09-08 08:24 UTC3019
                    "media decryption failed",
96fdf90aacd6atagen2026-09-08 08:24 UTC3020
                    source,
96fdf90aacd6atagen2026-09-08 08:24 UTC3021
                ));
96fdf90aacd6atagen2026-09-08 08:24 UTC3022
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC3023
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3024
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC3025
    Ok(decrypted)
96fdf90aacd6atagen2026-09-08 08:24 UTC3026
}
96fdf90aacd6atagen2026-09-08 08:24 UTC3027
96fdf90aacd6atagen2026-09-08 08:24 UTC3028
async fn download_bounded(
96fdf90aacd6atagen2026-09-08 08:24 UTC3029
    client: &Client,
96fdf90aacd6atagen2026-09-08 08:24 UTC3030
    request: &MediaRequestParameters,
96fdf90aacd6atagen2026-09-08 08:24 UTC3031
    max_media_bytes: usize,
96fdf90aacd6atagen2026-09-08 08:24 UTC3032
) -> Result<Vec<u8>, CommandError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC3033
    if is_local_media(&request.source) {
96fdf90aacd6atagen2026-09-08 08:24 UTC3034
        return Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC3035
            CommandErrorKind::MediaUnavailable,
96fdf90aacd6atagen2026-09-08 08:24 UTC3036
            "local media is not in the media cache",
96fdf90aacd6atagen2026-09-08 08:24 UTC3037
        ));
96fdf90aacd6atagen2026-09-08 08:24 UTC3038
    }
fa9114fcf75fatagen2026-09-24 12:51 UTC3039
    let mut refreshed = false;
fa9114fcf75fatagen2026-09-24 12:51 UTC3040
    let request = loop {
fa9114fcf75fatagen2026-09-24 12:51 UTC3041
        let http_request = media_http_request(client, request).await?;
fa9114fcf75fatagen2026-09-24 12:51 UTC3042
        let response = client
fa9114fcf75fatagen2026-09-24 12:51 UTC3043
            .http_client()
fa9114fcf75fatagen2026-09-24 12:51 UTC3044
            .execute(request_from_http(client, http_request)?)
fa9114fcf75fatagen2026-09-24 12:51 UTC3045
            .await
fa9114fcf75fatagen2026-09-24 12:51 UTC3046
            .map_err(|source| {
fa9114fcf75fatagen2026-09-24 12:51 UTC3047
                CommandError::with_source(
fa9114fcf75fatagen2026-09-24 12:51 UTC3048
                    CommandErrorKind::Network,
fa9114fcf75fatagen2026-09-24 12:51 UTC3049
                    "media download failed",
fa9114fcf75fatagen2026-09-24 12:51 UTC3050
                    source,
fa9114fcf75fatagen2026-09-24 12:51 UTC3051
                )
fa9114fcf75fatagen2026-09-24 12:51 UTC3052
            })?;
fa9114fcf75fatagen2026-09-24 12:51 UTC3053
        if response.status() != matrix_sdk::reqwest::StatusCode::UNAUTHORIZED {
fa9114fcf75fatagen2026-09-24 12:51 UTC3054
            break response;
fa9114fcf75fatagen2026-09-24 12:51 UTC3055
        }
fa9114fcf75fatagen2026-09-24 12:51 UTC3056
        if refreshed
fa9114fcf75fatagen2026-09-24 12:51 UTC3057
            || client
fa9114fcf75fatagen2026-09-24 12:51 UTC3058
                .session_tokens()
fa9114fcf75fatagen2026-09-24 12:51 UTC3059
                .is_none_or(|tokens| tokens.refresh_token.is_none())
fa9114fcf75fatagen2026-09-24 12:51 UTC3060
        {
fa9114fcf75fatagen2026-09-24 12:51 UTC3061
            return Err(CommandError::new(
fa9114fcf75fatagen2026-09-24 12:51 UTC3062
                CommandErrorKind::Unauthenticated,
fa9114fcf75fatagen2026-09-24 12:51 UTC3063
                "media download was not authorized",
fa9114fcf75fatagen2026-09-24 12:51 UTC3064
            ));
fa9114fcf75fatagen2026-09-24 12:51 UTC3065
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3066
        client.refresh_access_token().await.map_err(|source| {
96fdf90aacd6atagen2026-09-08 08:24 UTC3067
            CommandError::from_refresh_token("refreshing media access token failed", source)
96fdf90aacd6atagen2026-09-08 08:24 UTC3068
        })?;
fa9114fcf75fatagen2026-09-24 12:51 UTC3069
        refreshed = true;
fa9114fcf75fatagen2026-09-24 12:51 UTC3070
    };
fa9114fcf75fatagen2026-09-24 12:51 UTC3071
    let status = request.status();
96fdf90aacd6atagen2026-09-08 08:24 UTC3072
    if !status.is_success() {
96fdf90aacd6atagen2026-09-08 08:24 UTC3073
        return Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC3074
            CommandErrorKind::MediaUnavailable,
96fdf90aacd6atagen2026-09-08 08:24 UTC3075
            format!("media download failed with status {status}"),
96fdf90aacd6atagen2026-09-08 08:24 UTC3076
        ));
96fdf90aacd6atagen2026-09-08 08:24 UTC3077
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC3078
    if request
96fdf90aacd6atagen2026-09-08 08:24 UTC3079
        .content_length()
96fdf90aacd6atagen2026-09-08 08:24 UTC3080
        .is_some_and(|length| length > max_media_bytes as u64)
96fdf90aacd6atagen2026-09-08 08:24 UTC3081
    {
96fdf90aacd6atagen2026-09-08 08:24 UTC3082
        return Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC3083
            CommandErrorKind::MediaTooLarge,
96fdf90aacd6atagen2026-09-08 08:24 UTC3084
            "media exceeds the configured byte limit",
96fdf90aacd6atagen2026-09-08 08:24 UTC3085
        ));
96fdf90aacd6atagen2026-09-08 08:24 UTC3086
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC3087
    let mut stream = request.bytes_stream();
96fdf90aacd6atagen2026-09-08 08:24 UTC3088
    let mut bytes = Vec::new();
96fdf90aacd6atagen2026-09-08 08:24 UTC3089
    while let Some(chunk) = stream.next().await {
96fdf90aacd6atagen2026-09-08 08:24 UTC3090
        let chunk = chunk.map_err(|source| {
96fdf90aacd6atagen2026-09-08 08:24 UTC3091
            CommandError::with_source(CommandErrorKind::Network, "media download failed", source)
96fdf90aacd6atagen2026-09-08 08:24 UTC3092
        })?;
96fdf90aacd6atagen2026-09-08 08:24 UTC3093
        if chunk.len() > max_media_bytes.saturating_sub(bytes.len()) {
96fdf90aacd6atagen2026-09-08 08:24 UTC3094
            return Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC3095
                CommandErrorKind::MediaTooLarge,
96fdf90aacd6atagen2026-09-08 08:24 UTC3096
                "downloaded media exceeds the configured byte limit",
96fdf90aacd6atagen2026-09-08 08:24 UTC3097
            ));
96fdf90aacd6atagen2026-09-08 08:24 UTC3098
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3099
        bytes.extend_from_slice(&chunk);
96fdf90aacd6atagen2026-09-08 08:24 UTC3100
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC3101
    Ok(bytes)
96fdf90aacd6atagen2026-09-08 08:24 UTC3102
}
96fdf90aacd6atagen2026-09-08 08:24 UTC3103
96fdf90aacd6atagen2026-09-08 08:24 UTC3104
fn is_local_media(source: &matrix_sdk::ruma::events::room::MediaSource) -> bool {
96fdf90aacd6atagen2026-09-08 08:24 UTC3105
    let uri = match source {
96fdf90aacd6atagen2026-09-08 08:24 UTC3106
        matrix_sdk::ruma::events::room::MediaSource::Plain(uri) => uri,
96fdf90aacd6atagen2026-09-08 08:24 UTC3107
        matrix_sdk::ruma::events::room::MediaSource::Encrypted(file) => &file.url,
96fdf90aacd6atagen2026-09-08 08:24 UTC3108
    };
96fdf90aacd6atagen2026-09-08 08:24 UTC3109
    let Ok(server) = uri.server_name() else {
96fdf90aacd6atagen2026-09-08 08:24 UTC3110
        return false;
96fdf90aacd6atagen2026-09-08 08:24 UTC3111
    };
96fdf90aacd6atagen2026-09-08 08:24 UTC3112
    server.as_str() == "send-queue.localhost"
96fdf90aacd6atagen2026-09-08 08:24 UTC3113
}
96fdf90aacd6atagen2026-09-08 08:24 UTC3114
96fdf90aacd6atagen2026-09-08 08:24 UTC3115
async fn media_http_request(
96fdf90aacd6atagen2026-09-08 08:24 UTC3116
    client: &Client,
96fdf90aacd6atagen2026-09-08 08:24 UTC3117
    request: &MediaRequestParameters,
96fdf90aacd6atagen2026-09-08 08:24 UTC3118
) -> Result<http::Request<Vec<u8>>, CommandError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC3119
    use matrix_sdk::ruma::api::client::{authenticated_media, media};
96fdf90aacd6atagen2026-09-08 08:24 UTC3120
96fdf90aacd6atagen2026-09-08 08:24 UTC3121
    let versions = client.supported_versions().await.map_err(|source| {
96fdf90aacd6atagen2026-09-08 08:24 UTC3122
        CommandError::with_source(
96fdf90aacd6atagen2026-09-08 08:24 UTC3123
            CommandErrorKind::Network,
96fdf90aacd6atagen2026-09-08 08:24 UTC3124
            "reading supported server versions failed",
96fdf90aacd6atagen2026-09-08 08:24 UTC3125
            source,
96fdf90aacd6atagen2026-09-08 08:24 UTC3126
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC3127
    })?;
96fdf90aacd6atagen2026-09-08 08:24 UTC3128
    let use_auth =
96fdf90aacd6atagen2026-09-08 08:24 UTC3129
        authenticated_media::get_content::v1::Request::PATH_BUILDER.is_supported(&versions);
96fdf90aacd6atagen2026-09-08 08:24 UTC3130
    let base = client.homeserver().to_string();
96fdf90aacd6atagen2026-09-08 08:24 UTC3131
    let token = client.access_token();
96fdf90aacd6atagen2026-09-08 08:24 UTC3132
    let build = |request: http::Request<Vec<u8>>| -> Result<http::Request<Vec<u8>>, CommandError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC3133
        let mut request = request;
96fdf90aacd6atagen2026-09-08 08:24 UTC3134
        if let Some(token) = token.as_deref() {
96fdf90aacd6atagen2026-09-08 08:24 UTC3135
            request.headers_mut().insert(
96fdf90aacd6atagen2026-09-08 08:24 UTC3136
                http::header::AUTHORIZATION,
96fdf90aacd6atagen2026-09-08 08:24 UTC3137
                format!("Bearer {token}").parse().map_err(|_| {
96fdf90aacd6atagen2026-09-08 08:24 UTC3138
                    CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC3139
                        CommandErrorKind::Unauthenticated,
96fdf90aacd6atagen2026-09-08 08:24 UTC3140
                        "stored access token is invalid",
96fdf90aacd6atagen2026-09-08 08:24 UTC3141
                    )
96fdf90aacd6atagen2026-09-08 08:24 UTC3142
                })?,
96fdf90aacd6atagen2026-09-08 08:24 UTC3143
            );
96fdf90aacd6atagen2026-09-08 08:24 UTC3144
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3145
        Ok(request)
96fdf90aacd6atagen2026-09-08 08:24 UTC3146
    };
96fdf90aacd6atagen2026-09-08 08:24 UTC3147
    match &request.source {
96fdf90aacd6atagen2026-09-08 08:24 UTC3148
        matrix_sdk::ruma::events::room::MediaSource::Encrypted(file) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3149
            if use_auth {
96fdf90aacd6atagen2026-09-08 08:24 UTC3150
                let endpoint = authenticated_media::get_content::v1::Request::from_uri(&file.url)
96fdf90aacd6atagen2026-09-08 08:24 UTC3151
                    .map_err(|source| {
96fdf90aacd6atagen2026-09-08 08:24 UTC3152
                    CommandError::with_source(
96fdf90aacd6atagen2026-09-08 08:24 UTC3153
                        CommandErrorKind::Other,
96fdf90aacd6atagen2026-09-08 08:24 UTC3154
                        "media URI is invalid",
96fdf90aacd6atagen2026-09-08 08:24 UTC3155
                        source,
96fdf90aacd6atagen2026-09-08 08:24 UTC3156
                    )
96fdf90aacd6atagen2026-09-08 08:24 UTC3157
                })?;
96fdf90aacd6atagen2026-09-08 08:24 UTC3158
                endpoint
96fdf90aacd6atagen2026-09-08 08:24 UTC3159
                    .try_into_http_request::<Vec<u8>>(
96fdf90aacd6atagen2026-09-08 08:24 UTC3160
                        &base,
96fdf90aacd6atagen2026-09-08 08:24 UTC3161
                        SendAccessToken::IfRequired(token.as_deref().unwrap_or("")),
96fdf90aacd6atagen2026-09-08 08:24 UTC3162
                        Cow::Borrowed(&versions),
96fdf90aacd6atagen2026-09-08 08:24 UTC3163
                    )
96fdf90aacd6atagen2026-09-08 08:24 UTC3164
                    .map_err(|_| {
96fdf90aacd6atagen2026-09-08 08:24 UTC3165
                        CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC3166
                            CommandErrorKind::Unauthenticated,
96fdf90aacd6atagen2026-09-08 08:24 UTC3167
                            "media request could not be authorized",
96fdf90aacd6atagen2026-09-08 08:24 UTC3168
                        )
96fdf90aacd6atagen2026-09-08 08:24 UTC3169
                    })
96fdf90aacd6atagen2026-09-08 08:24 UTC3170
                    .and_then(build)
96fdf90aacd6atagen2026-09-08 08:24 UTC3171
            } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC3172
                #[allow(deprecated)]
96fdf90aacd6atagen2026-09-08 08:24 UTC3173
                let endpoint =
96fdf90aacd6atagen2026-09-08 08:24 UTC3174
                    media::get_content::v3::Request::from_url(&file.url).map_err(|source| {
96fdf90aacd6atagen2026-09-08 08:24 UTC3175
                        CommandError::with_source(
96fdf90aacd6atagen2026-09-08 08:24 UTC3176
                            CommandErrorKind::Other,
96fdf90aacd6atagen2026-09-08 08:24 UTC3177
                            "media URI is invalid",
96fdf90aacd6atagen2026-09-08 08:24 UTC3178
                            source,
96fdf90aacd6atagen2026-09-08 08:24 UTC3179
                        )
96fdf90aacd6atagen2026-09-08 08:24 UTC3180
                    })?;
96fdf90aacd6atagen2026-09-08 08:24 UTC3181
                endpoint
96fdf90aacd6atagen2026-09-08 08:24 UTC3182
                    .try_into_http_request::<Vec<u8>>(
96fdf90aacd6atagen2026-09-08 08:24 UTC3183
                        &base,
96fdf90aacd6atagen2026-09-08 08:24 UTC3184
                        SendAccessToken::None,
96fdf90aacd6atagen2026-09-08 08:24 UTC3185
                        Cow::Borrowed(&versions),
96fdf90aacd6atagen2026-09-08 08:24 UTC3186
                    )
96fdf90aacd6atagen2026-09-08 08:24 UTC3187
                    .map_err(|_| {
96fdf90aacd6atagen2026-09-08 08:24 UTC3188
                        CommandError::new(CommandErrorKind::Other, "media request is invalid")
96fdf90aacd6atagen2026-09-08 08:24 UTC3189
                    })
96fdf90aacd6atagen2026-09-08 08:24 UTC3190
                    .and_then(build)
96fdf90aacd6atagen2026-09-08 08:24 UTC3191
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC3192
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3193
        matrix_sdk::ruma::events::room::MediaSource::Plain(uri) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3194
            if let MediaFormat::Thumbnail(settings) = &request.format {
96fdf90aacd6atagen2026-09-08 08:24 UTC3195
                if use_auth {
96fdf90aacd6atagen2026-09-08 08:24 UTC3196
                    let mut endpoint =
96fdf90aacd6atagen2026-09-08 08:24 UTC3197
                        authenticated_media::get_content_thumbnail::v1::Request::from_uri(
96fdf90aacd6atagen2026-09-08 08:24 UTC3198
                            uri,
96fdf90aacd6atagen2026-09-08 08:24 UTC3199
                            settings.width,
96fdf90aacd6atagen2026-09-08 08:24 UTC3200
                            settings.height,
96fdf90aacd6atagen2026-09-08 08:24 UTC3201
                        )
96fdf90aacd6atagen2026-09-08 08:24 UTC3202
                        .map_err(|source| {
96fdf90aacd6atagen2026-09-08 08:24 UTC3203
                            CommandError::with_source(
96fdf90aacd6atagen2026-09-08 08:24 UTC3204
                                CommandErrorKind::Other,
96fdf90aacd6atagen2026-09-08 08:24 UTC3205
                                "media URI is invalid",
96fdf90aacd6atagen2026-09-08 08:24 UTC3206
                                source,
96fdf90aacd6atagen2026-09-08 08:24 UTC3207
                            )
96fdf90aacd6atagen2026-09-08 08:24 UTC3208
                        })?;
96fdf90aacd6atagen2026-09-08 08:24 UTC3209
                    endpoint.method = Some(settings.method.clone());
96fdf90aacd6atagen2026-09-08 08:24 UTC3210
                    endpoint.animated = Some(settings.animated);
96fdf90aacd6atagen2026-09-08 08:24 UTC3211
                    endpoint
96fdf90aacd6atagen2026-09-08 08:24 UTC3212
                        .try_into_http_request::<Vec<u8>>(
96fdf90aacd6atagen2026-09-08 08:24 UTC3213
                            &base,
96fdf90aacd6atagen2026-09-08 08:24 UTC3214
                            SendAccessToken::IfRequired(token.as_deref().unwrap_or("")),
96fdf90aacd6atagen2026-09-08 08:24 UTC3215
                            Cow::Borrowed(&versions),
96fdf90aacd6atagen2026-09-08 08:24 UTC3216
                        )
96fdf90aacd6atagen2026-09-08 08:24 UTC3217
                        .map_err(|_| {
96fdf90aacd6atagen2026-09-08 08:24 UTC3218
                            CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC3219
                                CommandErrorKind::Unauthenticated,
96fdf90aacd6atagen2026-09-08 08:24 UTC3220
                                "media request could not be authorized",
96fdf90aacd6atagen2026-09-08 08:24 UTC3221
                            )
96fdf90aacd6atagen2026-09-08 08:24 UTC3222
                        })
96fdf90aacd6atagen2026-09-08 08:24 UTC3223
                        .and_then(build)
96fdf90aacd6atagen2026-09-08 08:24 UTC3224
                } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC3225
                    #[allow(deprecated)]
96fdf90aacd6atagen2026-09-08 08:24 UTC3226
                    let endpoint = {
96fdf90aacd6atagen2026-09-08 08:24 UTC3227
                        let mut endpoint = media::get_content_thumbnail::v3::Request::from_url(
96fdf90aacd6atagen2026-09-08 08:24 UTC3228
                            uri,
96fdf90aacd6atagen2026-09-08 08:24 UTC3229
                            settings.width,
96fdf90aacd6atagen2026-09-08 08:24 UTC3230
                            settings.height,
96fdf90aacd6atagen2026-09-08 08:24 UTC3231
                        )
96fdf90aacd6atagen2026-09-08 08:24 UTC3232
                        .map_err(|source| {
96fdf90aacd6atagen2026-09-08 08:24 UTC3233
                            CommandError::with_source(
96fdf90aacd6atagen2026-09-08 08:24 UTC3234
                                CommandErrorKind::Other,
96fdf90aacd6atagen2026-09-08 08:24 UTC3235
                                "media URI is invalid",
96fdf90aacd6atagen2026-09-08 08:24 UTC3236
                                source,
96fdf90aacd6atagen2026-09-08 08:24 UTC3237
                            )
96fdf90aacd6atagen2026-09-08 08:24 UTC3238
                        })?;
96fdf90aacd6atagen2026-09-08 08:24 UTC3239
                        endpoint.method = Some(settings.method.clone());
96fdf90aacd6atagen2026-09-08 08:24 UTC3240
                        endpoint.animated = Some(settings.animated);
96fdf90aacd6atagen2026-09-08 08:24 UTC3241
                        endpoint
96fdf90aacd6atagen2026-09-08 08:24 UTC3242
                    };
96fdf90aacd6atagen2026-09-08 08:24 UTC3243
                    endpoint
96fdf90aacd6atagen2026-09-08 08:24 UTC3244
                        .try_into_http_request::<Vec<u8>>(
96fdf90aacd6atagen2026-09-08 08:24 UTC3245
                            &base,
96fdf90aacd6atagen2026-09-08 08:24 UTC3246
                            SendAccessToken::None,
96fdf90aacd6atagen2026-09-08 08:24 UTC3247
                            Cow::Borrowed(&versions),
96fdf90aacd6atagen2026-09-08 08:24 UTC3248
                        )
96fdf90aacd6atagen2026-09-08 08:24 UTC3249
                        .map_err(|_| {
96fdf90aacd6atagen2026-09-08 08:24 UTC3250
                            CommandError::new(CommandErrorKind::Other, "media request is invalid")
96fdf90aacd6atagen2026-09-08 08:24 UTC3251
                        })
96fdf90aacd6atagen2026-09-08 08:24 UTC3252
                        .and_then(build)
96fdf90aacd6atagen2026-09-08 08:24 UTC3253
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC3254
            } else if use_auth {
96fdf90aacd6atagen2026-09-08 08:24 UTC3255
                let endpoint = authenticated_media::get_content::v1::Request::from_uri(uri)
96fdf90aacd6atagen2026-09-08 08:24 UTC3256
                    .map_err(|source| {
96fdf90aacd6atagen2026-09-08 08:24 UTC3257
                        CommandError::with_source(
96fdf90aacd6atagen2026-09-08 08:24 UTC3258
                            CommandErrorKind::Other,
96fdf90aacd6atagen2026-09-08 08:24 UTC3259
                            "media URI is invalid",
96fdf90aacd6atagen2026-09-08 08:24 UTC3260
                            source,
96fdf90aacd6atagen2026-09-08 08:24 UTC3261
                        )
96fdf90aacd6atagen2026-09-08 08:24 UTC3262
                    })?;
96fdf90aacd6atagen2026-09-08 08:24 UTC3263
                endpoint
96fdf90aacd6atagen2026-09-08 08:24 UTC3264
                    .try_into_http_request::<Vec<u8>>(
96fdf90aacd6atagen2026-09-08 08:24 UTC3265
                        &base,
96fdf90aacd6atagen2026-09-08 08:24 UTC3266
                        SendAccessToken::IfRequired(token.as_deref().unwrap_or("")),
96fdf90aacd6atagen2026-09-08 08:24 UTC3267
                        Cow::Borrowed(&versions),
96fdf90aacd6atagen2026-09-08 08:24 UTC3268
                    )
96fdf90aacd6atagen2026-09-08 08:24 UTC3269
                    .map_err(|_| {
96fdf90aacd6atagen2026-09-08 08:24 UTC3270
                        CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC3271
                            CommandErrorKind::Unauthenticated,
96fdf90aacd6atagen2026-09-08 08:24 UTC3272
                            "media request could not be authorized",
96fdf90aacd6atagen2026-09-08 08:24 UTC3273
                        )
96fdf90aacd6atagen2026-09-08 08:24 UTC3274
                    })
96fdf90aacd6atagen2026-09-08 08:24 UTC3275
                    .and_then(build)
96fdf90aacd6atagen2026-09-08 08:24 UTC3276
            } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC3277
                #[allow(deprecated)]
96fdf90aacd6atagen2026-09-08 08:24 UTC3278
                let endpoint =
96fdf90aacd6atagen2026-09-08 08:24 UTC3279
                    media::get_content::v3::Request::from_url(uri).map_err(|source| {
96fdf90aacd6atagen2026-09-08 08:24 UTC3280
                        CommandError::with_source(
96fdf90aacd6atagen2026-09-08 08:24 UTC3281
                            CommandErrorKind::Other,
96fdf90aacd6atagen2026-09-08 08:24 UTC3282
                            "media URI is invalid",
96fdf90aacd6atagen2026-09-08 08:24 UTC3283
                            source,
96fdf90aacd6atagen2026-09-08 08:24 UTC3284
                        )
96fdf90aacd6atagen2026-09-08 08:24 UTC3285
                    })?;
96fdf90aacd6atagen2026-09-08 08:24 UTC3286
                endpoint
96fdf90aacd6atagen2026-09-08 08:24 UTC3287
                    .try_into_http_request::<Vec<u8>>(
96fdf90aacd6atagen2026-09-08 08:24 UTC3288
                        &base,
96fdf90aacd6atagen2026-09-08 08:24 UTC3289
                        SendAccessToken::None,
96fdf90aacd6atagen2026-09-08 08:24 UTC3290
                        Cow::Borrowed(&versions),
96fdf90aacd6atagen2026-09-08 08:24 UTC3291
                    )
96fdf90aacd6atagen2026-09-08 08:24 UTC3292
                    .map_err(|_| {
96fdf90aacd6atagen2026-09-08 08:24 UTC3293
                        CommandError::new(CommandErrorKind::Other, "media request is invalid")
96fdf90aacd6atagen2026-09-08 08:24 UTC3294
                    })
96fdf90aacd6atagen2026-09-08 08:24 UTC3295
                    .and_then(build)
96fdf90aacd6atagen2026-09-08 08:24 UTC3296
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC3297
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3298
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC3299
}
96fdf90aacd6atagen2026-09-08 08:24 UTC3300
96fdf90aacd6atagen2026-09-08 08:24 UTC3301
fn request_from_http(
fa9114fcf75fatagen2026-09-24 12:51 UTC3302
    client: &Client,
96fdf90aacd6atagen2026-09-08 08:24 UTC3303
    request: http::Request<Vec<u8>>,
96fdf90aacd6atagen2026-09-08 08:24 UTC3304
) -> Result<matrix_sdk::reqwest::Request, CommandError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC3305
    let (parts, _) = request.into_parts();
fa9114fcf75fatagen2026-09-24 12:51 UTC3306
    let mut builder = client.http_client().get(parts.uri.to_string());
96fdf90aacd6atagen2026-09-08 08:24 UTC3307
    for (name, value) in parts.headers.iter() {
96fdf90aacd6atagen2026-09-08 08:24 UTC3308
        builder = builder.header(name.as_str(), value.as_bytes());
96fdf90aacd6atagen2026-09-08 08:24 UTC3309
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC3310
    builder
96fdf90aacd6atagen2026-09-08 08:24 UTC3311
        .build()
96fdf90aacd6atagen2026-09-08 08:24 UTC3312
        .map_err(|_| CommandError::new(CommandErrorKind::Network, "media request is invalid"))
96fdf90aacd6atagen2026-09-08 08:24 UTC3313
}
96fdf90aacd6atagen2026-09-08 08:24 UTC3314
96fdf90aacd6atagen2026-09-08 08:24 UTC3315
impl RoomActor {
fa9114fcf75fatagen2026-09-24 12:51 UTC3316
    async fn finish(
fa9114fcf75fatagen2026-09-24 12:51 UTC3317
        &mut self,
fa9114fcf75fatagen2026-09-24 12:51 UTC3318
        id: CommandId,
fa9114fcf75fatagen2026-09-24 12:51 UTC3319
        outcome: TimelineOutcome,
fa9114fcf75fatagen2026-09-24 12:51 UTC3320
    ) -> Result<(), SendEventError> {
fa9114fcf75fatagen2026-09-24 12:51 UTC3321
        match outcome {
fa9114fcf75fatagen2026-09-24 12:51 UTC3322
            TimelineOutcome::Done(result) => {
fa9114fcf75fatagen2026-09-24 12:51 UTC3323
                emit_command_result(&self.events, id, CommandKind::Timeline, result).await
fa9114fcf75fatagen2026-09-24 12:51 UTC3324
            }
fa9114fcf75fatagen2026-09-24 12:51 UTC3325
            TimelineOutcome::Background(work) => {
fa9114fcf75fatagen2026-09-24 12:51 UTC3326
                let task = self.background.spawn(async move { (id, work.await) });
fa9114fcf75fatagen2026-09-24 12:51 UTC3327
                self.background_ids.insert(task.id(), id);
fa9114fcf75fatagen2026-09-24 12:51 UTC3328
                Ok(())
fa9114fcf75fatagen2026-09-24 12:51 UTC3329
            }
fa9114fcf75fatagen2026-09-24 12:51 UTC3330
        }
fa9114fcf75fatagen2026-09-24 12:51 UTC3331
    }
fa9114fcf75fatagen2026-09-24 12:51 UTC3332
fa9114fcf75fatagen2026-09-24 12:51 UTC3333
    async fn reap(&mut self, completed: BackgroundResult) -> Result<(), SendEventError> {
fa9114fcf75fatagen2026-09-24 12:51 UTC3334
        let (id, result) = match completed {
fa9114fcf75fatagen2026-09-24 12:51 UTC3335
            Ok((task_id, completed)) => {
fa9114fcf75fatagen2026-09-24 12:51 UTC3336
                self.background_ids.remove(&task_id);
fa9114fcf75fatagen2026-09-24 12:51 UTC3337
                completed
fa9114fcf75fatagen2026-09-24 12:51 UTC3338
            }
fa9114fcf75fatagen2026-09-24 12:51 UTC3339
            Err(error) => {
fa9114fcf75fatagen2026-09-24 12:51 UTC3340
                let Some(id) = self.background_ids.remove(&error.id()) else {
fa9114fcf75fatagen2026-09-24 12:51 UTC3341
                    return Ok(());
fa9114fcf75fatagen2026-09-24 12:51 UTC3342
                };
fa9114fcf75fatagen2026-09-24 12:51 UTC3343
                (
fa9114fcf75fatagen2026-09-24 12:51 UTC3344
                    id,
fa9114fcf75fatagen2026-09-24 12:51 UTC3345
                    Err(CommandError::new(
fa9114fcf75fatagen2026-09-24 12:51 UTC3346
                        CommandErrorKind::Other,
fa9114fcf75fatagen2026-09-24 12:51 UTC3347
                        "timeline operation panicked",
fa9114fcf75fatagen2026-09-24 12:51 UTC3348
                    )),
fa9114fcf75fatagen2026-09-24 12:51 UTC3349
                )
fa9114fcf75fatagen2026-09-24 12:51 UTC3350
            }
fa9114fcf75fatagen2026-09-24 12:51 UTC3351
        };
fa9114fcf75fatagen2026-09-24 12:51 UTC3352
        emit_command_result(&self.events, id, CommandKind::Timeline, result).await
fa9114fcf75fatagen2026-09-24 12:51 UTC3353
    }
fa9114fcf75fatagen2026-09-24 12:51 UTC3354
96fdf90aacd6atagen2026-09-08 08:24 UTC3355
    async fn run(mut self, current_user_id: Option<ruma::OwnedUserId>) {
96fdf90aacd6atagen2026-09-08 08:24 UTC3356
        let timeline = match self
96fdf90aacd6atagen2026-09-08 08:24 UTC3357
            .room
96fdf90aacd6atagen2026-09-08 08:24 UTC3358
            .timeline_builder()
96fdf90aacd6atagen2026-09-08 08:24 UTC3359
            .with_focus(TimelineFocus::Live {
96fdf90aacd6atagen2026-09-08 08:24 UTC3360
                hide_threaded_events: true,
96fdf90aacd6atagen2026-09-08 08:24 UTC3361
            })
96fdf90aacd6atagen2026-09-08 08:24 UTC3362
            .track_read_marker_and_receipts(TimelineReadReceiptTracking::AllEvents)
96fdf90aacd6atagen2026-09-08 08:24 UTC3363
            .build()
96fdf90aacd6atagen2026-09-08 08:24 UTC3364
            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC3365
        {
96fdf90aacd6atagen2026-09-08 08:24 UTC3366
            Ok(timeline) => timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC3367
            Err(error) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3368
                tracing::warn!(error = %error, room_id = %self.room_id, "room actor could not build its timeline");
96fdf90aacd6atagen2026-09-08 08:24 UTC3369
                let message = format!("room timeline build failed: {error}");
96fdf90aacd6atagen2026-09-08 08:24 UTC3370
                for id in self.open_ids.drain(..) {
96fdf90aacd6atagen2026-09-08 08:24 UTC3371
                    let _ = emit_command_result(
96fdf90aacd6atagen2026-09-08 08:24 UTC3372
                        &self.events,
96fdf90aacd6atagen2026-09-08 08:24 UTC3373
                        id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3374
                        CommandKind::Room,
96fdf90aacd6atagen2026-09-08 08:24 UTC3375
                        Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC3376
                            CommandErrorKind::Timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC3377
                            message.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC3378
                        )),
96fdf90aacd6atagen2026-09-08 08:24 UTC3379
                    )
96fdf90aacd6atagen2026-09-08 08:24 UTC3380
                    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC3381
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC3382
                self.receiver.close();
96fdf90aacd6atagen2026-09-08 08:24 UTC3383
                while let Some(request) = self.receiver.recv().await {
96fdf90aacd6atagen2026-09-08 08:24 UTC3384
                    let (id, command) = match request {
96fdf90aacd6atagen2026-09-08 08:24 UTC3385
                        RoomActorRequest::Opened { id } | RoomActorRequest::Close { id } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3386
                            (id, CommandKind::Room)
96fdf90aacd6atagen2026-09-08 08:24 UTC3387
                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3388
                        RoomActorRequest::Execute { id, .. } => (id, CommandKind::Timeline),
96fdf90aacd6atagen2026-09-08 08:24 UTC3389
                    };
96fdf90aacd6atagen2026-09-08 08:24 UTC3390
                    let _ = emit_command_result(
96fdf90aacd6atagen2026-09-08 08:24 UTC3391
                        &self.events,
96fdf90aacd6atagen2026-09-08 08:24 UTC3392
                        id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3393
                        command,
96fdf90aacd6atagen2026-09-08 08:24 UTC3394
                        Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC3395
                            CommandErrorKind::Timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC3396
                            "room timeline build failed",
96fdf90aacd6atagen2026-09-08 08:24 UTC3397
                        )),
96fdf90aacd6atagen2026-09-08 08:24 UTC3398
                    )
96fdf90aacd6atagen2026-09-08 08:24 UTC3399
                    .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC3400
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC3401
                return;
96fdf90aacd6atagen2026-09-08 08:24 UTC3402
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC3403
        };
96fdf90aacd6atagen2026-09-08 08:24 UTC3404
        let room_timeline_id = TimelineId::Room(self.room_id.clone());
96fdf90aacd6atagen2026-09-08 08:24 UTC3405
        let mut room_timeline = match TimelineSession::start(
96fdf90aacd6atagen2026-09-08 08:24 UTC3406
            room_timeline_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3407
            timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC3408
            self.events.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC3409
            self.media.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC3410
            current_user_id.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC3411
            self.initial_history_limit,
96fdf90aacd6atagen2026-09-08 08:24 UTC3412
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC3413
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC3414
        {
96fdf90aacd6atagen2026-09-08 08:24 UTC3415
            Ok(timeline) => timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC3416
            Err(_) => return,
96fdf90aacd6atagen2026-09-08 08:24 UTC3417
        };
96fdf90aacd6atagen2026-09-08 08:24 UTC3418
        let _ = self.engine_events.send(RoomActorEvent::Ready {
96fdf90aacd6atagen2026-09-08 08:24 UTC3419
            room_id: self.room_id.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC3420
            generation: self.generation,
96fdf90aacd6atagen2026-09-08 08:24 UTC3421
        });
96fdf90aacd6atagen2026-09-08 08:24 UTC3422
        for id in self.open_ids.drain(..) {
96fdf90aacd6atagen2026-09-08 08:24 UTC3423
            if emit_command_result(&self.events, id, CommandKind::Room, Ok(()))
96fdf90aacd6atagen2026-09-08 08:24 UTC3424
                .await
96fdf90aacd6atagen2026-09-08 08:24 UTC3425
                .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC3426
            {
96fdf90aacd6atagen2026-09-08 08:24 UTC3427
                return;
96fdf90aacd6atagen2026-09-08 08:24 UTC3428
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC3429
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3430
96fdf90aacd6atagen2026-09-08 08:24 UTC3431
        let (typing_guard, mut typing_rx) = self.room.subscribe_to_typing_notifications();
96fdf90aacd6atagen2026-09-08 08:24 UTC3432
        let typing_sender = self.events.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC3433
        let typing_room_id = self.room_id.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC3434
        let mut room_tasks = JoinSet::new();
96fdf90aacd6atagen2026-09-08 08:24 UTC3435
        room_tasks.spawn(async move {
96fdf90aacd6atagen2026-09-08 08:24 UTC3436
            loop {
96fdf90aacd6atagen2026-09-08 08:24 UTC3437
                match typing_rx.recv().await {
96fdf90aacd6atagen2026-09-08 08:24 UTC3438
                    Ok(users) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3439
                        let state = TypingState {
96fdf90aacd6atagen2026-09-08 08:24 UTC3440
                            room_id: typing_room_id.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC3441
                            users,
96fdf90aacd6atagen2026-09-08 08:24 UTC3442
                        };
96fdf90aacd6atagen2026-09-08 08:24 UTC3443
                        if typing_sender
96fdf90aacd6atagen2026-09-08 08:24 UTC3444
                            .send(Event::TypingChanged(state))
96fdf90aacd6atagen2026-09-08 08:24 UTC3445
                            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC3446
                            .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC3447
                        {
96fdf90aacd6atagen2026-09-08 08:24 UTC3448
                            tracing::debug!(
96fdf90aacd6atagen2026-09-08 08:24 UTC3449
                                room_id = %typing_room_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3450
                                "typing watcher stopped because the event receiver closed"
96fdf90aacd6atagen2026-09-08 08:24 UTC3451
                            );
96fdf90aacd6atagen2026-09-08 08:24 UTC3452
                            break;
96fdf90aacd6atagen2026-09-08 08:24 UTC3453
                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3454
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC3455
                    Err(tokio::sync::broadcast::error::RecvError::Lagged(skipped)) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3456
                        tracing::warn!(room_id = %typing_room_id, skipped, "typing watcher lagged");
96fdf90aacd6atagen2026-09-08 08:24 UTC3457
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC3458
                    Err(tokio::sync::broadcast::error::RecvError::Closed) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3459
                        tracing::debug!(room_id = %typing_room_id, "typing watcher stopped");
96fdf90aacd6atagen2026-09-08 08:24 UTC3460
                        break;
96fdf90aacd6atagen2026-09-08 08:24 UTC3461
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC3462
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC3463
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC3464
        });
96fdf90aacd6atagen2026-09-08 08:24 UTC3465
96fdf90aacd6atagen2026-09-08 08:24 UTC3466
        enum QueuedThreadCommand {
96fdf90aacd6atagen2026-09-08 08:24 UTC3467
            Open {
96fdf90aacd6atagen2026-09-08 08:24 UTC3468
                id: CommandId,
96fdf90aacd6atagen2026-09-08 08:24 UTC3469
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC3470
            Execute {
96fdf90aacd6atagen2026-09-08 08:24 UTC3471
                id: CommandId,
96fdf90aacd6atagen2026-09-08 08:24 UTC3472
                command: TimelineCommand,
96fdf90aacd6atagen2026-09-08 08:24 UTC3473
            },
96fdf90aacd6atagen2026-09-08 08:24 UTC3474
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3475
96fdf90aacd6atagen2026-09-08 08:24 UTC3476
        struct OpeningThread {
96fdf90aacd6atagen2026-09-08 08:24 UTC3477
            generation: u64,
96fdf90aacd6atagen2026-09-08 08:24 UTC3478
            abort: AbortHandle,
96fdf90aacd6atagen2026-09-08 08:24 UTC3479
            task_id: TaskId,
96fdf90aacd6atagen2026-09-08 08:24 UTC3480
            queued: VecDeque<QueuedThreadCommand>,
96fdf90aacd6atagen2026-09-08 08:24 UTC3481
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3482
96fdf90aacd6atagen2026-09-08 08:24 UTC3483
        let mut thread_timelines = HashMap::<OwnedEventId, TimelineSession>::new();
96fdf90aacd6atagen2026-09-08 08:24 UTC3484
        let mut opening_threads = HashMap::<OwnedEventId, OpeningThread>::new();
96fdf90aacd6atagen2026-09-08 08:24 UTC3485
        let mut thread_task_ids = HashMap::<TaskId, OwnedEventId>::new();
96fdf90aacd6atagen2026-09-08 08:24 UTC3486
        let mut thread_open_tasks = JoinSet::new();
96fdf90aacd6atagen2026-09-08 08:24 UTC3487
        let mut next_thread_generation = 0_u64;
96fdf90aacd6atagen2026-09-08 08:24 UTC3488
        let mut close_id = None;
96fdf90aacd6atagen2026-09-08 08:24 UTC3489
        let thread_queue_cap = self.receiver.max_capacity();
96fdf90aacd6atagen2026-09-08 08:24 UTC3490
        let mut queued_thread_count = 0;
96fdf90aacd6atagen2026-09-08 08:24 UTC3491
fa9114fcf75fatagen2026-09-24 12:51 UTC3492
        let mut room_info = self.room.subscribe_info();
96fdf90aacd6atagen2026-09-08 08:24 UTC3493
        'actor: loop {
96fdf90aacd6atagen2026-09-08 08:24 UTC3494
            tokio::select! {
fa9114fcf75fatagen2026-09-24 12:51 UTC3495
                Some(info) = room_info.next() => {
fa9114fcf75fatagen2026-09-24 12:51 UTC3496
                    if matches!(info.state(), matrix_sdk::RoomState::Left | matrix_sdk::RoomState::Banned) {
fa9114fcf75fatagen2026-09-24 12:51 UTC3497
                        tracing::debug!(room_id = %self.room_id, "room actor stopped because we left the room");
fa9114fcf75fatagen2026-09-24 12:51 UTC3498
                        break 'actor;
fa9114fcf75fatagen2026-09-24 12:51 UTC3499
                    }
fa9114fcf75fatagen2026-09-24 12:51 UTC3500
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC3501
                request = self.receiver.recv() => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3502
                    let Some(request) = request else {
96fdf90aacd6atagen2026-09-08 08:24 UTC3503
                        tracing::debug!(room_id = %self.room_id, "room actor stopped because its request channel closed");
96fdf90aacd6atagen2026-09-08 08:24 UTC3504
                        break;
96fdf90aacd6atagen2026-09-08 08:24 UTC3505
                    };
96fdf90aacd6atagen2026-09-08 08:24 UTC3506
                    match request {
96fdf90aacd6atagen2026-09-08 08:24 UTC3507
                        RoomActorRequest::Opened { id } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3508
                            if emit_command_result(
96fdf90aacd6atagen2026-09-08 08:24 UTC3509
                                &self.events,
96fdf90aacd6atagen2026-09-08 08:24 UTC3510
                                id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3511
                                CommandKind::Room,
96fdf90aacd6atagen2026-09-08 08:24 UTC3512
                                Ok(()),
96fdf90aacd6atagen2026-09-08 08:24 UTC3513
                            )
96fdf90aacd6atagen2026-09-08 08:24 UTC3514
                            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC3515
                            .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC3516
                            {
96fdf90aacd6atagen2026-09-08 08:24 UTC3517
                                break;
96fdf90aacd6atagen2026-09-08 08:24 UTC3518
                            }
96fdf90aacd6atagen2026-09-08 08:24 UTC3519
                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3520
                        RoomActorRequest::Execute { id, command } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3521
                            match command {
96fdf90aacd6atagen2026-09-08 08:24 UTC3522
                                TimelineCommand::OpenThread { root_event_id, .. } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3523
                                    if thread_timelines.contains_key(&root_event_id) {
96fdf90aacd6atagen2026-09-08 08:24 UTC3524
                                        if emit_command_result(
96fdf90aacd6atagen2026-09-08 08:24 UTC3525
                                            &self.events,
96fdf90aacd6atagen2026-09-08 08:24 UTC3526
                                            id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3527
                                            CommandKind::Timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC3528
                                            Ok(()),
96fdf90aacd6atagen2026-09-08 08:24 UTC3529
                                        )
96fdf90aacd6atagen2026-09-08 08:24 UTC3530
                                        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC3531
                                        .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC3532
                                        {
96fdf90aacd6atagen2026-09-08 08:24 UTC3533
                                            break 'actor;
96fdf90aacd6atagen2026-09-08 08:24 UTC3534
                                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3535
                                    } else if let Some(opening) =
96fdf90aacd6atagen2026-09-08 08:24 UTC3536
                                        opening_threads.get_mut(&root_event_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC3537
                                    {
96fdf90aacd6atagen2026-09-08 08:24 UTC3538
                                        if queued_thread_count >= thread_queue_cap {
96fdf90aacd6atagen2026-09-08 08:24 UTC3539
                                            if emit_command_result(
96fdf90aacd6atagen2026-09-08 08:24 UTC3540
                                                &self.events,
96fdf90aacd6atagen2026-09-08 08:24 UTC3541
                                                id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3542
                                                CommandKind::Timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC3543
                                                Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC3544
                                                    CommandErrorKind::QueueFull,
96fdf90aacd6atagen2026-09-08 08:24 UTC3545
                                                    "thread command queue is full",
96fdf90aacd6atagen2026-09-08 08:24 UTC3546
                                                )),
96fdf90aacd6atagen2026-09-08 08:24 UTC3547
                                            )
96fdf90aacd6atagen2026-09-08 08:24 UTC3548
                                            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC3549
                                            .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC3550
                                            {
96fdf90aacd6atagen2026-09-08 08:24 UTC3551
                                                break 'actor;
96fdf90aacd6atagen2026-09-08 08:24 UTC3552
                                            }
96fdf90aacd6atagen2026-09-08 08:24 UTC3553
                                        } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC3554
                                            opening
96fdf90aacd6atagen2026-09-08 08:24 UTC3555
                                                .queued
96fdf90aacd6atagen2026-09-08 08:24 UTC3556
                                                .push_back(QueuedThreadCommand::Open { id });
96fdf90aacd6atagen2026-09-08 08:24 UTC3557
                                            queued_thread_count += 1;
96fdf90aacd6atagen2026-09-08 08:24 UTC3558
                                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3559
                                    } else if queued_thread_count >= thread_queue_cap {
96fdf90aacd6atagen2026-09-08 08:24 UTC3560
                                        if emit_command_result(
96fdf90aacd6atagen2026-09-08 08:24 UTC3561
                                            &self.events,
96fdf90aacd6atagen2026-09-08 08:24 UTC3562
                                            id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3563
                                            CommandKind::Timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC3564
                                            Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC3565
                                                CommandErrorKind::QueueFull,
96fdf90aacd6atagen2026-09-08 08:24 UTC3566
                                                "thread command queue is full",
96fdf90aacd6atagen2026-09-08 08:24 UTC3567
                                            )),
96fdf90aacd6atagen2026-09-08 08:24 UTC3568
                                        )
96fdf90aacd6atagen2026-09-08 08:24 UTC3569
                                        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC3570
                                        .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC3571
                                        {
96fdf90aacd6atagen2026-09-08 08:24 UTC3572
                                            break 'actor;
96fdf90aacd6atagen2026-09-08 08:24 UTC3573
                                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3574
                                    } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC3575
                                        let generation = next_thread_generation;
96fdf90aacd6atagen2026-09-08 08:24 UTC3576
                                        next_thread_generation =
96fdf90aacd6atagen2026-09-08 08:24 UTC3577
                                            next_thread_generation.wrapping_add(1);
96fdf90aacd6atagen2026-09-08 08:24 UTC3578
                                        let task_root = root_event_id.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC3579
                                        let room = self.room.clone();
96fdf90aacd6atagen2026-09-08 08:24 UTC3580
                                        let abort = thread_open_tasks.spawn(async move {
96fdf90aacd6atagen2026-09-08 08:24 UTC3581
                                            let result = room
96fdf90aacd6atagen2026-09-08 08:24 UTC3582
                                                .timeline_builder()
96fdf90aacd6atagen2026-09-08 08:24 UTC3583
                                                .with_focus(TimelineFocus::Thread {
96fdf90aacd6atagen2026-09-08 08:24 UTC3584
                                                    root_event_id: root_event_id.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC3585
                                                })
96fdf90aacd6atagen2026-09-08 08:24 UTC3586
                                                .track_read_marker_and_receipts(
96fdf90aacd6atagen2026-09-08 08:24 UTC3587
                                                    TimelineReadReceiptTracking::AllEvents,
96fdf90aacd6atagen2026-09-08 08:24 UTC3588
                                                )
96fdf90aacd6atagen2026-09-08 08:24 UTC3589
                                                .build()
96fdf90aacd6atagen2026-09-08 08:24 UTC3590
                                                .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC3591
                                            (root_event_id, generation, result)
96fdf90aacd6atagen2026-09-08 08:24 UTC3592
                                        });
96fdf90aacd6atagen2026-09-08 08:24 UTC3593
                                        let task_id = abort.id();
96fdf90aacd6atagen2026-09-08 08:24 UTC3594
                                        thread_task_ids.insert(task_id, task_root.clone());
96fdf90aacd6atagen2026-09-08 08:24 UTC3595
                                        opening_threads.insert(
96fdf90aacd6atagen2026-09-08 08:24 UTC3596
                                            task_root,
96fdf90aacd6atagen2026-09-08 08:24 UTC3597
                                            OpeningThread {
96fdf90aacd6atagen2026-09-08 08:24 UTC3598
                                                generation,
96fdf90aacd6atagen2026-09-08 08:24 UTC3599
                                                abort,
96fdf90aacd6atagen2026-09-08 08:24 UTC3600
                                                task_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3601
                                                queued: VecDeque::from([
96fdf90aacd6atagen2026-09-08 08:24 UTC3602
                                                    QueuedThreadCommand::Open { id },
96fdf90aacd6atagen2026-09-08 08:24 UTC3603
                                                ]),
96fdf90aacd6atagen2026-09-08 08:24 UTC3604
                                            },
96fdf90aacd6atagen2026-09-08 08:24 UTC3605
                                        );
96fdf90aacd6atagen2026-09-08 08:24 UTC3606
                                        queued_thread_count += 1;
96fdf90aacd6atagen2026-09-08 08:24 UTC3607
                                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC3608
                                }
96fdf90aacd6atagen2026-09-08 08:24 UTC3609
                                TimelineCommand::CloseThread { root_event_id, .. } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3610
                                    if let Some(timeline) = thread_timelines.remove(&root_event_id) {
96fdf90aacd6atagen2026-09-08 08:24 UTC3611
                                        timeline
96fdf90aacd6atagen2026-09-08 08:24 UTC3612
                                            .close(
96fdf90aacd6atagen2026-09-08 08:24 UTC3613
                                                CommandErrorKind::TimelineNotOpen,
96fdf90aacd6atagen2026-09-08 08:24 UTC3614
                                                "thread closed while pagination was in progress",
96fdf90aacd6atagen2026-09-08 08:24 UTC3615
                                            )
96fdf90aacd6atagen2026-09-08 08:24 UTC3616
                                            .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC3617
                                        if emit_command_result(
96fdf90aacd6atagen2026-09-08 08:24 UTC3618
                                            &self.events,
96fdf90aacd6atagen2026-09-08 08:24 UTC3619
                                            id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3620
                                            CommandKind::Timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC3621
                                            Ok(()),
96fdf90aacd6atagen2026-09-08 08:24 UTC3622
                                        )
96fdf90aacd6atagen2026-09-08 08:24 UTC3623
                                        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC3624
                                        .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC3625
                                        {
96fdf90aacd6atagen2026-09-08 08:24 UTC3626
                                            break 'actor;
96fdf90aacd6atagen2026-09-08 08:24 UTC3627
                                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3628
                                    } else if let Some(opening) =
96fdf90aacd6atagen2026-09-08 08:24 UTC3629
                                        opening_threads.remove(&root_event_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC3630
                                    {
96fdf90aacd6atagen2026-09-08 08:24 UTC3631
                                        thread_task_ids.remove(&opening.task_id);
96fdf90aacd6atagen2026-09-08 08:24 UTC3632
                                        queued_thread_count -= opening.queued.len();
96fdf90aacd6atagen2026-09-08 08:24 UTC3633
                                        opening.abort.abort();
96fdf90aacd6atagen2026-09-08 08:24 UTC3634
                                        for queued in opening.queued {
96fdf90aacd6atagen2026-09-08 08:24 UTC3635
                                            let queued_id = match queued {
96fdf90aacd6atagen2026-09-08 08:24 UTC3636
                                                QueuedThreadCommand::Open { id } => id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3637
                                                QueuedThreadCommand::Execute { id, .. } => id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3638
                                            };
96fdf90aacd6atagen2026-09-08 08:24 UTC3639
                                            let _ = emit_command_result(
96fdf90aacd6atagen2026-09-08 08:24 UTC3640
                                                &self.events,
96fdf90aacd6atagen2026-09-08 08:24 UTC3641
                                                queued_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3642
                                                CommandKind::Timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC3643
                                                Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC3644
                                                    CommandErrorKind::TimelineNotOpen,
96fdf90aacd6atagen2026-09-08 08:24 UTC3645
                                                    "thread was closed before it finished opening",
96fdf90aacd6atagen2026-09-08 08:24 UTC3646
                                                )),
96fdf90aacd6atagen2026-09-08 08:24 UTC3647
                                            )
96fdf90aacd6atagen2026-09-08 08:24 UTC3648
                                            .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC3649
                                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3650
                                        if emit_command_result(
96fdf90aacd6atagen2026-09-08 08:24 UTC3651
                                            &self.events,
96fdf90aacd6atagen2026-09-08 08:24 UTC3652
                                            id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3653
                                            CommandKind::Timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC3654
                                            Ok(()),
96fdf90aacd6atagen2026-09-08 08:24 UTC3655
                                        )
96fdf90aacd6atagen2026-09-08 08:24 UTC3656
                                        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC3657
                                        .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC3658
                                        {
96fdf90aacd6atagen2026-09-08 08:24 UTC3659
                                            break 'actor;
96fdf90aacd6atagen2026-09-08 08:24 UTC3660
                                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3661
                                    } else if emit_command_result(
96fdf90aacd6atagen2026-09-08 08:24 UTC3662
                                        &self.events,
96fdf90aacd6atagen2026-09-08 08:24 UTC3663
                                        id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3664
                                        CommandKind::Timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC3665
                                        Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC3666
                                            CommandErrorKind::TimelineNotOpen,
96fdf90aacd6atagen2026-09-08 08:24 UTC3667
                                            "thread timeline is not open",
96fdf90aacd6atagen2026-09-08 08:24 UTC3668
                                        )),
96fdf90aacd6atagen2026-09-08 08:24 UTC3669
                                    )
96fdf90aacd6atagen2026-09-08 08:24 UTC3670
                                    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC3671
                                    .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC3672
                                    {
96fdf90aacd6atagen2026-09-08 08:24 UTC3673
                                        break 'actor;
96fdf90aacd6atagen2026-09-08 08:24 UTC3674
                                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC3675
                                }
96fdf90aacd6atagen2026-09-08 08:24 UTC3676
                                TimelineCommand::Paginate { timeline_id, limit } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3677
                                    match timeline_id {
96fdf90aacd6atagen2026-09-08 08:24 UTC3678
                                        TimelineId::Room(_) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3679
                                            room_timeline.paginate(limit, Some(id)).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC3680
                                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3681
                                        TimelineId::Thread { room_id, root_event_id } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3682
                                            if let Some(timeline) =
96fdf90aacd6atagen2026-09-08 08:24 UTC3683
                                                thread_timelines.get_mut(&root_event_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC3684
                                            {
96fdf90aacd6atagen2026-09-08 08:24 UTC3685
                                                timeline.paginate(limit, Some(id)).await;
96fdf90aacd6atagen2026-09-08 08:24 UTC3686
                                            } else if let Some(opening) =
96fdf90aacd6atagen2026-09-08 08:24 UTC3687
                                                opening_threads.get_mut(&root_event_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC3688
                                            {
96fdf90aacd6atagen2026-09-08 08:24 UTC3689
                                                if queued_thread_count >= thread_queue_cap {
96fdf90aacd6atagen2026-09-08 08:24 UTC3690
                                                    if emit_command_result(
96fdf90aacd6atagen2026-09-08 08:24 UTC3691
                                                        &self.events,
96fdf90aacd6atagen2026-09-08 08:24 UTC3692
                                                        id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3693
                                                        CommandKind::Timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC3694
                                                        Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC3695
                                                            CommandErrorKind::QueueFull,
96fdf90aacd6atagen2026-09-08 08:24 UTC3696
                                                            "thread command queue is full",
96fdf90aacd6atagen2026-09-08 08:24 UTC3697
                                                        )),
96fdf90aacd6atagen2026-09-08 08:24 UTC3698
                                                    )
96fdf90aacd6atagen2026-09-08 08:24 UTC3699
                                                    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC3700
                                                    .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC3701
                                                    {
96fdf90aacd6atagen2026-09-08 08:24 UTC3702
                                                        break 'actor;
96fdf90aacd6atagen2026-09-08 08:24 UTC3703
                                                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC3704
                                                } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC3705
                                                    opening.queued.push_back(
96fdf90aacd6atagen2026-09-08 08:24 UTC3706
                                                        QueuedThreadCommand::Execute {
96fdf90aacd6atagen2026-09-08 08:24 UTC3707
                                                            id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3708
                                                            command:
96fdf90aacd6atagen2026-09-08 08:24 UTC3709
                                                                TimelineCommand::Paginate {
96fdf90aacd6atagen2026-09-08 08:24 UTC3710
                                                                    timeline_id:
96fdf90aacd6atagen2026-09-08 08:24 UTC3711
                                                                        TimelineId::Thread {
96fdf90aacd6atagen2026-09-08 08:24 UTC3712
                                                                            room_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3713
                                                                            root_event_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3714
                                                                        },
96fdf90aacd6atagen2026-09-08 08:24 UTC3715
                                                                    limit,
96fdf90aacd6atagen2026-09-08 08:24 UTC3716
                                                                },
96fdf90aacd6atagen2026-09-08 08:24 UTC3717
                                                        },
96fdf90aacd6atagen2026-09-08 08:24 UTC3718
                                                    );
96fdf90aacd6atagen2026-09-08 08:24 UTC3719
                                                    queued_thread_count += 1;
96fdf90aacd6atagen2026-09-08 08:24 UTC3720
                                                }
96fdf90aacd6atagen2026-09-08 08:24 UTC3721
                                            } else if emit_command_result(
96fdf90aacd6atagen2026-09-08 08:24 UTC3722
                                                &self.events,
96fdf90aacd6atagen2026-09-08 08:24 UTC3723
                                                id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3724
                                                CommandKind::Timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC3725
                                                Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC3726
                                                    CommandErrorKind::TimelineNotOpen,
96fdf90aacd6atagen2026-09-08 08:24 UTC3727
                                                    "timeline is not open",
96fdf90aacd6atagen2026-09-08 08:24 UTC3728
                                                )),
96fdf90aacd6atagen2026-09-08 08:24 UTC3729
                                            )
96fdf90aacd6atagen2026-09-08 08:24 UTC3730
                                            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC3731
                                            .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC3732
                                            {
96fdf90aacd6atagen2026-09-08 08:24 UTC3733
                                                break 'actor;
96fdf90aacd6atagen2026-09-08 08:24 UTC3734
                                            }
96fdf90aacd6atagen2026-09-08 08:24 UTC3735
                                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3736
                                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC3737
                                }
96fdf90aacd6atagen2026-09-08 08:24 UTC3738
                                command => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3739
                                    let thread_target = match command
96fdf90aacd6atagen2026-09-08 08:24 UTC3740
                                        .timeline_id()
96fdf90aacd6atagen2026-09-08 08:24 UTC3741
                                        .expect("non-lifecycle timeline command has a timeline id")
96fdf90aacd6atagen2026-09-08 08:24 UTC3742
                                    {
96fdf90aacd6atagen2026-09-08 08:24 UTC3743
                                        TimelineId::Room(_) => None,
96fdf90aacd6atagen2026-09-08 08:24 UTC3744
                                        TimelineId::Thread { root_event_id, .. } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3745
                                            Some(root_event_id.clone())
96fdf90aacd6atagen2026-09-08 08:24 UTC3746
                                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3747
                                    };
96fdf90aacd6atagen2026-09-08 08:24 UTC3748
                                    match thread_target {
96fdf90aacd6atagen2026-09-08 08:24 UTC3749
                                        None => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3750
                                            let result = execute_timeline_command(
96fdf90aacd6atagen2026-09-08 08:24 UTC3751
                                                &mut room_timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC3752
                                                command,
96fdf90aacd6atagen2026-09-08 08:24 UTC3753
                                                None,
96fdf90aacd6atagen2026-09-08 08:24 UTC3754
                                            )
96fdf90aacd6atagen2026-09-08 08:24 UTC3755
                                            .await;
fa9114fcf75fatagen2026-09-24 12:51 UTC3756
                                            if self.finish(id, result)
96fdf90aacd6atagen2026-09-08 08:24 UTC3757
                                            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC3758
                                            .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC3759
                                            {
96fdf90aacd6atagen2026-09-08 08:24 UTC3760
                                                tracing::debug!(room_id = %self.room_id, "room actor stopped because the event receiver closed");
96fdf90aacd6atagen2026-09-08 08:24 UTC3761
                                                break;
96fdf90aacd6atagen2026-09-08 08:24 UTC3762
                                            }
96fdf90aacd6atagen2026-09-08 08:24 UTC3763
                                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3764
                                        Some(root_event_id) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3765
                                            if let Some(timeline) =
96fdf90aacd6atagen2026-09-08 08:24 UTC3766
                                                thread_timelines.get_mut(&root_event_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC3767
                                            {
96fdf90aacd6atagen2026-09-08 08:24 UTC3768
                                                let thread_root =
96fdf90aacd6atagen2026-09-08 08:24 UTC3769
                                                    timeline.id.thread_root().cloned();
96fdf90aacd6atagen2026-09-08 08:24 UTC3770
                                                let result = execute_timeline_command(
96fdf90aacd6atagen2026-09-08 08:24 UTC3771
                                                    timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC3772
                                                    command,
96fdf90aacd6atagen2026-09-08 08:24 UTC3773
                                                    thread_root.as_ref(),
96fdf90aacd6atagen2026-09-08 08:24 UTC3774
                                                )
96fdf90aacd6atagen2026-09-08 08:24 UTC3775
                                                .await;
fa9114fcf75fatagen2026-09-24 12:51 UTC3776
                                                if self.finish(id, result)
96fdf90aacd6atagen2026-09-08 08:24 UTC3777
                                                .await
96fdf90aacd6atagen2026-09-08 08:24 UTC3778
                                                .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC3779
                                                {
96fdf90aacd6atagen2026-09-08 08:24 UTC3780
                                                    tracing::debug!(room_id = %self.room_id, "room actor stopped because the event receiver closed");
96fdf90aacd6atagen2026-09-08 08:24 UTC3781
                                                    break;
96fdf90aacd6atagen2026-09-08 08:24 UTC3782
                                                }
96fdf90aacd6atagen2026-09-08 08:24 UTC3783
                                            } else if let Some(opening) =
96fdf90aacd6atagen2026-09-08 08:24 UTC3784
                                                opening_threads.get_mut(&root_event_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC3785
                                            {
96fdf90aacd6atagen2026-09-08 08:24 UTC3786
                                                if queued_thread_count >= thread_queue_cap {
96fdf90aacd6atagen2026-09-08 08:24 UTC3787
                                                    if emit_command_result(
96fdf90aacd6atagen2026-09-08 08:24 UTC3788
                                                        &self.events,
96fdf90aacd6atagen2026-09-08 08:24 UTC3789
                                                        id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3790
                                                        CommandKind::Timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC3791
                                                        Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC3792
                                                            CommandErrorKind::QueueFull,
96fdf90aacd6atagen2026-09-08 08:24 UTC3793
                                                            "thread command queue is full",
96fdf90aacd6atagen2026-09-08 08:24 UTC3794
                                                        )),
96fdf90aacd6atagen2026-09-08 08:24 UTC3795
                                                    )
96fdf90aacd6atagen2026-09-08 08:24 UTC3796
                                                    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC3797
                                                    .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC3798
                                                    {
96fdf90aacd6atagen2026-09-08 08:24 UTC3799
                                                        break 'actor;
96fdf90aacd6atagen2026-09-08 08:24 UTC3800
                                                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC3801
                                                } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC3802
                                                    opening.queued.push_back(
96fdf90aacd6atagen2026-09-08 08:24 UTC3803
                                                        QueuedThreadCommand::Execute { id, command },
96fdf90aacd6atagen2026-09-08 08:24 UTC3804
                                                    );
96fdf90aacd6atagen2026-09-08 08:24 UTC3805
                                                    queued_thread_count += 1;
96fdf90aacd6atagen2026-09-08 08:24 UTC3806
                                                }
96fdf90aacd6atagen2026-09-08 08:24 UTC3807
                                            } else if emit_command_result(
96fdf90aacd6atagen2026-09-08 08:24 UTC3808
                                                &self.events,
96fdf90aacd6atagen2026-09-08 08:24 UTC3809
                                                id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3810
                                                CommandKind::Timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC3811
                                                Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC3812
                                                    CommandErrorKind::TimelineNotOpen,
96fdf90aacd6atagen2026-09-08 08:24 UTC3813
                                                    "timeline is not open",
96fdf90aacd6atagen2026-09-08 08:24 UTC3814
                                                )),
96fdf90aacd6atagen2026-09-08 08:24 UTC3815
                                            )
96fdf90aacd6atagen2026-09-08 08:24 UTC3816
                                            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC3817
                                            .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC3818
                                            {
96fdf90aacd6atagen2026-09-08 08:24 UTC3819
                                                tracing::debug!(room_id = %self.room_id, "room actor stopped because the event receiver closed");
96fdf90aacd6atagen2026-09-08 08:24 UTC3820
                                                break;
96fdf90aacd6atagen2026-09-08 08:24 UTC3821
                                            }
96fdf90aacd6atagen2026-09-08 08:24 UTC3822
                                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3823
                                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC3824
                                }
96fdf90aacd6atagen2026-09-08 08:24 UTC3825
                            }
96fdf90aacd6atagen2026-09-08 08:24 UTC3826
                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3827
                        RoomActorRequest::Close { id } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3828
                            close_id = Some(id);
96fdf90aacd6atagen2026-09-08 08:24 UTC3829
                            break 'actor;
96fdf90aacd6atagen2026-09-08 08:24 UTC3830
                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3831
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC3832
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC3833
                completed = thread_open_tasks.join_next(), if !thread_open_tasks.is_empty() => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3834
                    match completed {
96fdf90aacd6atagen2026-09-08 08:24 UTC3835
                        Some(Ok((root_event_id, generation, result))) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3836
                            if opening_threads
96fdf90aacd6atagen2026-09-08 08:24 UTC3837
                                .get(&root_event_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC3838
                                .is_none_or(|opening| opening.generation != generation)
96fdf90aacd6atagen2026-09-08 08:24 UTC3839
                            {
96fdf90aacd6atagen2026-09-08 08:24 UTC3840
                                continue;
96fdf90aacd6atagen2026-09-08 08:24 UTC3841
                            }
96fdf90aacd6atagen2026-09-08 08:24 UTC3842
                            let opening = opening_threads
96fdf90aacd6atagen2026-09-08 08:24 UTC3843
                                .remove(&root_event_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC3844
                                .expect("thread generation was checked");
96fdf90aacd6atagen2026-09-08 08:24 UTC3845
                            thread_task_ids.remove(&opening.task_id);
96fdf90aacd6atagen2026-09-08 08:24 UTC3846
                            queued_thread_count -= opening.queued.len();
96fdf90aacd6atagen2026-09-08 08:24 UTC3847
                            match result {
96fdf90aacd6atagen2026-09-08 08:24 UTC3848
                                Ok(timeline) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3849
                                    let timeline_id = TimelineId::thread(
96fdf90aacd6atagen2026-09-08 08:24 UTC3850
                                        self.room_id.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC3851
                                        root_event_id.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC3852
                                    );
96fdf90aacd6atagen2026-09-08 08:24 UTC3853
                                    match TimelineSession::start(
96fdf90aacd6atagen2026-09-08 08:24 UTC3854
                                        timeline_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3855
                                        timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC3856
                                        self.events.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC3857
                                        self.media.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC3858
                                        current_user_id.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC3859
                                        self.initial_history_limit,
96fdf90aacd6atagen2026-09-08 08:24 UTC3860
                                    )
96fdf90aacd6atagen2026-09-08 08:24 UTC3861
                                    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC3862
                                    {
96fdf90aacd6atagen2026-09-08 08:24 UTC3863
                                        Ok(session) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3864
                                            thread_timelines
96fdf90aacd6atagen2026-09-08 08:24 UTC3865
                                                .insert(root_event_id.clone(), session);
96fdf90aacd6atagen2026-09-08 08:24 UTC3866
                                            let timeline = thread_timelines
96fdf90aacd6atagen2026-09-08 08:24 UTC3867
                                                .get_mut(&root_event_id)
96fdf90aacd6atagen2026-09-08 08:24 UTC3868
                                                .expect("thread session was inserted");
96fdf90aacd6atagen2026-09-08 08:24 UTC3869
                                            for queued in opening.queued {
96fdf90aacd6atagen2026-09-08 08:24 UTC3870
                                                match queued {
96fdf90aacd6atagen2026-09-08 08:24 UTC3871
                                                    QueuedThreadCommand::Open { id } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3872
                                                        if emit_command_result(
96fdf90aacd6atagen2026-09-08 08:24 UTC3873
                                                            &self.events,
96fdf90aacd6atagen2026-09-08 08:24 UTC3874
                                                            id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3875
                                                            CommandKind::Timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC3876
                                                            Ok(()),
96fdf90aacd6atagen2026-09-08 08:24 UTC3877
                                                        )
96fdf90aacd6atagen2026-09-08 08:24 UTC3878
                                                        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC3879
                                                        .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC3880
                                                        {
96fdf90aacd6atagen2026-09-08 08:24 UTC3881
                                                            break 'actor;
96fdf90aacd6atagen2026-09-08 08:24 UTC3882
                                                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3883
                                                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC3884
                                                    QueuedThreadCommand::Execute {
96fdf90aacd6atagen2026-09-08 08:24 UTC3885
                                                        id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3886
                                                        command,
96fdf90aacd6atagen2026-09-08 08:24 UTC3887
                                                    } => match command {
96fdf90aacd6atagen2026-09-08 08:24 UTC3888
                                                        TimelineCommand::Paginate {
96fdf90aacd6atagen2026-09-08 08:24 UTC3889
                                                            limit,
96fdf90aacd6atagen2026-09-08 08:24 UTC3890
                                                            ..
96fdf90aacd6atagen2026-09-08 08:24 UTC3891
                                                        } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3892
                                                            timeline
96fdf90aacd6atagen2026-09-08 08:24 UTC3893
                                                                .paginate(limit, Some(id))
96fdf90aacd6atagen2026-09-08 08:24 UTC3894
                                                                .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC3895
                                                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3896
                                                        command => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3897
                                                            let thread_root = timeline
96fdf90aacd6atagen2026-09-08 08:24 UTC3898
                                                                .id
96fdf90aacd6atagen2026-09-08 08:24 UTC3899
                                                                .thread_root()
96fdf90aacd6atagen2026-09-08 08:24 UTC3900
                                                                .cloned();
96fdf90aacd6atagen2026-09-08 08:24 UTC3901
                                                            let result =
96fdf90aacd6atagen2026-09-08 08:24 UTC3902
                                                                execute_timeline_command(
96fdf90aacd6atagen2026-09-08 08:24 UTC3903
                                                                    timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC3904
                                                                    command,
96fdf90aacd6atagen2026-09-08 08:24 UTC3905
                                                                    thread_root.as_ref(),
96fdf90aacd6atagen2026-09-08 08:24 UTC3906
                                                                )
96fdf90aacd6atagen2026-09-08 08:24 UTC3907
                                                                .await;
fa9114fcf75fatagen2026-09-24 12:51 UTC3908
                                                            if self.finish(id, result)
96fdf90aacd6atagen2026-09-08 08:24 UTC3909
                                                            .await
96fdf90aacd6atagen2026-09-08 08:24 UTC3910
                                                            .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC3911
                                                            {
96fdf90aacd6atagen2026-09-08 08:24 UTC3912
                                                                break 'actor;
96fdf90aacd6atagen2026-09-08 08:24 UTC3913
                                                            }
96fdf90aacd6atagen2026-09-08 08:24 UTC3914
                                                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3915
                                                    },
96fdf90aacd6atagen2026-09-08 08:24 UTC3916
                                                }
96fdf90aacd6atagen2026-09-08 08:24 UTC3917
                                            }
96fdf90aacd6atagen2026-09-08 08:24 UTC3918
                                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3919
                                        Err(_) => break 'actor,
96fdf90aacd6atagen2026-09-08 08:24 UTC3920
                                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC3921
                                }
96fdf90aacd6atagen2026-09-08 08:24 UTC3922
                                Err(error) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3923
                                    tracing::warn!(error = %error, room_id = %self.room_id, %root_event_id, "thread timeline build failed");
96fdf90aacd6atagen2026-09-08 08:24 UTC3924
                                    let message = format!("thread timeline build failed: {error}");
96fdf90aacd6atagen2026-09-08 08:24 UTC3925
                                    for queued in opening.queued {
96fdf90aacd6atagen2026-09-08 08:24 UTC3926
                                        let queued_id = match queued {
96fdf90aacd6atagen2026-09-08 08:24 UTC3927
                                            QueuedThreadCommand::Open { id } => id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3928
                                            QueuedThreadCommand::Execute { id, .. } => id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3929
                                        };
96fdf90aacd6atagen2026-09-08 08:24 UTC3930
                                        if emit_command_result(
96fdf90aacd6atagen2026-09-08 08:24 UTC3931
                                            &self.events,
96fdf90aacd6atagen2026-09-08 08:24 UTC3932
                                            queued_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3933
                                            CommandKind::Timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC3934
                                            Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC3935
                                                CommandErrorKind::Timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC3936
                                                message.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC3937
                                            )),
96fdf90aacd6atagen2026-09-08 08:24 UTC3938
                                        )
96fdf90aacd6atagen2026-09-08 08:24 UTC3939
                                        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC3940
                                        .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC3941
                                        {
96fdf90aacd6atagen2026-09-08 08:24 UTC3942
                                            break 'actor;
96fdf90aacd6atagen2026-09-08 08:24 UTC3943
                                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3944
                                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC3945
                                }
96fdf90aacd6atagen2026-09-08 08:24 UTC3946
                            }
96fdf90aacd6atagen2026-09-08 08:24 UTC3947
                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3948
                        Some(Err(error)) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3949
                            if let Some(root_event_id) = thread_task_ids.remove(&error.id()) {
96fdf90aacd6atagen2026-09-08 08:24 UTC3950
                                tracing::warn!(error = %error, room_id = %self.room_id, %root_event_id, "thread open task failed");
96fdf90aacd6atagen2026-09-08 08:24 UTC3951
                                if let Some(opening) = opening_threads.remove(&root_event_id) {
96fdf90aacd6atagen2026-09-08 08:24 UTC3952
                                    queued_thread_count -= opening.queued.len();
96fdf90aacd6atagen2026-09-08 08:24 UTC3953
                                    for queued in opening.queued {
96fdf90aacd6atagen2026-09-08 08:24 UTC3954
                                        let queued_id = match queued {
96fdf90aacd6atagen2026-09-08 08:24 UTC3955
                                            QueuedThreadCommand::Open { id } => id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3956
                                            QueuedThreadCommand::Execute { id, .. } => id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3957
                                        };
96fdf90aacd6atagen2026-09-08 08:24 UTC3958
                                        if emit_command_result(
96fdf90aacd6atagen2026-09-08 08:24 UTC3959
                                            &self.events,
96fdf90aacd6atagen2026-09-08 08:24 UTC3960
                                            queued_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC3961
                                            CommandKind::Timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC3962
                                            Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC3963
                                                CommandErrorKind::Timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC3964
                                                "thread timeline build task failed",
96fdf90aacd6atagen2026-09-08 08:24 UTC3965
                                            )),
96fdf90aacd6atagen2026-09-08 08:24 UTC3966
                                        )
96fdf90aacd6atagen2026-09-08 08:24 UTC3967
                                        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC3968
                                        .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC3969
                                        {
96fdf90aacd6atagen2026-09-08 08:24 UTC3970
                                            break 'actor;
96fdf90aacd6atagen2026-09-08 08:24 UTC3971
                                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3972
                                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC3973
                                }
96fdf90aacd6atagen2026-09-08 08:24 UTC3974
                            } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC3975
                                tracing::debug!(error = %error, room_id = %self.room_id, "thread open task was aborted");
96fdf90aacd6atagen2026-09-08 08:24 UTC3976
                            }
96fdf90aacd6atagen2026-09-08 08:24 UTC3977
                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3978
                        None => {}
96fdf90aacd6atagen2026-09-08 08:24 UTC3979
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC3980
                }
fa9114fcf75fatagen2026-09-24 12:51 UTC3981
                Some(completed) = self.background.join_next_with_id(), if !self.background.is_empty() => {
fa9114fcf75fatagen2026-09-24 12:51 UTC3982
                    if self.reap(completed).await.is_err() {
fa9114fcf75fatagen2026-09-24 12:51 UTC3983
                        break 'actor;
fa9114fcf75fatagen2026-09-24 12:51 UTC3984
                    }
fa9114fcf75fatagen2026-09-24 12:51 UTC3985
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC3986
                completed = room_tasks.join_next(), if !room_tasks.is_empty() => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3987
                    match completed {
96fdf90aacd6atagen2026-09-08 08:24 UTC3988
                        Some(Err(error)) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3989
                            tracing::warn!(error = %error, room_id = %self.room_id, "room actor task failed");
96fdf90aacd6atagen2026-09-08 08:24 UTC3990
                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3991
                        Some(Ok(())) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC3992
                            tracing::warn!(room_id = %self.room_id, "room actor task stopped unexpectedly");
96fdf90aacd6atagen2026-09-08 08:24 UTC3993
                        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3994
                        None => {}
96fdf90aacd6atagen2026-09-08 08:24 UTC3995
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC3996
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC3997
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC3998
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC3999
fa9114fcf75fatagen2026-09-24 12:51 UTC4000
        while let Some(completed) = self.background.join_next_with_id().await {
fa9114fcf75fatagen2026-09-24 12:51 UTC4001
            let _ = self.reap(completed).await;
fa9114fcf75fatagen2026-09-24 12:51 UTC4002
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC4003
        drop(typing_guard);
96fdf90aacd6atagen2026-09-08 08:24 UTC4004
        room_tasks.abort_all();
96fdf90aacd6atagen2026-09-08 08:24 UTC4005
        while room_tasks.join_next().await.is_some() {}
96fdf90aacd6atagen2026-09-08 08:24 UTC4006
        thread_task_ids.clear();
96fdf90aacd6atagen2026-09-08 08:24 UTC4007
        let pending_opens = opening_threads
96fdf90aacd6atagen2026-09-08 08:24 UTC4008
            .drain()
96fdf90aacd6atagen2026-09-08 08:24 UTC4009
            .flat_map(|(_, opening)| opening.queued)
96fdf90aacd6atagen2026-09-08 08:24 UTC4010
            .collect::<Vec<_>>();
96fdf90aacd6atagen2026-09-08 08:24 UTC4011
        for queued in pending_opens {
96fdf90aacd6atagen2026-09-08 08:24 UTC4012
            let queued_id = match queued {
96fdf90aacd6atagen2026-09-08 08:24 UTC4013
                QueuedThreadCommand::Open { id } => id,
96fdf90aacd6atagen2026-09-08 08:24 UTC4014
                QueuedThreadCommand::Execute { id, .. } => id,
96fdf90aacd6atagen2026-09-08 08:24 UTC4015
            };
96fdf90aacd6atagen2026-09-08 08:24 UTC4016
            let _ = emit_command_result(
96fdf90aacd6atagen2026-09-08 08:24 UTC4017
                &self.events,
96fdf90aacd6atagen2026-09-08 08:24 UTC4018
                queued_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC4019
                CommandKind::Timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC4020
                Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC4021
                    CommandErrorKind::RoomNotOpen,
96fdf90aacd6atagen2026-09-08 08:24 UTC4022
                    "room closed while thread timeline was opening",
96fdf90aacd6atagen2026-09-08 08:24 UTC4023
                )),
96fdf90aacd6atagen2026-09-08 08:24 UTC4024
            )
96fdf90aacd6atagen2026-09-08 08:24 UTC4025
            .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC4026
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC4027
        thread_open_tasks.abort_all();
96fdf90aacd6atagen2026-09-08 08:24 UTC4028
        while thread_open_tasks.join_next().await.is_some() {}
96fdf90aacd6atagen2026-09-08 08:24 UTC4029
        for (_, timeline) in thread_timelines {
96fdf90aacd6atagen2026-09-08 08:24 UTC4030
            timeline
96fdf90aacd6atagen2026-09-08 08:24 UTC4031
                .close(
96fdf90aacd6atagen2026-09-08 08:24 UTC4032
                    CommandErrorKind::RoomNotOpen,
96fdf90aacd6atagen2026-09-08 08:24 UTC4033
                    "room closed while thread pagination was in progress",
96fdf90aacd6atagen2026-09-08 08:24 UTC4034
                )
96fdf90aacd6atagen2026-09-08 08:24 UTC4035
                .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC4036
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC4037
        room_timeline
96fdf90aacd6atagen2026-09-08 08:24 UTC4038
            .close(
96fdf90aacd6atagen2026-09-08 08:24 UTC4039
                CommandErrorKind::RoomNotOpen,
96fdf90aacd6atagen2026-09-08 08:24 UTC4040
                "room closed while pagination was in progress",
96fdf90aacd6atagen2026-09-08 08:24 UTC4041
            )
96fdf90aacd6atagen2026-09-08 08:24 UTC4042
            .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC4043
        if let Some(id) = close_id
96fdf90aacd6atagen2026-09-08 08:24 UTC4044
            && emit_command_result(&self.events, id, CommandKind::Room, Ok(()))
96fdf90aacd6atagen2026-09-08 08:24 UTC4045
                .await
96fdf90aacd6atagen2026-09-08 08:24 UTC4046
                .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC4047
        {
96fdf90aacd6atagen2026-09-08 08:24 UTC4048
            tracing::debug!(room_id = %self.room_id, "room close acknowledgement was not delivered");
96fdf90aacd6atagen2026-09-08 08:24 UTC4049
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC4050
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC4051
}
96fdf90aacd6atagen2026-09-08 08:24 UTC4052
96fdf90aacd6atagen2026-09-08 08:24 UTC4053
async fn execute_timeline_command(
96fdf90aacd6atagen2026-09-08 08:24 UTC4054
    session: &mut TimelineSession,
96fdf90aacd6atagen2026-09-08 08:24 UTC4055
    command: TimelineCommand,
96fdf90aacd6atagen2026-09-08 08:24 UTC4056
    thread_root: Option<&OwnedEventId>,
fa9114fcf75fatagen2026-09-24 12:51 UTC4057
) -> TimelineOutcome {
fa9114fcf75fatagen2026-09-24 12:51 UTC4058
    let timeline = session.timeline.clone();
fa9114fcf75fatagen2026-09-24 12:51 UTC4059
    let work: BackgroundWork = match command {
fa9114fcf75fatagen2026-09-24 12:51 UTC4060
        TimelineCommand::Redact {
fa9114fcf75fatagen2026-09-24 12:51 UTC4061
            event_id, reason, ..
fa9114fcf75fatagen2026-09-24 12:51 UTC4062
        } => Box::pin(async move {
fa9114fcf75fatagen2026-09-24 12:51 UTC4063
            timeline
fa9114fcf75fatagen2026-09-24 12:51 UTC4064
                .redact(&TimelineEventItemId::EventId(event_id), reason.as_deref())
fa9114fcf75fatagen2026-09-24 12:51 UTC4065
                .await
fa9114fcf75fatagen2026-09-24 12:51 UTC4066
                .map_err(timeline_error)
fa9114fcf75fatagen2026-09-24 12:51 UTC4067
        }),
fa9114fcf75fatagen2026-09-24 12:51 UTC4068
        TimelineCommand::ToggleReaction { event_id, key, .. } => Box::pin(async move {
fa9114fcf75fatagen2026-09-24 12:51 UTC4069
            timeline
fa9114fcf75fatagen2026-09-24 12:51 UTC4070
                .toggle_reaction(&TimelineEventItemId::EventId(event_id), &key)
fa9114fcf75fatagen2026-09-24 12:51 UTC4071
                .await
fa9114fcf75fatagen2026-09-24 12:51 UTC4072
                .map(|_| ())
fa9114fcf75fatagen2026-09-24 12:51 UTC4073
                .map_err(timeline_error)
fa9114fcf75fatagen2026-09-24 12:51 UTC4074
        }),
fa9114fcf75fatagen2026-09-24 12:51 UTC4075
        TimelineCommand::MarkRead { event_id, .. } => {
fa9114fcf75fatagen2026-09-24 12:51 UTC4076
            let thread = thread_root.is_some();
fa9114fcf75fatagen2026-09-24 12:51 UTC4077
            Box::pin(async move {
fa9114fcf75fatagen2026-09-24 12:51 UTC4078
                let receipt_failed =
fa9114fcf75fatagen2026-09-24 12:51 UTC4079
                    |source| CommandError::from_sdk("sending read receipt failed", source);
fa9114fcf75fatagen2026-09-24 12:51 UTC4080
                timeline
fa9114fcf75fatagen2026-09-24 12:51 UTC4081
                    .send_single_receipt(ReceiptType::Read, event_id.clone())
fa9114fcf75fatagen2026-09-24 12:51 UTC4082
                    .await
fa9114fcf75fatagen2026-09-24 12:51 UTC4083
                    .map_err(receipt_failed)?;
fa9114fcf75fatagen2026-09-24 12:51 UTC4084
                if !thread {
fa9114fcf75fatagen2026-09-24 12:51 UTC4085
                    timeline
fa9114fcf75fatagen2026-09-24 12:51 UTC4086
                        .send_single_receipt(ReceiptType::FullyRead, event_id)
fa9114fcf75fatagen2026-09-24 12:51 UTC4087
                        .await
fa9114fcf75fatagen2026-09-24 12:51 UTC4088
                        .map_err(receipt_failed)?;
fa9114fcf75fatagen2026-09-24 12:51 UTC4089
                }
fa9114fcf75fatagen2026-09-24 12:51 UTC4090
                Ok(())
fa9114fcf75fatagen2026-09-24 12:51 UTC4091
            })
fa9114fcf75fatagen2026-09-24 12:51 UTC4092
        }
fa9114fcf75fatagen2026-09-24 12:51 UTC4093
        command => {
fa9114fcf75fatagen2026-09-24 12:51 UTC4094
            return TimelineOutcome::Done(
fa9114fcf75fatagen2026-09-24 12:51 UTC4095
                execute_serial_timeline_command(session, command, thread_root).await,
fa9114fcf75fatagen2026-09-24 12:51 UTC4096
            );
fa9114fcf75fatagen2026-09-24 12:51 UTC4097
        }
fa9114fcf75fatagen2026-09-24 12:51 UTC4098
    };
fa9114fcf75fatagen2026-09-24 12:51 UTC4099
    TimelineOutcome::Background(work)
fa9114fcf75fatagen2026-09-24 12:51 UTC4100
}
fa9114fcf75fatagen2026-09-24 12:51 UTC4101
fa9114fcf75fatagen2026-09-24 12:51 UTC4102
async fn execute_serial_timeline_command(
fa9114fcf75fatagen2026-09-24 12:51 UTC4103
    session: &mut TimelineSession,
fa9114fcf75fatagen2026-09-24 12:51 UTC4104
    command: TimelineCommand,
fa9114fcf75fatagen2026-09-24 12:51 UTC4105
    thread_root: Option<&OwnedEventId>,
96fdf90aacd6atagen2026-09-08 08:24 UTC4106
) -> Result<(), CommandError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC4107
    let timeline = &session.timeline;
96fdf90aacd6atagen2026-09-08 08:24 UTC4108
    match command {
fa9114fcf75fatagen2026-09-24 12:51 UTC4109
        TimelineCommand::OpenThread { .. }
fa9114fcf75fatagen2026-09-24 12:51 UTC4110
        | TimelineCommand::CloseThread { .. }
fa9114fcf75fatagen2026-09-24 12:51 UTC4111
        | TimelineCommand::Redact { .. }
fa9114fcf75fatagen2026-09-24 12:51 UTC4112
        | TimelineCommand::ToggleReaction { .. }
fa9114fcf75fatagen2026-09-24 12:51 UTC4113
        | TimelineCommand::MarkRead { .. } => Err(CommandError::new(
fa9114fcf75fatagen2026-09-24 12:51 UTC4114
            CommandErrorKind::Other,
fa9114fcf75fatagen2026-09-24 12:51 UTC4115
            "timeline command was misrouted",
fa9114fcf75fatagen2026-09-24 12:51 UTC4116
        )),
96fdf90aacd6atagen2026-09-08 08:24 UTC4117
        TimelineCommand::Resync { .. } => session.resync().await.map_err(|_| {
96fdf90aacd6atagen2026-09-08 08:24 UTC4118
            CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC4119
                CommandErrorKind::Timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC4120
                "timeline resync could not emit a reset",
96fdf90aacd6atagen2026-09-08 08:24 UTC4121
            )
96fdf90aacd6atagen2026-09-08 08:24 UTC4122
        }),
96fdf90aacd6atagen2026-09-08 08:24 UTC4123
        TimelineCommand::Paginate { .. } => Ok(()),
96fdf90aacd6atagen2026-09-08 08:24 UTC4124
        TimelineCommand::Send { content, .. } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC4125
            let body = match &content {
96fdf90aacd6atagen2026-09-08 08:24 UTC4126
                MessageContent::Text { content, .. }
96fdf90aacd6atagen2026-09-08 08:24 UTC4127
                | MessageContent::Emote { content, .. }
96fdf90aacd6atagen2026-09-08 08:24 UTC4128
                | MessageContent::Reply { content, .. } => &content.body,
96fdf90aacd6atagen2026-09-08 08:24 UTC4129
            };
96fdf90aacd6atagen2026-09-08 08:24 UTC4130
            if body.trim().is_empty() {
96fdf90aacd6atagen2026-09-08 08:24 UTC4131
                return Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC4132
                    CommandErrorKind::EmptyBody,
96fdf90aacd6atagen2026-09-08 08:24 UTC4133
                    "message body is empty",
96fdf90aacd6atagen2026-09-08 08:24 UTC4134
                ));
96fdf90aacd6atagen2026-09-08 08:24 UTC4135
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC4136
            match content {
96fdf90aacd6atagen2026-09-08 08:24 UTC4137
                MessageContent::Text { content, mentions } => timeline
96fdf90aacd6atagen2026-09-08 08:24 UTC4138
                    .send(
96fdf90aacd6atagen2026-09-08 08:24 UTC4139
                        message_content(content, mentions, false)
96fdf90aacd6atagen2026-09-08 08:24 UTC4140
                            .with_relation(None)
96fdf90aacd6atagen2026-09-08 08:24 UTC4141
                            .into(),
96fdf90aacd6atagen2026-09-08 08:24 UTC4142
                    )
96fdf90aacd6atagen2026-09-08 08:24 UTC4143
                    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC4144
                    .map(|_| ())
96fdf90aacd6atagen2026-09-08 08:24 UTC4145
                    .map_err(timeline_error),
96fdf90aacd6atagen2026-09-08 08:24 UTC4146
                MessageContent::Emote { content, mentions } => timeline
96fdf90aacd6atagen2026-09-08 08:24 UTC4147
                    .send(
96fdf90aacd6atagen2026-09-08 08:24 UTC4148
                        message_content(content, mentions, true)
96fdf90aacd6atagen2026-09-08 08:24 UTC4149
                            .with_relation(None)
96fdf90aacd6atagen2026-09-08 08:24 UTC4150
                            .into(),
96fdf90aacd6atagen2026-09-08 08:24 UTC4151
                    )
96fdf90aacd6atagen2026-09-08 08:24 UTC4152
                    .await
96fdf90aacd6atagen2026-09-08 08:24 UTC4153
                    .map(|_| ())
96fdf90aacd6atagen2026-09-08 08:24 UTC4154
                    .map_err(timeline_error),
96fdf90aacd6atagen2026-09-08 08:24 UTC4155
                MessageContent::Reply {
96fdf90aacd6atagen2026-09-08 08:24 UTC4156
                    event_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC4157
                    content,
96fdf90aacd6atagen2026-09-08 08:24 UTC4158
                    mentions,
96fdf90aacd6atagen2026-09-08 08:24 UTC4159
                    notify_replied_to,
96fdf90aacd6atagen2026-09-08 08:24 UTC4160
                } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC4161
                    let content = timeline
96fdf90aacd6atagen2026-09-08 08:24 UTC4162
                        .room()
96fdf90aacd6atagen2026-09-08 08:24 UTC4163
                        .make_reply_event(
96fdf90aacd6atagen2026-09-08 08:24 UTC4164
                            message_content(content, mentions, false),
96fdf90aacd6atagen2026-09-08 08:24 UTC4165
                            SdkReply {
96fdf90aacd6atagen2026-09-08 08:24 UTC4166
                                event_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC4167
                                enforce_thread: if thread_root.is_some() {
96fdf90aacd6atagen2026-09-08 08:24 UTC4168
                                    EnforceThread::Threaded(ReplyWithinThread::Yes)
96fdf90aacd6atagen2026-09-08 08:24 UTC4169
                                } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC4170
                                    EnforceThread::MaybeThreaded
96fdf90aacd6atagen2026-09-08 08:24 UTC4171
                                },
96fdf90aacd6atagen2026-09-08 08:24 UTC4172
                                add_mentions: if notify_replied_to {
96fdf90aacd6atagen2026-09-08 08:24 UTC4173
                                    AddMentions::Yes
96fdf90aacd6atagen2026-09-08 08:24 UTC4174
                                } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC4175
                                    AddMentions::No
96fdf90aacd6atagen2026-09-08 08:24 UTC4176
                                },
96fdf90aacd6atagen2026-09-08 08:24 UTC4177
                            },
96fdf90aacd6atagen2026-09-08 08:24 UTC4178
                        )
96fdf90aacd6atagen2026-09-08 08:24 UTC4179
                        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC4180
                        .map_err(|source| {
96fdf90aacd6atagen2026-09-08 08:24 UTC4181
                            CommandError::with_source(
96fdf90aacd6atagen2026-09-08 08:24 UTC4182
                                CommandErrorKind::Timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC4183
                                "reply operation failed",
96fdf90aacd6atagen2026-09-08 08:24 UTC4184
                                source,
96fdf90aacd6atagen2026-09-08 08:24 UTC4185
                            )
96fdf90aacd6atagen2026-09-08 08:24 UTC4186
                        })?;
96fdf90aacd6atagen2026-09-08 08:24 UTC4187
                    if let Some(thread_root) = thread_root
96fdf90aacd6atagen2026-09-08 08:24 UTC4188
                        && !matches!(
96fdf90aacd6atagen2026-09-08 08:24 UTC4189
                            content.relates_to.as_ref(),
96fdf90aacd6atagen2026-09-08 08:24 UTC4190
                            Some(Relation::Thread(thread)) if &thread.event_id == thread_root
96fdf90aacd6atagen2026-09-08 08:24 UTC4191
                        )
96fdf90aacd6atagen2026-09-08 08:24 UTC4192
                    {
96fdf90aacd6atagen2026-09-08 08:24 UTC4193
                        return Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC4194
                            CommandErrorKind::Timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC4195
                            "reply target does not belong to the selected thread",
96fdf90aacd6atagen2026-09-08 08:24 UTC4196
                        ));
96fdf90aacd6atagen2026-09-08 08:24 UTC4197
                    }
96fdf90aacd6atagen2026-09-08 08:24 UTC4198
                    timeline
96fdf90aacd6atagen2026-09-08 08:24 UTC4199
                        .send(content.into())
96fdf90aacd6atagen2026-09-08 08:24 UTC4200
                        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC4201
                        .map(|_| ())
96fdf90aacd6atagen2026-09-08 08:24 UTC4202
                        .map_err(timeline_error)
96fdf90aacd6atagen2026-09-08 08:24 UTC4203
                }
96fdf90aacd6atagen2026-09-08 08:24 UTC4204
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC4205
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC4206
        TimelineCommand::Edit {
96fdf90aacd6atagen2026-09-08 08:24 UTC4207
            event_id, content, ..
96fdf90aacd6atagen2026-09-08 08:24 UTC4208
        } => {
96fdf90aacd6atagen2026-09-08 08:24 UTC4209
            let (content, mentions, emote) = match content {
96fdf90aacd6atagen2026-09-08 08:24 UTC4210
                MessageEditContent::Text { content, mentions } => (content, mentions, false),
96fdf90aacd6atagen2026-09-08 08:24 UTC4211
                MessageEditContent::Emote { content, mentions } => (content, mentions, true),
96fdf90aacd6atagen2026-09-08 08:24 UTC4212
            };
96fdf90aacd6atagen2026-09-08 08:24 UTC4213
            if content.body.trim().is_empty() {
96fdf90aacd6atagen2026-09-08 08:24 UTC4214
                return Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC4215
                    CommandErrorKind::EmptyBody,
96fdf90aacd6atagen2026-09-08 08:24 UTC4216
                    "message body is empty",
96fdf90aacd6atagen2026-09-08 08:24 UTC4217
                ));
96fdf90aacd6atagen2026-09-08 08:24 UTC4218
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC4219
            timeline
96fdf90aacd6atagen2026-09-08 08:24 UTC4220
                .edit(
96fdf90aacd6atagen2026-09-08 08:24 UTC4221
                    &TimelineEventItemId::EventId(event_id),
96fdf90aacd6atagen2026-09-08 08:24 UTC4222
                    EditedContent::RoomMessage(message_content(content, mentions, emote)),
96fdf90aacd6atagen2026-09-08 08:24 UTC4223
                )
96fdf90aacd6atagen2026-09-08 08:24 UTC4224
                .await
96fdf90aacd6atagen2026-09-08 08:24 UTC4225
                .map_err(timeline_error)
96fdf90aacd6atagen2026-09-08 08:24 UTC4226
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC4227
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC4228
}
96fdf90aacd6atagen2026-09-08 08:24 UTC4229
96fdf90aacd6atagen2026-09-08 08:24 UTC4230
fn message_content(
96fdf90aacd6atagen2026-09-08 08:24 UTC4231
    content: RichText,
96fdf90aacd6atagen2026-09-08 08:24 UTC4232
    mentions: MessageMentions,
96fdf90aacd6atagen2026-09-08 08:24 UTC4233
    emote: bool,
96fdf90aacd6atagen2026-09-08 08:24 UTC4234
) -> RoomMessageEventContentWithoutRelation {
96fdf90aacd6atagen2026-09-08 08:24 UTC4235
    let content = match content.formatted_body {
96fdf90aacd6atagen2026-09-08 08:24 UTC4236
        Some(formatted_body) if emote => {
96fdf90aacd6atagen2026-09-08 08:24 UTC4237
            RoomMessageEventContentWithoutRelation::emote_html(content.body, formatted_body)
96fdf90aacd6atagen2026-09-08 08:24 UTC4238
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC4239
        Some(formatted_body) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC4240
            RoomMessageEventContentWithoutRelation::text_html(content.body, formatted_body)
96fdf90aacd6atagen2026-09-08 08:24 UTC4241
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC4242
        None if emote => RoomMessageEventContentWithoutRelation::emote_plain(content.body),
96fdf90aacd6atagen2026-09-08 08:24 UTC4243
        None => RoomMessageEventContentWithoutRelation::text_plain(content.body),
96fdf90aacd6atagen2026-09-08 08:24 UTC4244
    };
96fdf90aacd6atagen2026-09-08 08:24 UTC4245
    let content = content.add_mentions(Mentions::with_user_ids(mentions.user_ids));
96fdf90aacd6atagen2026-09-08 08:24 UTC4246
    if mentions.room {
96fdf90aacd6atagen2026-09-08 08:24 UTC4247
        content.add_mentions(Mentions::with_room_mention())
96fdf90aacd6atagen2026-09-08 08:24 UTC4248
    } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC4249
        content
96fdf90aacd6atagen2026-09-08 08:24 UTC4250
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC4251
}
96fdf90aacd6atagen2026-09-08 08:24 UTC4252
96fdf90aacd6atagen2026-09-08 08:24 UTC4253
async fn run_pagination(request: PaginationRequest) {
96fdf90aacd6atagen2026-09-08 08:24 UTC4254
    let PaginationRequest {
96fdf90aacd6atagen2026-09-08 08:24 UTC4255
        timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC4256
        events,
96fdf90aacd6atagen2026-09-08 08:24 UTC4257
        timeline_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC4258
        limit,
96fdf90aacd6atagen2026-09-08 08:24 UTC4259
        command_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC4260
        results,
96fdf90aacd6atagen2026-09-08 08:24 UTC4261
    } = request;
96fdf90aacd6atagen2026-09-08 08:24 UTC4262
    if events
96fdf90aacd6atagen2026-09-08 08:24 UTC4263
        .send(Event::HistoryChanged {
96fdf90aacd6atagen2026-09-08 08:24 UTC4264
            timeline_id: timeline_id.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC4265
            state: HistoryState::Loading,
96fdf90aacd6atagen2026-09-08 08:24 UTC4266
        })
96fdf90aacd6atagen2026-09-08 08:24 UTC4267
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC4268
        .is_err()
96fdf90aacd6atagen2026-09-08 08:24 UTC4269
    {
96fdf90aacd6atagen2026-09-08 08:24 UTC4270
        if let Some(id) = command_id {
96fdf90aacd6atagen2026-09-08 08:24 UTC4271
            let _ = results.send((
96fdf90aacd6atagen2026-09-08 08:24 UTC4272
                id,
96fdf90aacd6atagen2026-09-08 08:24 UTC4273
                Err(CommandError::new(
96fdf90aacd6atagen2026-09-08 08:24 UTC4274
                    CommandErrorKind::EventReceiverClosed,
96fdf90aacd6atagen2026-09-08 08:24 UTC4275
                    "event receiver has closed",
96fdf90aacd6atagen2026-09-08 08:24 UTC4276
                )),
96fdf90aacd6atagen2026-09-08 08:24 UTC4277
            ));
96fdf90aacd6atagen2026-09-08 08:24 UTC4278
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC4279
        return;
96fdf90aacd6atagen2026-09-08 08:24 UTC4280
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC4281
    let result = timeline
96fdf90aacd6atagen2026-09-08 08:24 UTC4282
        .paginate_backwards(limit.max(1))
96fdf90aacd6atagen2026-09-08 08:24 UTC4283
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC4284
        .map_err(timeline_error);
96fdf90aacd6atagen2026-09-08 08:24 UTC4285
    match result {
96fdf90aacd6atagen2026-09-08 08:24 UTC4286
        Ok(at_start) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC4287
            let _ = events
96fdf90aacd6atagen2026-09-08 08:24 UTC4288
                .send(Event::HistoryChanged {
96fdf90aacd6atagen2026-09-08 08:24 UTC4289
                    timeline_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC4290
                    state: if at_start {
96fdf90aacd6atagen2026-09-08 08:24 UTC4291
                        HistoryState::StartReached
96fdf90aacd6atagen2026-09-08 08:24 UTC4292
                    } else {
96fdf90aacd6atagen2026-09-08 08:24 UTC4293
                        HistoryState::Idle
96fdf90aacd6atagen2026-09-08 08:24 UTC4294
                    },
96fdf90aacd6atagen2026-09-08 08:24 UTC4295
                })
96fdf90aacd6atagen2026-09-08 08:24 UTC4296
                .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC4297
            if let Some(id) = command_id {
96fdf90aacd6atagen2026-09-08 08:24 UTC4298
                let _ = results.send((id, Ok(())));
96fdf90aacd6atagen2026-09-08 08:24 UTC4299
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC4300
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC4301
        Err(error) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC4302
            let _ = events
96fdf90aacd6atagen2026-09-08 08:24 UTC4303
                .send(Event::HistoryChanged {
96fdf90aacd6atagen2026-09-08 08:24 UTC4304
                    timeline_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC4305
                    state: HistoryState::Failed(error.to_string()),
96fdf90aacd6atagen2026-09-08 08:24 UTC4306
                })
96fdf90aacd6atagen2026-09-08 08:24 UTC4307
                .await;
96fdf90aacd6atagen2026-09-08 08:24 UTC4308
            if let Some(id) = command_id {
96fdf90aacd6atagen2026-09-08 08:24 UTC4309
                let _ = results.send((id, Err(error)));
96fdf90aacd6atagen2026-09-08 08:24 UTC4310
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC4311
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC4312
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC4313
}
96fdf90aacd6atagen2026-09-08 08:24 UTC4314
96fdf90aacd6atagen2026-09-08 08:24 UTC4315
async fn emit_command_result_with_room(
96fdf90aacd6atagen2026-09-08 08:24 UTC4316
    events: &EventSender,
96fdf90aacd6atagen2026-09-08 08:24 UTC4317
    id: CommandId,
96fdf90aacd6atagen2026-09-08 08:24 UTC4318
    room_id: OwnedRoomId,
96fdf90aacd6atagen2026-09-08 08:24 UTC4319
) -> Result<(), SendEventError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC4320
    events
96fdf90aacd6atagen2026-09-08 08:24 UTC4321
        .send(Event::CommandSucceededWithRoom {
96fdf90aacd6atagen2026-09-08 08:24 UTC4322
            id,
96fdf90aacd6atagen2026-09-08 08:24 UTC4323
            command: CommandKind::Room,
96fdf90aacd6atagen2026-09-08 08:24 UTC4324
            room_id,
96fdf90aacd6atagen2026-09-08 08:24 UTC4325
        })
96fdf90aacd6atagen2026-09-08 08:24 UTC4326
        .await
96fdf90aacd6atagen2026-09-08 08:24 UTC4327
}
96fdf90aacd6atagen2026-09-08 08:24 UTC4328
96fdf90aacd6atagen2026-09-08 08:24 UTC4329
async fn emit_command_result(
96fdf90aacd6atagen2026-09-08 08:24 UTC4330
    events: &EventSender,
96fdf90aacd6atagen2026-09-08 08:24 UTC4331
    id: CommandId,
96fdf90aacd6atagen2026-09-08 08:24 UTC4332
    command: CommandKind,
96fdf90aacd6atagen2026-09-08 08:24 UTC4333
    result: Result<(), CommandError>,
96fdf90aacd6atagen2026-09-08 08:24 UTC4334
) -> Result<(), SendEventError> {
96fdf90aacd6atagen2026-09-08 08:24 UTC4335
    let event = match result {
96fdf90aacd6atagen2026-09-08 08:24 UTC4336
        Ok(()) => Event::CommandSucceeded { id, command },
96fdf90aacd6atagen2026-09-08 08:24 UTC4337
        Err(error) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC4338
            tracing::warn!(?id, ?command, error = ?error, "room actor command failed");
96fdf90aacd6atagen2026-09-08 08:24 UTC4339
            Event::CommandFailed(CommandFailure {
96fdf90aacd6atagen2026-09-08 08:24 UTC4340
                id,
96fdf90aacd6atagen2026-09-08 08:24 UTC4341
                command,
96fdf90aacd6atagen2026-09-08 08:24 UTC4342
                kind: error.kind,
96fdf90aacd6atagen2026-09-08 08:24 UTC4343
                message: error.to_string(),
96fdf90aacd6atagen2026-09-08 08:24 UTC4344
            })
96fdf90aacd6atagen2026-09-08 08:24 UTC4345
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC4346
    };
96fdf90aacd6atagen2026-09-08 08:24 UTC4347
    events.send(event).await
96fdf90aacd6atagen2026-09-08 08:24 UTC4348
}
96fdf90aacd6atagen2026-09-08 08:24 UTC4349
96fdf90aacd6atagen2026-09-08 08:24 UTC4350
fn send_error(source: SendEventError) -> CommandError {
96fdf90aacd6atagen2026-09-08 08:24 UTC4351
    CommandError::with_source(
96fdf90aacd6atagen2026-09-08 08:24 UTC4352
        CommandErrorKind::EventReceiverClosed,
96fdf90aacd6atagen2026-09-08 08:24 UTC4353
        "event receiver has closed",
96fdf90aacd6atagen2026-09-08 08:24 UTC4354
        source,
96fdf90aacd6atagen2026-09-08 08:24 UTC4355
    )
96fdf90aacd6atagen2026-09-08 08:24 UTC4356
}
96fdf90aacd6atagen2026-09-08 08:24 UTC4357
96fdf90aacd6atagen2026-09-08 08:24 UTC4358
fn timeline_error(source: matrix_sdk_ui::timeline::Error) -> CommandError {
96fdf90aacd6atagen2026-09-08 08:24 UTC4359
    CommandError::with_source(
96fdf90aacd6atagen2026-09-08 08:24 UTC4360
        CommandErrorKind::Timeline,
96fdf90aacd6atagen2026-09-08 08:24 UTC4361
        "timeline operation failed",
96fdf90aacd6atagen2026-09-08 08:24 UTC4362
        source,
96fdf90aacd6atagen2026-09-08 08:24 UTC4363
    )
96fdf90aacd6atagen2026-09-08 08:24 UTC4364
}