atagen

macltok

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

Blame: src/engine/mod.rs

31ce2453c82fatagen2026-09-24 13:07 UTC1
mod commands;
31ce2453c82fatagen2026-09-24 13:07 UTC2
mod media;
31ce2453c82fatagen2026-09-24 13:07 UTC3
mod mutations;
31ce2453c82fatagen2026-09-24 13:07 UTC4
mod room;
31ce2453c82fatagen2026-09-24 13:07 UTC5
mod timeline;
31ce2453c82fatagen2026-09-24 13:07 UTC6
mod watchers;
31ce2453c82fatagen2026-09-24 13:07 UTC7
31ce2453c82fatagen2026-09-24 13:07 UTC8
use std::{
31ce2453c82fatagen2026-09-24 13:07 UTC9
    collections::{HashMap, VecDeque},
31ce2453c82fatagen2026-09-24 13:07 UTC10
    future::Future,
31ce2453c82fatagen2026-09-24 13:07 UTC11
    sync::Arc,
31ce2453c82fatagen2026-09-24 13:07 UTC12
};
31ce2453c82fatagen2026-09-24 13:07 UTC13
31ce2453c82fatagen2026-09-24 13:07 UTC14
use futures_util::future::BoxFuture;
31ce2453c82fatagen2026-09-24 13:07 UTC15
use matrix_sdk::{Client, SessionChange};
31ce2453c82fatagen2026-09-24 13:07 UTC16
use matrix_sdk_ui::sync_service::SyncService;
31ce2453c82fatagen2026-09-24 13:07 UTC17
use ruma::{OwnedRoomId, RoomId};
31ce2453c82fatagen2026-09-24 13:07 UTC18
use thiserror::Error as ThisError;
31ce2453c82fatagen2026-09-24 13:07 UTC19
use tokio::{
31ce2453c82fatagen2026-09-24 13:07 UTC20
    sync::{Notify, broadcast, mpsc, watch},
31ce2453c82fatagen2026-09-24 13:07 UTC21
    task::{AbortHandle, Id as TaskId, JoinError, JoinSet},
31ce2453c82fatagen2026-09-24 13:07 UTC22
};
31ce2453c82fatagen2026-09-24 13:07 UTC23
31ce2453c82fatagen2026-09-24 13:07 UTC24
pub(crate) use media::MediaService;
31ce2453c82fatagen2026-09-24 13:07 UTC25
31ce2453c82fatagen2026-09-24 13:07 UTC26
use self::{
31ce2453c82fatagen2026-09-24 13:07 UTC27
    commands::Job,
31ce2453c82fatagen2026-09-24 13:07 UTC28
    mutations::{Mutation, MutationJob},
31ce2453c82fatagen2026-09-24 13:07 UTC29
    room::{ActorRequest, Exited, RoomActor},
31ce2453c82fatagen2026-09-24 13:07 UTC30
    timeline::{SessionContext, Supervisor},
31ce2453c82fatagen2026-09-24 13:07 UTC31
};
31ce2453c82fatagen2026-09-24 13:07 UTC32
use crate::{
31ce2453c82fatagen2026-09-24 13:07 UTC33
    AccountCommand, ClientHandle, Command, ConnectionState, EncryptionCommand, EngineErrorKind,
31ce2453c82fatagen2026-09-24 13:07 UTC34
    EngineFailure, Error, ErrorKind, Event, EventStream, LifecycleCommand, MediaCommand,
31ce2453c82fatagen2026-09-24 13:07 UTC35
    NotificationCommand, RoomCommand, TimelineCommand,
31ce2453c82fatagen2026-09-24 13:07 UTC36
    transport::{DriverPort, Request, Responder, transport_pair},
31ce2453c82fatagen2026-09-24 13:07 UTC37
};
31ce2453c82fatagen2026-09-24 13:07 UTC38
31ce2453c82fatagen2026-09-24 13:07 UTC39
#[derive(Clone, Debug, Eq, PartialEq)]
31ce2453c82fatagen2026-09-24 13:07 UTC40
#[non_exhaustive]
31ce2453c82fatagen2026-09-24 13:07 UTC41
pub struct EngineConfig {
31ce2453c82fatagen2026-09-24 13:07 UTC42
    pub command_capacity: usize,
31ce2453c82fatagen2026-09-24 13:07 UTC43
    pub event_capacity: usize,
31ce2453c82fatagen2026-09-24 13:07 UTC44
    pub initial_history_limit: u16,
31ce2453c82fatagen2026-09-24 13:07 UTC45
    pub max_media_bytes: usize,
31ce2453c82fatagen2026-09-24 13:07 UTC46
    pub max_media_sources: usize,
31ce2453c82fatagen2026-09-24 13:07 UTC47
}
31ce2453c82fatagen2026-09-24 13:07 UTC48
31ce2453c82fatagen2026-09-24 13:07 UTC49
impl EngineConfig {
31ce2453c82fatagen2026-09-24 13:07 UTC50
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC51
    pub const fn with_command_capacity(mut self, capacity: usize) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC52
        self.command_capacity = capacity;
31ce2453c82fatagen2026-09-24 13:07 UTC53
        self
31ce2453c82fatagen2026-09-24 13:07 UTC54
    }
31ce2453c82fatagen2026-09-24 13:07 UTC55
31ce2453c82fatagen2026-09-24 13:07 UTC56
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC57
    pub const fn with_event_capacity(mut self, capacity: usize) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC58
        self.event_capacity = capacity;
31ce2453c82fatagen2026-09-24 13:07 UTC59
        self
31ce2453c82fatagen2026-09-24 13:07 UTC60
    }
31ce2453c82fatagen2026-09-24 13:07 UTC61
31ce2453c82fatagen2026-09-24 13:07 UTC62
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC63
    pub const fn with_initial_history_limit(mut self, limit: u16) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC64
        self.initial_history_limit = limit;
31ce2453c82fatagen2026-09-24 13:07 UTC65
        self
31ce2453c82fatagen2026-09-24 13:07 UTC66
    }
31ce2453c82fatagen2026-09-24 13:07 UTC67
31ce2453c82fatagen2026-09-24 13:07 UTC68
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC69
    pub const fn with_max_media_bytes(mut self, bytes: usize) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC70
        self.max_media_bytes = bytes;
31ce2453c82fatagen2026-09-24 13:07 UTC71
        self
31ce2453c82fatagen2026-09-24 13:07 UTC72
    }
31ce2453c82fatagen2026-09-24 13:07 UTC73
31ce2453c82fatagen2026-09-24 13:07 UTC74
    #[must_use]
31ce2453c82fatagen2026-09-24 13:07 UTC75
    pub const fn with_max_media_sources(mut self, sources: usize) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC76
        self.max_media_sources = sources;
31ce2453c82fatagen2026-09-24 13:07 UTC77
        self
31ce2453c82fatagen2026-09-24 13:07 UTC78
    }
31ce2453c82fatagen2026-09-24 13:07 UTC79
}
31ce2453c82fatagen2026-09-24 13:07 UTC80
31ce2453c82fatagen2026-09-24 13:07 UTC81
impl Default for EngineConfig {
31ce2453c82fatagen2026-09-24 13:07 UTC82
    fn default() -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC83
        Self {
31ce2453c82fatagen2026-09-24 13:07 UTC84
            command_capacity: 64,
31ce2453c82fatagen2026-09-24 13:07 UTC85
            event_capacity: 128,
31ce2453c82fatagen2026-09-24 13:07 UTC86
            initial_history_limit: 30,
31ce2453c82fatagen2026-09-24 13:07 UTC87
            max_media_bytes: 20 * 1024 * 1024,
31ce2453c82fatagen2026-09-24 13:07 UTC88
            max_media_sources: 2_048,
31ce2453c82fatagen2026-09-24 13:07 UTC89
        }
31ce2453c82fatagen2026-09-24 13:07 UTC90
    }
