atagen

macltok

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