31ce2453c82fatagen2026-09-24 13:07 UTC91
}
31ce2453c82fatagen2026-09-24 13:07 UTC92
31ce2453c82fatagen2026-09-24 13:07 UTC93
#[derive(Debug, ThisError)]
31ce2453c82fatagen2026-09-24 13:07 UTC94
pub enum EngineError {
31ce2453c82fatagen2026-09-24 13:07 UTC95
    #[error("the Matrix client is not authenticated")]
31ce2453c82fatagen2026-09-24 13:07 UTC96
    Unauthenticated,
31ce2453c82fatagen2026-09-24 13:07 UTC97
    #[error("command, event, and media source capacities must be nonzero")]
31ce2453c82fatagen2026-09-24 13:07 UTC98
    ZeroCapacity,
31ce2453c82fatagen2026-09-24 13:07 UTC99
    #[error("could not build the sync service")]
31ce2453c82fatagen2026-09-24 13:07 UTC100
    SyncService(#[source] matrix_sdk_ui::sync_service::Error),
31ce2453c82fatagen2026-09-24 13:07 UTC101
    #[error("the event receiver has closed")]
31ce2453c82fatagen2026-09-24 13:07 UTC102
    EventReceiverClosed,
31ce2453c82fatagen2026-09-24 13:07 UTC103
    #[error("engine task {task} panicked")]
31ce2453c82fatagen2026-09-24 13:07 UTC104
    TaskPanicked { task: String },
31ce2453c82fatagen2026-09-24 13:07 UTC105
}
31ce2453c82fatagen2026-09-24 13:07 UTC106
31ce2453c82fatagen2026-09-24 13:07 UTC107
impl EngineError {
31ce2453c82fatagen2026-09-24 13:07 UTC108
    fn failure(&self) -> EngineFailure {
31ce2453c82fatagen2026-09-24 13:07 UTC109
        EngineFailure {
31ce2453c82fatagen2026-09-24 13:07 UTC110
            kind: match self {
31ce2453c82fatagen2026-09-24 13:07 UTC111
                Self::EventReceiverClosed => EngineErrorKind::EventReceiverClosed,
31ce2453c82fatagen2026-09-24 13:07 UTC112
                Self::TaskPanicked { .. } => EngineErrorKind::Watcher,
31ce2453c82fatagen2026-09-24 13:07 UTC113
                Self::Unauthenticated | Self::ZeroCapacity | Self::SyncService(_) => {
31ce2453c82fatagen2026-09-24 13:07 UTC114
                    EngineErrorKind::Other
31ce2453c82fatagen2026-09-24 13:07 UTC115
                }
31ce2453c82fatagen2026-09-24 13:07 UTC116
            },
31ce2453c82fatagen2026-09-24 13:07 UTC117
            message: self.to_string(),
31ce2453c82fatagen2026-09-24 13:07 UTC118
        }
31ce2453c82fatagen2026-09-24 13:07 UTC119
    }
31ce2453c82fatagen2026-09-24 13:07 UTC120
}
31ce2453c82fatagen2026-09-24 13:07 UTC121
31ce2453c82fatagen2026-09-24 13:07 UTC122
#[derive(Clone)]
31ce2453c82fatagen2026-09-24 13:07 UTC123
enum TaskContext {
31ce2453c82fatagen2026-09-24 13:07 UTC124
    /// a panic here costs the caller an `Interrupted` answer and nothing else
31ce2453c82fatagen2026-09-24 13:07 UTC125
    Command(&'static str),
31ce2453c82fatagen2026-09-24 13:07 UTC126
    /// a panic here takes the engine with it
31ce2453c82fatagen2026-09-24 13:07 UTC127
    Watcher(&'static str),
31ce2453c82fatagen2026-09-24 13:07 UTC128
    RoomActor {
31ce2453c82fatagen2026-09-24 13:07 UTC129
        room_id: OwnedRoomId,
31ce2453c82fatagen2026-09-24 13:07 UTC130
        generation: u64,
31ce2453c82fatagen2026-09-24 13:07 UTC131
    },
31ce2453c82fatagen2026-09-24 13:07 UTC132
}
31ce2453c82fatagen2026-09-24 13:07 UTC133
31ce2453c82fatagen2026-09-24 13:07 UTC134
impl TaskContext {
31ce2453c82fatagen2026-09-24 13:07 UTC135
    fn label(&self) -> String {
31ce2453c82fatagen2026-09-24 13:07 UTC136
        match self {
31ce2453c82fatagen2026-09-24 13:07 UTC137
            Self::Command(name) | Self::Watcher(name) => (*name).to_owned(),
31ce2453c82fatagen2026-09-24 13:07 UTC138
            Self::RoomActor {
31ce2453c82fatagen2026-09-24 13:07 UTC139
                room_id,
31ce2453c82fatagen2026-09-24 13:07 UTC140
                generation,
31ce2453c82fatagen2026-09-24 13:07 UTC141
            } => format!("room actor {room_id} generation {generation}"),
31ce2453c82fatagen2026-09-24 13:07 UTC142
        }
31ce2453c82fatagen2026-09-24 13:07 UTC143
    }
31ce2453c82fatagen2026-09-24 13:07 UTC144
}
31ce2453c82fatagen2026-09-24 13:07 UTC145
31ce2453c82fatagen2026-09-24 13:07 UTC146
struct TaskRegistry {
31ce2453c82fatagen2026-09-24 13:07 UTC147
    tasks: JoinSet<()>,
31ce2453c82fatagen2026-09-24 13:07 UTC148
    contexts: HashMap<TaskId, TaskContext>,
31ce2453c82fatagen2026-09-24 13:07 UTC149
}
31ce2453c82fatagen2026-09-24 13:07 UTC150
31ce2453c82fatagen2026-09-24 13:07 UTC151
impl TaskRegistry {
31ce2453c82fatagen2026-09-24 13:07 UTC152
    fn spawn(
31ce2453c82fatagen2026-09-24 13:07 UTC153
        &mut self,
31ce2453c82fatagen2026-09-24 13:07 UTC154
        context: TaskContext,
31ce2453c82fatagen2026-09-24 13:07 UTC155
        task: impl Future<Output = ()> + Send + 'static,
31ce2453c82fatagen2026-09-24 13:07 UTC156
    ) -> AbortHandle {
31ce2453c82fatagen2026-09-24 13:07 UTC157
        let handle = self.tasks.spawn(task);
31ce2453c82fatagen2026-09-24 13:07 UTC158
        self.contexts.insert(handle.id(), context);
31ce2453c82fatagen2026-09-24 13:07 UTC159
        handle
31ce2453c82fatagen2026-09-24 13:07 UTC160
    }
31ce2453c82fatagen2026-09-24 13:07 UTC161
31ce2453c82fatagen2026-09-24 13:07 UTC162
    async fn join_next(&mut self) -> Option<(TaskContext, Result<(), JoinError>)> {
31ce2453c82fatagen2026-09-24 13:07 UTC163
        let result = self.tasks.join_next_with_id().await?;
31ce2453c82fatagen2026-09-24 13:07 UTC164
        let id = match &result {
31ce2453c82fatagen2026-09-24 13:07 UTC165
            Ok((id, ())) => *id,
31ce2453c82fatagen2026-09-24 13:07 UTC166
            Err(error) => error.id(),
31ce2453c82fatagen2026-09-24 13:07 UTC167
        };
31ce2453c82fatagen2026-09-24 13:07 UTC168
        let context = self.contexts.remove(&id)?;
31ce2453c82fatagen2026-09-24 13:07 UTC169
        Some((context, result.map(drop)))
31ce2453c82fatagen2026-09-24 13:07 UTC170
    }
31ce2453c82fatagen2026-09-24 13:07 UTC171
31ce2453c82fatagen2026-09-24 13:07 UTC172
    async fn shutdown(&mut self) {
31ce2453c82fatagen2026-09-24 13:07 UTC173
        self.tasks.abort_all();
31ce2453c82fatagen2026-09-24 13:07 UTC174
        while self.tasks.join_next().await.is_some() {}
31ce2453c82fatagen2026-09-24 13:07 UTC175
        self.contexts.clear();
31ce2453c82fatagen2026-09-24 13:07 UTC176
    }
31ce2453c82fatagen2026-09-24 13:07 UTC177
}
31ce2453c82fatagen2026-09-24 13:07 UTC178
31ce2453c82fatagen2026-09-24 13:07 UTC179
enum RoomSlot {
31ce2453c82fatagen2026-09-24 13:07 UTC180
    Active {
31ce2453c82fatagen2026-09-24 13:07 UTC181
        generation: u64,
31ce2453c82fatagen2026-09-24 13:07 UTC182
        sender: mpsc::Sender<ActorRequest>,
31ce2453c82fatagen2026-09-24 13:07 UTC183
    },
31ce2453c82fatagen2026-09-24 13:07 UTC184
    /// opens and closes that arrive while the actor winds down, replayed in order once it's gone
31ce2453c82fatagen2026-09-24 13:07 UTC185
    Closing {
31ce2453c82fatagen2026-09-24 13:07 UTC186
        generation: u64,
31ce2453c82fatagen2026-09-24 13:07 UTC187
        pending: VecDeque<Deferred>,
31ce2453c82fatagen2026-09-24 13:07 UTC188
    },
31ce2453c82fatagen2026-09-24 13:07 UTC189
}
31ce2453c82fatagen2026-09-24 13:07 UTC190
31ce2453c82fatagen2026-09-24 13:07 UTC191
impl RoomSlot {
31ce2453c82fatagen2026-09-24 13:07 UTC192
    const fn generation(&self) -> u64 {
31ce2453c82fatagen2026-09-24 13:07 UTC193
        match self {
31ce2453c82fatagen2026-09-24 13:07 UTC194
            Self::Active { generation, .. } | Self::Closing { generation, .. } => *generation,
31ce2453c82fatagen2026-09-24 13:07 UTC195
        }
31ce2453c82fatagen2026-09-24 13:07 UTC196
    }
31ce2453c82fatagen2026-09-24 13:07 UTC197
}
31ce2453c82fatagen2026-09-24 13:07 UTC198
31ce2453c82fatagen2026-09-24 13:07 UTC199
enum Deferred {
31ce2453c82fatagen2026-09-24 13:07 UTC200
    Open(Responder),
31ce2453c82fatagen2026-09-24 13:07 UTC201
    Close(Responder),
31ce2453c82fatagen2026-09-24 13:07 UTC202
}
31ce2453c82fatagen2026-09-24 13:07 UTC203
31ce2453c82fatagen2026-09-24 13:07 UTC204
struct Queues {
31ce2453c82fatagen2026-09-24 13:07 UTC205
    mutations: mpsc::Receiver<MutationJob>,
31ce2453c82fatagen2026-09-24 13:07 UTC206
    notifications: mpsc::Receiver<Job<NotificationCommand>>,
31ce2453c82fatagen2026-09-24 13:07 UTC207
    encryption: mpsc::Receiver<Job<EncryptionCommand>>,
31ce2453c82fatagen2026-09-24 13:07 UTC208
    session_changes: broadcast::Receiver<SessionChange>,
31ce2453c82fatagen2026-09-24 13:07 UTC209
}
31ce2453c82fatagen2026-09-24 13:07 UTC210
31ce2453c82fatagen2026-09-24 13:07 UTC211
pub struct Engine {
31ce2453c82fatagen2026-09-24 13:07 UTC212
    client: Client,
31ce2453c82fatagen2026-09-24 13:07 UTC213
    sync: Arc<SyncService>,
31ce2453c82fatagen2026-09-24 13:07 UTC214
    port: DriverPort,
31ce2453c82fatagen2026-09-24 13:07 UTC215
    config: EngineConfig,
31ce2453c82fatagen2026-09-24 13:07 UTC216
    media: Arc<MediaService>,
31ce2453c82fatagen2026-09-24 13:07 UTC217
    tasks: TaskRegistry,
31ce2453c82fatagen2026-09-24 13:07 UTC218
    rooms: HashMap<OwnedRoomId, RoomSlot>,
31ce2453c82fatagen2026-09-24 13:07 UTC219
    next_generation: u64,
31ce2453c82fatagen2026-09-24 13:07 UTC220
    exits: mpsc::UnboundedReceiver<Exited>,
31ce2453c82fatagen2026-09-24 13:07 UTC221
    exit_sender: mpsc::UnboundedSender<Exited>,
31ce2453c82fatagen2026-09-24 13:07 UTC222
    mutations: mpsc::Sender<MutationJob>,
31ce2453c82fatagen2026-09-24 13:07 UTC223
    notifications: mpsc::Sender<Job<NotificationCommand>>,
31ce2453c82fatagen2026-09-24 13:07 UTC224
    encryption: mpsc::Sender<Job<EncryptionCommand>>,
31ce2453c82fatagen2026-09-24 13:07 UTC225
    queues: Option<Queues>,
31ce2453c82fatagen2026-09-24 13:07 UTC226
    /// one progress forwarder per room that has uploaded, and whether it's subscribed yet
31ce2453c82fatagen2026-09-24 13:07 UTC227
    uploads: HashMap<OwnedRoomId, (AbortHandle, watch::Receiver<bool>)>,
31ce2453c82fatagen2026-09-24 13:07 UTC228
    stop: Arc<Notify>,
31ce2453c82fatagen2026-09-24 13:07 UTC229
}
31ce2453c82fatagen2026-09-24 13:07 UTC230
31ce2453c82fatagen2026-09-24 13:07 UTC231
impl Engine {
31ce2453c82fatagen2026-09-24 13:07 UTC232
    /// the escape hatch for a client you built yourself. `Session` is the usual way in
31ce2453c82fatagen2026-09-24 13:07 UTC233
    pub async fn from_client(
31ce2453c82fatagen2026-09-24 13:07 UTC234
        client: Client,
31ce2453c82fatagen2026-09-24 13:07 UTC235
        config: EngineConfig,
31ce2453c82fatagen2026-09-24 13:07 UTC236
    ) -> Result<(Self, ClientHandle, EventStream), EngineError> {
31ce2453c82fatagen2026-09-24 13:07 UTC237
        if client.user_id().is_none() {
31ce2453c82fatagen2026-09-24 13:07 UTC238
            return Err(EngineError::Unauthenticated);
31ce2453c82fatagen2026-09-24 13:07 UTC239
        }
31ce2453c82fatagen2026-09-24 13:07 UTC240
        if config.command_capacity == 0
31ce2453c82fatagen2026-09-24 13:07 UTC241
            || config.event_capacity == 0
31ce2453c82fatagen2026-09-24 13:07 UTC242
            || config.max_media_sources == 0
31ce2453c82fatagen2026-09-24 13:07 UTC243
        {
31ce2453c82fatagen2026-09-24 13:07 UTC244
            return Err(EngineError::ZeroCapacity);
31ce2453c82fatagen2026-09-24 13:07 UTC245
        }
31ce2453c82fatagen2026-09-24 13:07 UTC246
        let session_changes = client.subscribe_to_session_changes();
31ce2453c82fatagen2026-09-24 13:07 UTC247
        let sync = Arc::new(
31ce2453c82fatagen2026-09-24 13:07 UTC248
            SyncService::builder(client.clone())
31ce2453c82fatagen2026-09-24 13:07 UTC249
                .with_offline_mode()
31ce2453c82fatagen2026-09-24 13:07 UTC250
                .build()
31ce2453c82fatagen2026-09-24 13:07 UTC251
                .await
31ce2453c82fatagen2026-09-24 13:07 UTC252
                .map_err(EngineError::SyncService)?,
31ce2453c82fatagen2026-09-24 13:07 UTC253
        );
31ce2453c82fatagen2026-09-24 13:07 UTC254
        let media = Arc::new(MediaService::new(
31ce2453c82fatagen2026-09-24 13:07 UTC255
            client.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC256
            config.max_media_bytes,
31ce2453c82fatagen2026-09-24 13:07 UTC257
            config.max_media_sources,
31ce2453c82fatagen2026-09-24 13:07 UTC258
        ));
31ce2453c82fatagen2026-09-24 13:07 UTC259
        let (handle, port, events) = transport_pair(
31ce2453c82fatagen2026-09-24 13:07 UTC260
            client.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC261
            sync.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC262
            media.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC263
            config.command_capacity,
31ce2453c82fatagen2026-09-24 13:07 UTC264
            config.event_capacity,
31ce2453c82fatagen2026-09-24 13:07 UTC265
        );
31ce2453c82fatagen2026-09-24 13:07 UTC266
        client.send_queue().enable_upload_progress(true);
31ce2453c82fatagen2026-09-24 13:07 UTC267
        let (exit_sender, exits) = mpsc::unbounded_channel();
31ce2453c82fatagen2026-09-24 13:07 UTC268
        let (mutations, mutation_queue) = mpsc::channel(config.command_capacity);
31ce2453c82fatagen2026-09-24 13:07 UTC269
        let (notifications, notification_queue) = mpsc::channel(config.command_capacity);
31ce2453c82fatagen2026-09-24 13:07 UTC270
        let (encryption, encryption_queue) = mpsc::channel(config.command_capacity);
31ce2453c82fatagen2026-09-24 13:07 UTC271
        let engine = Self {
31ce2453c82fatagen2026-09-24 13:07 UTC272
            client,
31ce2453c82fatagen2026-09-24 13:07 UTC273
            sync,
31ce2453c82fatagen2026-09-24 13:07 UTC274
            port,
31ce2453c82fatagen2026-09-24 13:07 UTC275
            config,
31ce2453c82fatagen2026-09-24 13:07 UTC276
            media,
31ce2453c82fatagen2026-09-24 13:07 UTC277
            tasks: TaskRegistry {
31ce2453c82fatagen2026-09-24 13:07 UTC278
                tasks: JoinSet::new(),
31ce2453c82fatagen2026-09-24 13:07 UTC279
                contexts: HashMap::new(),
31ce2453c82fatagen2026-09-24 13:07 UTC280
            },
31ce2453c82fatagen2026-09-24 13:07 UTC281
            rooms: HashMap::new(),
31ce2453c82fatagen2026-09-24 13:07 UTC282
            next_generation: 0,
31ce2453c82fatagen2026-09-24 13:07 UTC283
            exits,
31ce2453c82fatagen2026-09-24 13:07 UTC284
            exit_sender,
31ce2453c82fatagen2026-09-24 13:07 UTC285
            mutations,
31ce2453c82fatagen2026-09-24 13:07 UTC286
            notifications,
31ce2453c82fatagen2026-09-24 13:07 UTC287
            encryption,
31ce2453c82fatagen2026-09-24 13:07 UTC288
            queues: Some(Queues {
31ce2453c82fatagen2026-09-24 13:07 UTC289
                mutations: mutation_queue,
31ce2453c82fatagen2026-09-24 13:07 UTC290
                notifications: notification_queue,
31ce2453c82fatagen2026-09-24 13:07 UTC291
                encryption: encryption_queue,
31ce2453c82fatagen2026-09-24 13:07 UTC292
                session_changes,
31ce2453c82fatagen2026-09-24 13:07 UTC293
            }),
31ce2453c82fatagen2026-09-24 13:07 UTC294
            uploads: HashMap::new(),
31ce2453c82fatagen2026-09-24 13:07 UTC295
            stop: Arc::new(Notify::new()),
31ce2453c82fatagen2026-09-24 13:07 UTC296
        };
31ce2453c82fatagen2026-09-24 13:07 UTC297
        Ok((engine, handle, events))
31ce2453c82fatagen2026-09-24 13:07 UTC298
    }
31ce2453c82fatagen2026-09-24 13:07 UTC299
31ce2453c82fatagen2026-09-24 13:07 UTC300
    pub async fn run(mut self) -> Result<(), EngineError> {
31ce2453c82fatagen2026-09-24 13:07 UTC301
        let events = self.port.event_sender();
31ce2453c82fatagen2026-09-24 13:07 UTC302
        let user_id = self
31ce2453c82fatagen2026-09-24 13:07 UTC303
            .client
31ce2453c82fatagen2026-09-24 13:07 UTC304
            .user_id()
31ce2453c82fatagen2026-09-24 13:07 UTC305
            .expect("authentication was checked during construction")
31ce2453c82fatagen2026-09-24 13:07 UTC306
            .to_owned();
31ce2453c82fatagen2026-09-24 13:07 UTC307
        self.port
31ce2453c82fatagen2026-09-24 13:07 UTC308
            .connection_sender()
31ce2453c82fatagen2026-09-24 13:07 UTC309
            .send_replace(ConnectionState::Connecting);
31ce2453c82fatagen2026-09-24 13:07 UTC310
        for event in [
31ce2453c82fatagen2026-09-24 13:07 UTC311
            Event::Authenticated { user_id },
31ce2453c82fatagen2026-09-24 13:07 UTC312
            Event::ConnectionChanged(ConnectionState::Connecting),
31ce2453c82fatagen2026-09-24 13:07 UTC313
        ] {
31ce2453c82fatagen2026-09-24 13:07 UTC314
            if events.send(event).await.is_err() {
31ce2453c82fatagen2026-09-24 13:07 UTC315
                self.shutdown().await;
31ce2453c82fatagen2026-09-24 13:07 UTC316
                return Err(EngineError::EventReceiverClosed);
31ce2453c82fatagen2026-09-24 13:07 UTC317
            }
31ce2453c82fatagen2026-09-24 13:07 UTC318
        }
31ce2453c82fatagen2026-09-24 13:07 UTC319
        self.start_tasks();
31ce2453c82fatagen2026-09-24 13:07 UTC320
        self.sync.start().await;
31ce2453c82fatagen2026-09-24 13:07 UTC321
31ce2453c82fatagen2026-09-24 13:07 UTC322
        let mut failure = None;
31ce2453c82fatagen2026-09-24 13:07 UTC323
        let mut shutdown_reply = None;
31ce2453c82fatagen2026-09-24 13:07 UTC324
        loop {
31ce2453c82fatagen2026-09-24 13:07 UTC325
            tokio::select! {
31ce2453c82fatagen2026-09-24 13:07 UTC326
                biased;
31ce2453c82fatagen2026-09-24 13:07 UTC327
                () = events.closed() => {
31ce2453c82fatagen2026-09-24 13:07 UTC328
                    failure = Some(EngineError::EventReceiverClosed);
31ce2453c82fatagen2026-09-24 13:07 UTC329
                    break;
31ce2453c82fatagen2026-09-24 13:07 UTC330
                }
31ce2453c82fatagen2026-09-24 13:07 UTC331
                () = self.stop.notified() => break,
31ce2453c82fatagen2026-09-24 13:07 UTC332
                request = self.port.recv() => {
31ce2453c82fatagen2026-09-24 13:07 UTC333
                    let Some(request) = request else { break };
31ce2453c82fatagen2026-09-24 13:07 UTC334
                    if let Some(reply) = self.dispatch(request).await {
31ce2453c82fatagen2026-09-24 13:07 UTC335
                        shutdown_reply = Some(reply);
31ce2453c82fatagen2026-09-24 13:07 UTC336
                        break;
31ce2453c82fatagen2026-09-24 13:07 UTC337
                    }
31ce2453c82fatagen2026-09-24 13:07 UTC338
                }
31ce2453c82fatagen2026-09-24 13:07 UTC339
                Some((context, result)) = self.tasks.join_next(), if !self.tasks.tasks.is_empty() => {
31ce2453c82fatagen2026-09-24 13:07 UTC340
                    let Err(error) = result else { continue };
31ce2453c82fatagen2026-09-24 13:07 UTC341
                    if !error.is_panic() {
31ce2453c82fatagen2026-09-24 13:07 UTC342
                        continue;
31ce2453c82fatagen2026-09-24 13:07 UTC343
                    }
31ce2453c82fatagen2026-09-24 13:07 UTC344
                    if let TaskContext::Command(label) = context {
31ce2453c82fatagen2026-09-24 13:07 UTC345
                        tracing::error!(label, %error, "command task panicked");
31ce2453c82fatagen2026-09-24 13:07 UTC346
                        continue;
31ce2453c82fatagen2026-09-24 13:07 UTC347
                    }
31ce2453c82fatagen2026-09-24 13:07 UTC348
                    failure = Some(EngineError::TaskPanicked { task: context.label() });
31ce2453c82fatagen2026-09-24 13:07 UTC349
                    break;
31ce2453c82fatagen2026-09-24 13:07 UTC350
                }
31ce2453c82fatagen2026-09-24 13:07 UTC351
                Some(exited) = self.exits.recv() => self.room_exited(exited).await,
31ce2453c82fatagen2026-09-24 13:07 UTC352
            }
31ce2453c82fatagen2026-09-24 13:07 UTC353
        }
31ce2453c82fatagen2026-09-24 13:07 UTC354
31ce2453c82fatagen2026-09-24 13:07 UTC355
        if let Some(error) = &failure {
31ce2453c82fatagen2026-09-24 13:07 UTC356
            let _ = events.send(Event::Failed(error.failure())).await;
31ce2453c82fatagen2026-09-24 13:07 UTC357
        }
31ce2453c82fatagen2026-09-24 13:07 UTC358
        self.shutdown().await;
31ce2453c82fatagen2026-09-24 13:07 UTC359
        let _ = events.send(Event::Stopped).await;
31ce2453c82fatagen2026-09-24 13:07 UTC360
        if let Some(reply) = shutdown_reply {
31ce2453c82fatagen2026-09-24 13:07 UTC361
            reply.done(Ok(()));
31ce2453c82fatagen2026-09-24 13:07 UTC362
        }
31ce2453c82fatagen2026-09-24 13:07 UTC363
        failure.map_or(Ok(()), Err)
31ce2453c82fatagen2026-09-24 13:07 UTC364
    }
31ce2453c82fatagen2026-09-24 13:07 UTC365
31ce2453c82fatagen2026-09-24 13:07 UTC366
    fn start_tasks(&mut self) {
31ce2453c82fatagen2026-09-24 13:07 UTC367
        let queues = self.queues.take().expect("engine tasks start once");
31ce2453c82fatagen2026-09-24 13:07 UTC368
        let client = &self.client;
31ce2453c82fatagen2026-09-24 13:07 UTC369
        let events = || self.port.event_sender();
31ce2453c82fatagen2026-09-24 13:07 UTC370
        let watchers: [(&'static str, BoxFuture<'static, ()>); 9] = [
31ce2453c82fatagen2026-09-24 13:07 UTC371
            (
31ce2453c82fatagen2026-09-24 13:07 UTC372
                "session change watcher",
31ce2453c82fatagen2026-09-24 13:07 UTC373
                Box::pin(watchers::session(
31ce2453c82fatagen2026-09-24 13:07 UTC374
                    queues.session_changes,
31ce2453c82fatagen2026-09-24 13:07 UTC375
                    events(),
31ce2453c82fatagen2026-09-24 13:07 UTC376
                    self.stop.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC377
                )),
31ce2453c82fatagen2026-09-24 13:07 UTC378
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC379
            (
31ce2453c82fatagen2026-09-24 13:07 UTC380
                "connection state watcher",
31ce2453c82fatagen2026-09-24 13:07 UTC381
                Box::pin(watchers::connection(
31ce2453c82fatagen2026-09-24 13:07 UTC382
                    self.sync.state(),
31ce2453c82fatagen2026-09-24 13:07 UTC383
                    self.sync.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC384
                    client.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC385
                    events(),
31ce2453c82fatagen2026-09-24 13:07 UTC386
                    self.port.connection_sender(),
31ce2453c82fatagen2026-09-24 13:07 UTC387
                )),
31ce2453c82fatagen2026-09-24 13:07 UTC388
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC389
            (
31ce2453c82fatagen2026-09-24 13:07 UTC390
                "send queue watcher",
31ce2453c82fatagen2026-09-24 13:07 UTC391
                Box::pin(watchers::send_queue_errors(
31ce2453c82fatagen2026-09-24 13:07 UTC392
                    client.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC393
                    client.send_queue().subscribe_errors(),
31ce2453c82fatagen2026-09-24 13:07 UTC394
                    self.port.connection_sender().subscribe(),
31ce2453c82fatagen2026-09-24 13:07 UTC395
                )),
31ce2453c82fatagen2026-09-24 13:07 UTC396
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC397
            (
31ce2453c82fatagen2026-09-24 13:07 UTC398
                "encryption state watcher",
31ce2453c82fatagen2026-09-24 13:07 UTC399
                Box::pin(watchers::encryption(client.clone(), events())),
31ce2453c82fatagen2026-09-24 13:07 UTC400
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC401
            (
31ce2453c82fatagen2026-09-24 13:07 UTC402
                "room list watcher",
31ce2453c82fatagen2026-09-24 13:07 UTC403
                Box::pin(watchers::rooms(client.clone(), events())),
31ce2453c82fatagen2026-09-24 13:07 UTC404
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC405
            (
31ce2453c82fatagen2026-09-24 13:07 UTC406
                "notification settings watcher",
31ce2453c82fatagen2026-09-24 13:07 UTC407
                Box::pin(watchers::notification_settings(client.clone(), events())),
31ce2453c82fatagen2026-09-24 13:07 UTC408
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC409
            (
31ce2453c82fatagen2026-09-24 13:07 UTC410
                "room command worker",
31ce2453c82fatagen2026-09-24 13:07 UTC411
                Box::pin(mutations::run_scheduler(
31ce2453c82fatagen2026-09-24 13:07 UTC412
                    client.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC413
                    queues.mutations,
31ce2453c82fatagen2026-09-24 13:07 UTC414
                    self.config.command_capacity,
31ce2453c82fatagen2026-09-24 13:07 UTC415
                )),
31ce2453c82fatagen2026-09-24 13:07 UTC416
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC417
            (
31ce2453c82fatagen2026-09-24 13:07 UTC418
                "notification command worker",
31ce2453c82fatagen2026-09-24 13:07 UTC419
                Box::pin(commands::run_notifications(
31ce2453c82fatagen2026-09-24 13:07 UTC420
                    client.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC421
                    queues.notifications,
31ce2453c82fatagen2026-09-24 13:07 UTC422
                )),
31ce2453c82fatagen2026-09-24 13:07 UTC423
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC424
            (
31ce2453c82fatagen2026-09-24 13:07 UTC425
                "encryption command worker",
31ce2453c82fatagen2026-09-24 13:07 UTC426
                Box::pin(commands::run_encryption(
31ce2453c82fatagen2026-09-24 13:07 UTC427
                    client.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC428
                    events(),
31ce2453c82fatagen2026-09-24 13:07 UTC429
                    queues.encryption,
31ce2453c82fatagen2026-09-24 13:07 UTC430
                )),
31ce2453c82fatagen2026-09-24 13:07 UTC431
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC432
        ];
31ce2453c82fatagen2026-09-24 13:07 UTC433
        for (label, task) in watchers {
31ce2453c82fatagen2026-09-24 13:07 UTC434
            self.tasks.spawn(TaskContext::Watcher(label), task);
31ce2453c82fatagen2026-09-24 13:07 UTC435
        }
31ce2453c82fatagen2026-09-24 13:07 UTC436
    }
31ce2453c82fatagen2026-09-24 13:07 UTC437
31ce2453c82fatagen2026-09-24 13:07 UTC438
    /// hands back the shutdown request's responder, which is answered once we've stopped
31ce2453c82fatagen2026-09-24 13:07 UTC439
    async fn dispatch(&mut self, Request { command, reply }: Request) -> Option<Responder> {
31ce2453c82fatagen2026-09-24 13:07 UTC440
        let client = self.client.clone();
31ce2453c82fatagen2026-09-24 13:07 UTC441
        match command {
31ce2453c82fatagen2026-09-24 13:07 UTC442
            Command::Lifecycle(LifecycleCommand::Shutdown) => return Some(reply),
31ce2453c82fatagen2026-09-24 13:07 UTC443
            Command::Account(AccountCommand::Logout) => {
31ce2453c82fatagen2026-09-24 13:07 UTC444
                let events = self.port.event_sender();
31ce2453c82fatagen2026-09-24 13:07 UTC445
                let stop = self.stop.clone();
31ce2453c82fatagen2026-09-24 13:07 UTC446
                self.spawn_command("logout", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC447
                    client
31ce2453c82fatagen2026-09-24 13:07 UTC448
                        .logout()
31ce2453c82fatagen2026-09-24 13:07 UTC449
                        .await
31ce2453c82fatagen2026-09-24 13:07 UTC450
                        .map_err(|source| Error::from_sdk("logout failed", source))?;
31ce2453c82fatagen2026-09-24 13:07 UTC451
                    let _ = events.send(Event::LoggedOut).await;
31ce2453c82fatagen2026-09-24 13:07 UTC452
                    stop.notify_one();
31ce2453c82fatagen2026-09-24 13:07 UTC453
                    Ok(())
31ce2453c82fatagen2026-09-24 13:07 UTC454
                });
31ce2453c82fatagen2026-09-24 13:07 UTC455
            }
31ce2453c82fatagen2026-09-24 13:07 UTC456
            Command::Account(AccountCommand::SetAccountData {
31ce2453c82fatagen2026-09-24 13:07 UTC457
                event_type,
31ce2453c82fatagen2026-09-24 13:07 UTC458
                content,
31ce2453c82fatagen2026-09-24 13:07 UTC459
            }) => enqueue(
31ce2453c82fatagen2026-09-24 13:07 UTC460
                &self.mutations,
31ce2453c82fatagen2026-09-24 13:07 UTC461
                Job {
31ce2453c82fatagen2026-09-24 13:07 UTC462
                    command: Mutation::GlobalAccountData {
31ce2453c82fatagen2026-09-24 13:07 UTC463
                        event_type,
31ce2453c82fatagen2026-09-24 13:07 UTC464
                        content,
31ce2453c82fatagen2026-09-24 13:07 UTC465
                    },
31ce2453c82fatagen2026-09-24 13:07 UTC466
                    reply,
31ce2453c82fatagen2026-09-24 13:07 UTC467
                },
31ce2453c82fatagen2026-09-24 13:07 UTC468
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC469
            Command::Account(AccountCommand::SetDisplayName(name)) => {
31ce2453c82fatagen2026-09-24 13:07 UTC470
                self.spawn_command("set display name", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC471
                    client
31ce2453c82fatagen2026-09-24 13:07 UTC472
                        .account()
31ce2453c82fatagen2026-09-24 13:07 UTC473
                        .set_display_name(name.as_deref())
31ce2453c82fatagen2026-09-24 13:07 UTC474
                        .await
31ce2453c82fatagen2026-09-24 13:07 UTC475
                        .map_err(|source| Error::from_sdk("setting display name failed", source))
31ce2453c82fatagen2026-09-24 13:07 UTC476
                });
31ce2453c82fatagen2026-09-24 13:07 UTC477
            }
31ce2453c82fatagen2026-09-24 13:07 UTC478
            Command::Account(AccountCommand::SetAvatar(avatar)) => {
31ce2453c82fatagen2026-09-24 13:07 UTC479
                self.spawn_command("set avatar", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC480
                    client
31ce2453c82fatagen2026-09-24 13:07 UTC481
                        .account()
31ce2453c82fatagen2026-09-24 13:07 UTC482
                        .set_avatar_url(avatar.as_deref())
31ce2453c82fatagen2026-09-24 13:07 UTC483
                        .await
31ce2453c82fatagen2026-09-24 13:07 UTC484
                        .map_err(|source| Error::from_sdk("setting avatar failed", source))
31ce2453c82fatagen2026-09-24 13:07 UTC485
                });
31ce2453c82fatagen2026-09-24 13:07 UTC486
            }
31ce2453c82fatagen2026-09-24 13:07 UTC487
            Command::Account(AccountCommand::UploadAvatar { mimetype, bytes }) => {
31ce2453c82fatagen2026-09-24 13:07 UTC488
                let max_bytes = self.config.max_media_bytes;
31ce2453c82fatagen2026-09-24 13:07 UTC489
                self.spawn_command("upload avatar", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC490
                    commands::upload_avatar(&client, &mimetype, bytes, max_bytes).await
31ce2453c82fatagen2026-09-24 13:07 UTC491
                });
31ce2453c82fatagen2026-09-24 13:07 UTC492
            }
31ce2453c82fatagen2026-09-24 13:07 UTC493
            Command::Account(AccountCommand::RenameDevice {
31ce2453c82fatagen2026-09-24 13:07 UTC494
                device_id,
31ce2453c82fatagen2026-09-24 13:07 UTC495
                display_name,
31ce2453c82fatagen2026-09-24 13:07 UTC496
            }) => self.spawn_command("rename device", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC497
                commands::rename_device(&client, &device_id, &display_name).await
31ce2453c82fatagen2026-09-24 13:07 UTC498
            }),
31ce2453c82fatagen2026-09-24 13:07 UTC499
            Command::Account(AccountCommand::DeleteDevices { device_ids, auth }) => {
31ce2453c82fatagen2026-09-24 13:07 UTC500
                self.spawn_command("delete devices", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC501
                    commands::delete_devices(&client, &device_ids, auth).await
31ce2453c82fatagen2026-09-24 13:07 UTC502
                });
31ce2453c82fatagen2026-09-24 13:07 UTC503
            }
31ce2453c82fatagen2026-09-24 13:07 UTC504
            Command::Room(RoomCommand::Open(room_id)) => self.open_room(room_id, reply).await,
31ce2453c82fatagen2026-09-24 13:07 UTC505
            Command::Room(RoomCommand::Close(room_id)) => self.close_room(room_id, reply).await,
31ce2453c82fatagen2026-09-24 13:07 UTC506
            Command::Room(RoomCommand::SetTyping { room_id, typing }) => {
31ce2453c82fatagen2026-09-24 13:07 UTC507
                self.spawn_command("typing", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC508
                    commands::set_typing(&client, &room_id, typing).await
31ce2453c82fatagen2026-09-24 13:07 UTC509
                });
31ce2453c82fatagen2026-09-24 13:07 UTC510
            }
31ce2453c82fatagen2026-09-24 13:07 UTC511
            Command::Room(RoomCommand::CancelSend {
31ce2453c82fatagen2026-09-24 13:07 UTC512
                room_id,
31ce2453c82fatagen2026-09-24 13:07 UTC513
                transaction_id,
31ce2453c82fatagen2026-09-24 13:07 UTC514
            }) => self.spawn_command("cancel send", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC515
                commands::cancel_send(&client, &room_id, &transaction_id).await
31ce2453c82fatagen2026-09-24 13:07 UTC516
            }),
31ce2453c82fatagen2026-09-24 13:07 UTC517
            Command::Room(RoomCommand::RetrySend {
31ce2453c82fatagen2026-09-24 13:07 UTC518
                room_id,
31ce2453c82fatagen2026-09-24 13:07 UTC519
                transaction_id,
31ce2453c82fatagen2026-09-24 13:07 UTC520
            }) => self.spawn_command("retry send", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC521
                commands::retry_send(&client, &room_id, &transaction_id).await
31ce2453c82fatagen2026-09-24 13:07 UTC522
            }),
31ce2453c82fatagen2026-09-24 13:07 UTC523
            Command::Room(command) => enqueue(
31ce2453c82fatagen2026-09-24 13:07 UTC524
                &self.mutations,
31ce2453c82fatagen2026-09-24 13:07 UTC525
                Job {
31ce2453c82fatagen2026-09-24 13:07 UTC526
                    command: Mutation::Room(command),
31ce2453c82fatagen2026-09-24 13:07 UTC527
                    reply,
31ce2453c82fatagen2026-09-24 13:07 UTC528
                },
31ce2453c82fatagen2026-09-24 13:07 UTC529
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC530
            Command::Timeline(command) => self.route_timeline(command, reply),
31ce2453c82fatagen2026-09-24 13:07 UTC531
            Command::Media(MediaCommand::Upload(upload)) => self.upload(upload, reply),
31ce2453c82fatagen2026-09-24 13:07 UTC532
            Command::Encryption(command) => enqueue(&self.encryption, Job { command, reply }),
31ce2453c82fatagen2026-09-24 13:07 UTC533
            Command::Notification(command) => {
31ce2453c82fatagen2026-09-24 13:07 UTC534
                enqueue(&self.notifications, Job { command, reply });
31ce2453c82fatagen2026-09-24 13:07 UTC535
            }
31ce2453c82fatagen2026-09-24 13:07 UTC536
        }
31ce2453c82fatagen2026-09-24 13:07 UTC537
        None
31ce2453c82fatagen2026-09-24 13:07 UTC538
    }
31ce2453c82fatagen2026-09-24 13:07 UTC539
31ce2453c82fatagen2026-09-24 13:07 UTC540
    fn spawn_command(
31ce2453c82fatagen2026-09-24 13:07 UTC541
        &mut self,
31ce2453c82fatagen2026-09-24 13:07 UTC542
        label: &'static str,
31ce2453c82fatagen2026-09-24 13:07 UTC543
        reply: Responder,
31ce2453c82fatagen2026-09-24 13:07 UTC544
        work: impl Future<Output = Result<(), Error>> + Send + 'static,
31ce2453c82fatagen2026-09-24 13:07 UTC545
    ) {
31ce2453c82fatagen2026-09-24 13:07 UTC546
        self.tasks.spawn(TaskContext::Command(label), async move {
31ce2453c82fatagen2026-09-24 13:07 UTC547
            reply.done(work.await);
31ce2453c82fatagen2026-09-24 13:07 UTC548
        });
31ce2453c82fatagen2026-09-24 13:07 UTC549
    }
31ce2453c82fatagen2026-09-24 13:07 UTC550
31ce2453c82fatagen2026-09-24 13:07 UTC551
    fn upload(&mut self, upload: crate::AttachmentUpload, reply: Responder) {
31ce2453c82fatagen2026-09-24 13:07 UTC552
        let Some(room) = self.client.get_room(upload.timeline_id.room_id()) else {
31ce2453c82fatagen2026-09-24 13:07 UTC553
            return reply.done(Err(Error::room_unknown()));
31ce2453c82fatagen2026-09-24 13:07 UTC554
        };
31ce2453c82fatagen2026-09-24 13:07 UTC555
        let room_id = room.room_id().to_owned();
31ce2453c82fatagen2026-09-24 13:07 UTC556
        self.uploads
31ce2453c82fatagen2026-09-24 13:07 UTC557
            .retain(|_, (forwarder, _)| !forwarder.is_finished());
31ce2453c82fatagen2026-09-24 13:07 UTC558
        let mut ready = match self.uploads.get(&room_id) {
31ce2453c82fatagen2026-09-24 13:07 UTC559
            Some((_, ready)) => ready.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC560
            None => {
31ce2453c82fatagen2026-09-24 13:07 UTC561
                let (subscribed, ready) = watch::channel(false);
31ce2453c82fatagen2026-09-24 13:07 UTC562
                let forwarder = self.tasks.spawn(
31ce2453c82fatagen2026-09-24 13:07 UTC563
                    TaskContext::Command("upload watcher"),
31ce2453c82fatagen2026-09-24 13:07 UTC564
                    watchers::room_uploads(room.clone(), self.port.event_sender(), subscribed),
31ce2453c82fatagen2026-09-24 13:07 UTC565
                );
31ce2453c82fatagen2026-09-24 13:07 UTC566
                self.uploads.insert(room_id, (forwarder, ready.clone()));
31ce2453c82fatagen2026-09-24 13:07 UTC567
                ready
31ce2453c82fatagen2026-09-24 13:07 UTC568
            }
31ce2453c82fatagen2026-09-24 13:07 UTC569
        };
31ce2453c82fatagen2026-09-24 13:07 UTC570
        let max_bytes = self.config.max_media_bytes;
31ce2453c82fatagen2026-09-24 13:07 UTC571
        // queueing before the forwarder listens would lose the first progress ticks
31ce2453c82fatagen2026-09-24 13:07 UTC572
        self.spawn_command("upload", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC573
            ready
31ce2453c82fatagen2026-09-24 13:07 UTC574
                .wait_for(|subscribed| *subscribed)
31ce2453c82fatagen2026-09-24 13:07 UTC575
                .await
31ce2453c82fatagen2026-09-24 13:07 UTC576
                .map_err(|_| Error::new(ErrorKind::Store, "reading the media send queue failed"))?;
31ce2453c82fatagen2026-09-24 13:07 UTC577
            media::queue_upload(room, upload, max_bytes).await
31ce2453c82fatagen2026-09-24 13:07 UTC578
        });
31ce2453c82fatagen2026-09-24 13:07 UTC579
    }
31ce2453c82fatagen2026-09-24 13:07 UTC580
31ce2453c82fatagen2026-09-24 13:07 UTC581
    fn route_timeline(&mut self, command: TimelineCommand, reply: Responder) {
31ce2453c82fatagen2026-09-24 13:07 UTC582
        let sender = match self.rooms.get(command.room_id()) {
31ce2453c82fatagen2026-09-24 13:07 UTC583
            Some(RoomSlot::Active { sender, .. }) => Some(sender.clone()),
31ce2453c82fatagen2026-09-24 13:07 UTC584
            _ => None,
31ce2453c82fatagen2026-09-24 13:07 UTC585
        };
31ce2453c82fatagen2026-09-24 13:07 UTC586
        match (sender, command) {
31ce2453c82fatagen2026-09-24 13:07 UTC587
            (Some(sender), command) => send_actor(&sender, ActorRequest::Execute(command, reply)),
31ce2453c82fatagen2026-09-24 13:07 UTC588
            (
31ce2453c82fatagen2026-09-24 13:07 UTC589
                None,
31ce2453c82fatagen2026-09-24 13:07 UTC590
                TimelineCommand::MarkRead {
31ce2453c82fatagen2026-09-24 13:07 UTC591
                    timeline_id,
31ce2453c82fatagen2026-09-24 13:07 UTC592
                    event_id,
31ce2453c82fatagen2026-09-24 13:07 UTC593
                },
31ce2453c82fatagen2026-09-24 13:07 UTC594
            ) => {
31ce2453c82fatagen2026-09-24 13:07 UTC595
                let client = self.client.clone();
31ce2453c82fatagen2026-09-24 13:07 UTC596
                self.spawn_command("mark read", reply, async move {
31ce2453c82fatagen2026-09-24 13:07 UTC597
                    commands::mark_read(&client, &timeline_id, event_id).await
31ce2453c82fatagen2026-09-24 13:07 UTC598
                });
31ce2453c82fatagen2026-09-24 13:07 UTC599
            }
31ce2453c82fatagen2026-09-24 13:07 UTC600
            (None, _) => reply.done(Err(Error::new(ErrorKind::RoomNotOpen, "room is not open"))),
31ce2453c82fatagen2026-09-24 13:07 UTC601
        }
31ce2453c82fatagen2026-09-24 13:07 UTC602
    }
31ce2453c82fatagen2026-09-24 13:07 UTC603
31ce2453c82fatagen2026-09-24 13:07 UTC604
    async fn open_room(&mut self, room_id: OwnedRoomId, reply: Responder) {
31ce2453c82fatagen2026-09-24 13:07 UTC605
        match self.rooms.get_mut(&room_id) {
31ce2453c82fatagen2026-09-24 13:07 UTC606
            Some(RoomSlot::Active { sender, .. }) => send_actor(sender, ActorRequest::Open(reply)),
31ce2453c82fatagen2026-09-24 13:07 UTC607
            Some(RoomSlot::Closing { pending, .. }) => {
31ce2453c82fatagen2026-09-24 13:07 UTC608
                defer(pending, Deferred::Open(reply), self.config.command_capacity);
31ce2453c82fatagen2026-09-24 13:07 UTC609
            }
31ce2453c82fatagen2026-09-24 13:07 UTC610
            None => self.start_actor(room_id, reply).await,
31ce2453c82fatagen2026-09-24 13:07 UTC611
        }
31ce2453c82fatagen2026-09-24 13:07 UTC612
    }
31ce2453c82fatagen2026-09-24 13:07 UTC613
31ce2453c82fatagen2026-09-24 13:07 UTC614
    async fn close_room(&mut self, room_id: OwnedRoomId, reply: Responder) {
31ce2453c82fatagen2026-09-24 13:07 UTC615
        let (generation, sender) = match self.rooms.get_mut(&room_id) {
31ce2453c82fatagen2026-09-24 13:07 UTC616
            None => return reply.done(Ok(())),
31ce2453c82fatagen2026-09-24 13:07 UTC617
            Some(RoomSlot::Closing { pending, .. }) => {
31ce2453c82fatagen2026-09-24 13:07 UTC618
                return defer(
31ce2453c82fatagen2026-09-24 13:07 UTC619
                    pending,
31ce2453c82fatagen2026-09-24 13:07 UTC620
                    Deferred::Close(reply),
31ce2453c82fatagen2026-09-24 13:07 UTC621
                    self.config.command_capacity,
31ce2453c82fatagen2026-09-24 13:07 UTC622
                );
31ce2453c82fatagen2026-09-24 13:07 UTC623
            }
31ce2453c82fatagen2026-09-24 13:07 UTC624
            Some(RoomSlot::Active { generation, sender }) => (*generation, sender.clone()),
31ce2453c82fatagen2026-09-24 13:07 UTC625
        };
31ce2453c82fatagen2026-09-24 13:07 UTC626
        match sender.try_send(ActorRequest::Close(reply)) {
31ce2453c82fatagen2026-09-24 13:07 UTC627
            // a closed channel means the actor already left on its own, which is closed enough
31ce2453c82fatagen2026-09-24 13:07 UTC628
            Ok(()) | Err(mpsc::error::TrySendError::Closed(_)) => {}
31ce2453c82fatagen2026-09-24 13:07 UTC629
            Err(mpsc::error::TrySendError::Full(request)) => {
31ce2453c82fatagen2026-09-24 13:07 UTC630
                return request.into_reply().done(Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC631
                    ErrorKind::RoomBusy,
31ce2453c82fatagen2026-09-24 13:07 UTC632
                    "room command queue is full",
31ce2453c82fatagen2026-09-24 13:07 UTC633
                )));
31ce2453c82fatagen2026-09-24 13:07 UTC634
            }
31ce2453c82fatagen2026-09-24 13:07 UTC635
        }
31ce2453c82fatagen2026-09-24 13:07 UTC636
        self.rooms.insert(
31ce2453c82fatagen2026-09-24 13:07 UTC637
            room_id,
31ce2453c82fatagen2026-09-24 13:07 UTC638
            RoomSlot::Closing {
31ce2453c82fatagen2026-09-24 13:07 UTC639
                generation,
31ce2453c82fatagen2026-09-24 13:07 UTC640
                pending: VecDeque::new(),
31ce2453c82fatagen2026-09-24 13:07 UTC641
            },
31ce2453c82fatagen2026-09-24 13:07 UTC642
        );
31ce2453c82fatagen2026-09-24 13:07 UTC643
        self.update_room_subscriptions().await;
31ce2453c82fatagen2026-09-24 13:07 UTC644
    }
31ce2453c82fatagen2026-09-24 13:07 UTC645
31ce2453c82fatagen2026-09-24 13:07 UTC646
    async fn start_actor(&mut self, room_id: OwnedRoomId, reply: Responder) {
31ce2453c82fatagen2026-09-24 13:07 UTC647
        let Some(room) = self.client.get_room(&room_id) else {
31ce2453c82fatagen2026-09-24 13:07 UTC648
            return reply.done(Err(Error::room_unknown()));
31ce2453c82fatagen2026-09-24 13:07 UTC649
        };
31ce2453c82fatagen2026-09-24 13:07 UTC650
        self.next_generation = self.next_generation.wrapping_add(1);
31ce2453c82fatagen2026-09-24 13:07 UTC651
        let generation = self.next_generation;
31ce2453c82fatagen2026-09-24 13:07 UTC652
        let (sender, requests) = mpsc::channel(self.config.command_capacity);
31ce2453c82fatagen2026-09-24 13:07 UTC653
        let (supervisor, panics) = Supervisor::new();
31ce2453c82fatagen2026-09-24 13:07 UTC654
        let actor = RoomActor {
31ce2453c82fatagen2026-09-24 13:07 UTC655
            room_id: room_id.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC656
            generation,
31ce2453c82fatagen2026-09-24 13:07 UTC657
            room,
31ce2453c82fatagen2026-09-24 13:07 UTC658
            requests,
31ce2453c82fatagen2026-09-24 13:07 UTC659
            exits: self.exit_sender.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC660
            context: SessionContext {
31ce2453c82fatagen2026-09-24 13:07 UTC661
                events: self.port.event_sender(),
31ce2453c82fatagen2026-09-24 13:07 UTC662
                media: self.media.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC663
                current_user_id: self.client.user_id().map(ToOwned::to_owned),
31ce2453c82fatagen2026-09-24 13:07 UTC664
                initial_history_limit: self.config.initial_history_limit,
31ce2453c82fatagen2026-09-24 13:07 UTC665
                supervisor,
31ce2453c82fatagen2026-09-24 13:07 UTC666
            },
31ce2453c82fatagen2026-09-24 13:07 UTC667
            panics,
31ce2453c82fatagen2026-09-24 13:07 UTC668
            opening: vec![reply],
31ce2453c82fatagen2026-09-24 13:07 UTC669
        };
31ce2453c82fatagen2026-09-24 13:07 UTC670
        self.rooms
31ce2453c82fatagen2026-09-24 13:07 UTC671
            .insert(room_id.clone(), RoomSlot::Active { generation, sender });
31ce2453c82fatagen2026-09-24 13:07 UTC672
        self.tasks.spawn(
31ce2453c82fatagen2026-09-24 13:07 UTC673
            TaskContext::RoomActor {
31ce2453c82fatagen2026-09-24 13:07 UTC674
                room_id,
31ce2453c82fatagen2026-09-24 13:07 UTC675
                generation,
31ce2453c82fatagen2026-09-24 13:07 UTC676
            },
31ce2453c82fatagen2026-09-24 13:07 UTC677
            actor.run(),
31ce2453c82fatagen2026-09-24 13:07 UTC678
        );
31ce2453c82fatagen2026-09-24 13:07 UTC679
        self.update_room_subscriptions().await;
31ce2453c82fatagen2026-09-24 13:07 UTC680
    }
31ce2453c82fatagen2026-09-24 13:07 UTC681
31ce2453c82fatagen2026-09-24 13:07 UTC682
    async fn room_exited(
31ce2453c82fatagen2026-09-24 13:07 UTC683
        &mut self,
31ce2453c82fatagen2026-09-24 13:07 UTC684
        Exited {
31ce2453c82fatagen2026-09-24 13:07 UTC685
            room_id,
31ce2453c82fatagen2026-09-24 13:07 UTC686
            generation,
31ce2453c82fatagen2026-09-24 13:07 UTC687
        }: Exited,
31ce2453c82fatagen2026-09-24 13:07 UTC688
    ) {
31ce2453c82fatagen2026-09-24 13:07 UTC689
        if self
31ce2453c82fatagen2026-09-24 13:07 UTC690
            .rooms
31ce2453c82fatagen2026-09-24 13:07 UTC691
            .get(&room_id)
31ce2453c82fatagen2026-09-24 13:07 UTC692
            .is_none_or(|slot| slot.generation() != generation)
31ce2453c82fatagen2026-09-24 13:07 UTC693
        {
31ce2453c82fatagen2026-09-24 13:07 UTC694
            return;
31ce2453c82fatagen2026-09-24 13:07 UTC695
        }
31ce2453c82fatagen2026-09-24 13:07 UTC696
        if let Some(RoomSlot::Closing { pending, .. }) = self.rooms.remove(&room_id) {
31ce2453c82fatagen2026-09-24 13:07 UTC697
            for deferred in pending {
31ce2453c82fatagen2026-09-24 13:07 UTC698
                match deferred {
31ce2453c82fatagen2026-09-24 13:07 UTC699
                    Deferred::Open(reply) => self.open_room(room_id.clone(), reply).await,
31ce2453c82fatagen2026-09-24 13:07 UTC700
                    Deferred::Close(reply) => self.close_room(room_id.clone(), reply).await,
31ce2453c82fatagen2026-09-24 13:07 UTC701
                }
31ce2453c82fatagen2026-09-24 13:07 UTC702
            }
31ce2453c82fatagen2026-09-24 13:07 UTC703
        }
31ce2453c82fatagen2026-09-24 13:07 UTC704
        self.update_room_subscriptions().await;
31ce2453c82fatagen2026-09-24 13:07 UTC705
    }
31ce2453c82fatagen2026-09-24 13:07 UTC706
31ce2453c82fatagen2026-09-24 13:07 UTC707
    async fn update_room_subscriptions(&self) {
31ce2453c82fatagen2026-09-24 13:07 UTC708
        let room_ids = self
31ce2453c82fatagen2026-09-24 13:07 UTC709
            .rooms
31ce2453c82fatagen2026-09-24 13:07 UTC710
            .iter()
31ce2453c82fatagen2026-09-24 13:07 UTC711
            .filter(|(_, slot)| matches!(slot, RoomSlot::Active { .. }))
31ce2453c82fatagen2026-09-24 13:07 UTC712
            .map(|(room_id, _)| room_id.as_ref())
31ce2453c82fatagen2026-09-24 13:07 UTC713
            .collect::<Vec<&RoomId>>();
31ce2453c82fatagen2026-09-24 13:07 UTC714
        self.sync
31ce2453c82fatagen2026-09-24 13:07 UTC715
            .room_list_service()
31ce2453c82fatagen2026-09-24 13:07 UTC716
            .subscribe_to_rooms(&room_ids)
31ce2453c82fatagen2026-09-24 13:07 UTC717
            .await;
31ce2453c82fatagen2026-09-24 13:07 UTC718
    }
31ce2453c82fatagen2026-09-24 13:07 UTC719
31ce2453c82fatagen2026-09-24 13:07 UTC720
    async fn shutdown(&mut self) {
31ce2453c82fatagen2026-09-24 13:07 UTC721
        self.port.close();
31ce2453c82fatagen2026-09-24 13:07 UTC722
        self.tasks.shutdown().await;
31ce2453c82fatagen2026-09-24 13:07 UTC723
        self.rooms.clear();
31ce2453c82fatagen2026-09-24 13:07 UTC724
        self.uploads.clear();
31ce2453c82fatagen2026-09-24 13:07 UTC725
        self.media.clear().await;
31ce2453c82fatagen2026-09-24 13:07 UTC726
        self.sync.stop().await;
31ce2453c82fatagen2026-09-24 13:07 UTC727
        self.port
31ce2453c82fatagen2026-09-24 13:07 UTC728
            .connection_sender()
31ce2453c82fatagen2026-09-24 13:07 UTC729
            .send_replace(ConnectionState::Stopped);
31ce2453c82fatagen2026-09-24 13:07 UTC730
        let _ = self
31ce2453c82fatagen2026-09-24 13:07 UTC731
            .port
31ce2453c82fatagen2026-09-24 13:07 UTC732
            .event_sender()
31ce2453c82fatagen2026-09-24 13:07 UTC733
            .send(Event::ConnectionChanged(ConnectionState::Stopped))
31ce2453c82fatagen2026-09-24 13:07 UTC734
            .await;
31ce2453c82fatagen2026-09-24 13:07 UTC735
    }
31ce2453c82fatagen2026-09-24 13:07 UTC736
}
31ce2453c82fatagen2026-09-24 13:07 UTC737
31ce2453c82fatagen2026-09-24 13:07 UTC738
fn send_actor(sender: &mpsc::Sender<ActorRequest>, request: ActorRequest) {
31ce2453c82fatagen2026-09-24 13:07 UTC739
    if let Err(error) = sender.try_send(request) {
31ce2453c82fatagen2026-09-24 13:07 UTC740
        let (request, error) = match error {
31ce2453c82fatagen2026-09-24 13:07 UTC741
            mpsc::error::TrySendError::Full(request) => (
31ce2453c82fatagen2026-09-24 13:07 UTC742
                request,
31ce2453c82fatagen2026-09-24 13:07 UTC743
                Error::new(ErrorKind::RoomBusy, "room command queue is full"),
31ce2453c82fatagen2026-09-24 13:07 UTC744
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC745
            mpsc::error::TrySendError::Closed(request) => (
31ce2453c82fatagen2026-09-24 13:07 UTC746
                request,
31ce2453c82fatagen2026-09-24 13:07 UTC747
                Error::new(ErrorKind::RoomNotOpen, "room is not open"),
31ce2453c82fatagen2026-09-24 13:07 UTC748
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC749
        };
31ce2453c82fatagen2026-09-24 13:07 UTC750
        request.into_reply().done(Err(error));
31ce2453c82fatagen2026-09-24 13:07 UTC751
    }
31ce2453c82fatagen2026-09-24 13:07 UTC752
}
31ce2453c82fatagen2026-09-24 13:07 UTC753
31ce2453c82fatagen2026-09-24 13:07 UTC754
fn defer(pending: &mut VecDeque<Deferred>, deferred: Deferred, capacity: usize) {
31ce2453c82fatagen2026-09-24 13:07 UTC755
    if pending.len() < capacity {
31ce2453c82fatagen2026-09-24 13:07 UTC756
        return pending.push_back(deferred);
31ce2453c82fatagen2026-09-24 13:07 UTC757
    }
31ce2453c82fatagen2026-09-24 13:07 UTC758
    let (Deferred::Open(reply) | Deferred::Close(reply)) = deferred;
31ce2453c82fatagen2026-09-24 13:07 UTC759
    reply.done(Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC760
        ErrorKind::RoomBusy,
31ce2453c82fatagen2026-09-24 13:07 UTC761
        "room lifecycle queue is full",
31ce2453c82fatagen2026-09-24 13:07 UTC762
    )));
31ce2453c82fatagen2026-09-24 13:07 UTC763
}
31ce2453c82fatagen2026-09-24 13:07 UTC764
31ce2453c82fatagen2026-09-24 13:07 UTC765
fn enqueue<T>(sender: &mpsc::Sender<Job<T>>, job: Job<T>) {
31ce2453c82fatagen2026-09-24 13:07 UTC766
    if let Err(error) = sender.try_send(job) {
31ce2453c82fatagen2026-09-24 13:07 UTC767
        let (job, error) = match error {
31ce2453c82fatagen2026-09-24 13:07 UTC768
            mpsc::error::TrySendError::Full(job) => (
31ce2453c82fatagen2026-09-24 13:07 UTC769
                job,
31ce2453c82fatagen2026-09-24 13:07 UTC770
                Error::new(ErrorKind::QueueFull, "command queue is full"),
31ce2453c82fatagen2026-09-24 13:07 UTC771
            ),
31ce2453c82fatagen2026-09-24 13:07 UTC772
            mpsc::error::TrySendError::Closed(job) => (job, Error::interrupted()),
31ce2453c82fatagen2026-09-24 13:07 UTC773
        };
31ce2453c82fatagen2026-09-24 13:07 UTC774
        job.reply.done(Err(error));
31ce2453c82fatagen2026-09-24 13:07 UTC775
    }
31ce2453c82fatagen2026-09-24 13:07 UTC776
}