atagen

macltok

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

Blame: src/mirror.rs

a319bae745d8atagen2026-09-26 09:59 UTC1
use std::{
a319bae745d8atagen2026-09-26 09:59 UTC2
    collections::HashMap,
a319bae745d8atagen2026-09-26 09:59 UTC3
    fmt,
a319bae745d8atagen2026-09-26 09:59 UTC4
    pin::Pin,
a319bae745d8atagen2026-09-26 09:59 UTC5
    sync::{Arc, Mutex, MutexGuard, PoisonError},
a319bae745d8atagen2026-09-26 09:59 UTC6
    task::{Context, Poll},
a319bae745d8atagen2026-09-26 09:59 UTC7
    time::Duration,
a319bae745d8atagen2026-09-26 09:59 UTC8
};
a319bae745d8atagen2026-09-26 09:59 UTC9
a319bae745d8atagen2026-09-26 09:59 UTC10
use futures_util::{Stream, StreamExt};
a319bae745d8atagen2026-09-26 09:59 UTC11
use imbl::Vector;
a319bae745d8atagen2026-09-26 09:59 UTC12
use matrix_sdk::{
a319bae745d8atagen2026-09-26 09:59 UTC13
    SendOutsideWasm,
a319bae745d8atagen2026-09-26 09:59 UTC14
    executor::{AbortHandle, spawn},
a319bae745d8atagen2026-09-26 09:59 UTC15
};
a319bae745d8atagen2026-09-26 09:59 UTC16
use ruma::{OwnedDeviceId, OwnedRoomId, OwnedTransactionId, OwnedUserId};
a319bae745d8atagen2026-09-26 09:59 UTC17
#[cfg(feature = "serde")]
a319bae745d8atagen2026-09-26 09:59 UTC18
use serde::{Deserialize, Serialize};
a319bae745d8atagen2026-09-26 09:59 UTC19
use tokio::sync::broadcast;
a319bae745d8atagen2026-09-26 09:59 UTC20
a319bae745d8atagen2026-09-26 09:59 UTC21
use crate::{
a319bae745d8atagen2026-09-26 09:59 UTC22
    ClientHandle, Command, ConnectionState, EncryptionState, EngineFailure, ErrorKind, Event,
a319bae745d8atagen2026-09-26 09:59 UTC23
    HistoryState, RoomSummary, TimelineCommand, TimelineDiff, TimelineId, TimelineItem,
a319bae745d8atagen2026-09-26 09:59 UTC24
    TypingState, UploadOutcome, VerificationState, VerificationTarget,
a319bae745d8atagen2026-09-26 09:59 UTC25
    engine::executor::EngineBoxFuture,
a319bae745d8atagen2026-09-26 09:59 UTC26
};
a319bae745d8atagen2026-09-26 09:59 UTC27
a319bae745d8atagen2026-09-26 09:59 UTC28
#[cfg(not(target_family = "wasm"))]
a319bae745d8atagen2026-09-26 09:59 UTC29
type EngineBoxStream<T> = futures_util::stream::BoxStream<'static, T>;
a319bae745d8atagen2026-09-26 09:59 UTC30
#[cfg(target_family = "wasm")]
a319bae745d8atagen2026-09-26 09:59 UTC31
type EngineBoxStream<T> = futures_util::stream::LocalBoxStream<'static, T>;
a319bae745d8atagen2026-09-26 09:59 UTC32
a319bae745d8atagen2026-09-26 09:59 UTC33
const DEFAULT_CAPACITY: usize = 256;
a319bae745d8atagen2026-09-26 09:59 UTC34
const RESYNC_BACKOFF: Duration = Duration::from_millis(100);
a319bae745d8atagen2026-09-26 09:59 UTC35
const RESYNC_BACKOFF_CAP: Duration = Duration::from_secs(10);
a319bae745d8atagen2026-09-26 09:59 UTC36
a319bae745d8atagen2026-09-26 09:59 UTC37
/// everything the engine has said so far, folded together
a319bae745d8atagen2026-09-26 09:59 UTC38
#[derive(Clone, Debug, PartialEq)]
a319bae745d8atagen2026-09-26 09:59 UTC39
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
a319bae745d8atagen2026-09-26 09:59 UTC40
#[non_exhaustive]
a319bae745d8atagen2026-09-26 09:59 UTC41
pub struct State {
a319bae745d8atagen2026-09-26 09:59 UTC42
    pub user_id: Option<OwnedUserId>,
a319bae745d8atagen2026-09-26 09:59 UTC43
    pub session: SessionStatus,
a319bae745d8atagen2026-09-26 09:59 UTC44
    pub connection: ConnectionState,
a319bae745d8atagen2026-09-26 09:59 UTC45
    pub encryption: Option<EncryptionState>,
a319bae745d8atagen2026-09-26 09:59 UTC46
    pub verification: Option<VerificationState>,
a319bae745d8atagen2026-09-26 09:59 UTC47
    pub verification_target: Option<VerificationTarget>,
a319bae745d8atagen2026-09-26 09:59 UTC48
    pub rooms: Vector<RoomSummary>,
a319bae745d8atagen2026-09-26 09:59 UTC49
    pub timelines: imbl::HashMap<TimelineId, TimelineState>,
a319bae745d8atagen2026-09-26 09:59 UTC50
    pub typing: imbl::HashMap<OwnedRoomId, Vec<OwnedUserId>>,
a319bae745d8atagen2026-09-26 09:59 UTC51
    pub uploads: imbl::HashMap<OwnedTransactionId, UploadState>,
a319bae745d8atagen2026-09-26 09:59 UTC52
}
a319bae745d8atagen2026-09-26 09:59 UTC53
a319bae745d8atagen2026-09-26 09:59 UTC54
#[derive(Clone, Debug, PartialEq)]
a319bae745d8atagen2026-09-26 09:59 UTC55
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
a319bae745d8atagen2026-09-26 09:59 UTC56
#[non_exhaustive]
a319bae745d8atagen2026-09-26 09:59 UTC57
pub struct TimelineState {
a319bae745d8atagen2026-09-26 09:59 UTC58
    pub items: Vector<TimelineItem>,
a319bae745d8atagen2026-09-26 09:59 UTC59
    pub history: HistoryState,
a319bae745d8atagen2026-09-26 09:59 UTC60
    /// a batch didn't fit, so `items` is the last good copy until the next reset
a319bae745d8atagen2026-09-26 09:59 UTC61
    pub diverged: bool,
a319bae745d8atagen2026-09-26 09:59 UTC62
}
a319bae745d8atagen2026-09-26 09:59 UTC63
a319bae745d8atagen2026-09-26 09:59 UTC64
#[derive(Clone, Debug, Eq, PartialEq)]
a319bae745d8atagen2026-09-26 09:59 UTC65
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
a319bae745d8atagen2026-09-26 09:59 UTC66
#[non_exhaustive]
a319bae745d8atagen2026-09-26 09:59 UTC67
pub struct UploadState {
a319bae745d8atagen2026-09-26 09:59 UTC68
    pub room_id: OwnedRoomId,
a319bae745d8atagen2026-09-26 09:59 UTC69
    pub uploaded_bytes: u64,
a319bae745d8atagen2026-09-26 09:59 UTC70
    pub total_bytes: u64,
a319bae745d8atagen2026-09-26 09:59 UTC71
    pub complete: bool,
a319bae745d8atagen2026-09-26 09:59 UTC72
    pub failure: Option<UploadFailure>,
a319bae745d8atagen2026-09-26 09:59 UTC73
}
a319bae745d8atagen2026-09-26 09:59 UTC74
a319bae745d8atagen2026-09-26 09:59 UTC75
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
a319bae745d8atagen2026-09-26 09:59 UTC76
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
a319bae745d8atagen2026-09-26 09:59 UTC77
#[non_exhaustive]
a319bae745d8atagen2026-09-26 09:59 UTC78
pub struct UploadFailure {
a319bae745d8atagen2026-09-26 09:59 UTC79
    pub kind: ErrorKind,
a319bae745d8atagen2026-09-26 09:59 UTC80
    pub will_retry: bool,
a319bae745d8atagen2026-09-26 09:59 UTC81
}
a319bae745d8atagen2026-09-26 09:59 UTC82
a319bae745d8atagen2026-09-26 09:59 UTC83
/// the `Termination` it'll end in, as far as the events tell
a319bae745d8atagen2026-09-26 09:59 UTC84
#[derive(Clone, Debug, Eq, PartialEq)]
a319bae745d8atagen2026-09-26 09:59 UTC85
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
a319bae745d8atagen2026-09-26 09:59 UTC86
#[non_exhaustive]
a319bae745d8atagen2026-09-26 09:59 UTC87
pub enum SessionStatus {
a319bae745d8atagen2026-09-26 09:59 UTC88
    Active,
a319bae745d8atagen2026-09-26 09:59 UTC89
    SoftLoggedOut {
a319bae745d8atagen2026-09-26 09:59 UTC90
        device_id: OwnedDeviceId,
a319bae745d8atagen2026-09-26 09:59 UTC91
    },
a319bae745d8atagen2026-09-26 09:59 UTC92
    AccessRevoked,
a319bae745d8atagen2026-09-26 09:59 UTC93
    LoggedOut,
a319bae745d8atagen2026-09-26 09:59 UTC94
    /// stopped with the session still good
a319bae745d8atagen2026-09-26 09:59 UTC95
    Stopped,
a319bae745d8atagen2026-09-26 09:59 UTC96
}
a319bae745d8atagen2026-09-26 09:59 UTC97
a319bae745d8atagen2026-09-26 09:59 UTC98
#[derive(Clone, Debug)]
a319bae745d8atagen2026-09-26 09:59 UTC99
#[non_exhaustive]
a319bae745d8atagen2026-09-26 09:59 UTC100
pub struct Update {
a319bae745d8atagen2026-09-26 09:59 UTC101
    /// one more than the update before it, and a `State` carries the one it's current as of
a319bae745d8atagen2026-09-26 09:59 UTC102
    pub sequence: u64,
a319bae745d8atagen2026-09-26 09:59 UTC103
    pub change: StateChange,
a319bae745d8atagen2026-09-26 09:59 UTC104
}
a319bae745d8atagen2026-09-26 09:59 UTC105
a319bae745d8atagen2026-09-26 09:59 UTC106
#[derive(Clone, Debug)]
a319bae745d8atagen2026-09-26 09:59 UTC107
#[non_exhaustive]
a319bae745d8atagen2026-09-26 09:59 UTC108
pub enum StateChange {
a319bae745d8atagen2026-09-26 09:59 UTC109
    /// start over from this. a subscription opens with one and gets another after lagging
a319bae745d8atagen2026-09-26 09:59 UTC110
    State(Arc<State>),
a319bae745d8atagen2026-09-26 09:59 UTC111
    Authenticated(OwnedUserId),
a319bae745d8atagen2026-09-26 09:59 UTC112
    Session(SessionStatus),
a319bae745d8atagen2026-09-26 09:59 UTC113
    /// the record on disk may now hold a spent refresh token
a319bae745d8atagen2026-09-26 09:59 UTC114
    #[cfg(any(feature = "sqlite", feature = "indexeddb"))]
a319bae745d8atagen2026-09-26 09:59 UTC115
    SessionSaveFailed(Arc<crate::AccountError>),
a319bae745d8atagen2026-09-26 09:59 UTC116
    Connection(ConnectionState),
a319bae745d8atagen2026-09-26 09:59 UTC117
    Encryption(EncryptionState),
a319bae745d8atagen2026-09-26 09:59 UTC118
    Verification(VerificationState),
a319bae745d8atagen2026-09-26 09:59 UTC119
    VerificationTarget(VerificationTarget),
a319bae745d8atagen2026-09-26 09:59 UTC120
    /// room modes arrive with the `Rooms` that follows
a319bae745d8atagen2026-09-26 09:59 UTC121
    NotificationSettings,
a319bae745d8atagen2026-09-26 09:59 UTC122
    Rooms(Vector<RoomSummary>),
a319bae745d8atagen2026-09-26 09:59 UTC123
    Timeline {
a319bae745d8atagen2026-09-26 09:59 UTC124
        timeline_id: TimelineId,
a319bae745d8atagen2026-09-26 09:59 UTC125
        change: TimelineChange,
a319bae745d8atagen2026-09-26 09:59 UTC126
    },
a319bae745d8atagen2026-09-26 09:59 UTC127
    History {
a319bae745d8atagen2026-09-26 09:59 UTC128
        timeline_id: TimelineId,
a319bae745d8atagen2026-09-26 09:59 UTC129
        state: HistoryState,
a319bae745d8atagen2026-09-26 09:59 UTC130
    },
a319bae745d8atagen2026-09-26 09:59 UTC131
    Typing(TypingState),
a319bae745d8atagen2026-09-26 09:59 UTC132
    Upload {
a319bae745d8atagen2026-09-26 09:59 UTC133
        transaction_id: OwnedTransactionId,
a319bae745d8atagen2026-09-26 09:59 UTC134
        upload: UploadState,
a319bae745d8atagen2026-09-26 09:59 UTC135
    },
a319bae745d8atagen2026-09-26 09:59 UTC136
    UploadFinished {
a319bae745d8atagen2026-09-26 09:59 UTC137
        room_id: OwnedRoomId,
a319bae745d8atagen2026-09-26 09:59 UTC138
        transaction_id: OwnedTransactionId,
a319bae745d8atagen2026-09-26 09:59 UTC139
        outcome: UploadOutcome,
a319bae745d8atagen2026-09-26 09:59 UTC140
    },
a319bae745d8atagen2026-09-26 09:59 UTC141
    Failed(EngineFailure),
a319bae745d8atagen2026-09-26 09:59 UTC142
}
a319bae745d8atagen2026-09-26 09:59 UTC143
a319bae745d8atagen2026-09-26 09:59 UTC144
#[derive(Clone, Debug)]
a319bae745d8atagen2026-09-26 09:59 UTC145
#[non_exhaustive]
a319bae745d8atagen2026-09-26 09:59 UTC146
pub enum TimelineChange {
a319bae745d8atagen2026-09-26 09:59 UTC147
    Reset {
a319bae745d8atagen2026-09-26 09:59 UTC148
        items: Vector<TimelineItem>,
a319bae745d8atagen2026-09-26 09:59 UTC149
    },
a319bae745d8atagen2026-09-26 09:59 UTC150
    /// `items` is what applying `diffs` left
a319bae745d8atagen2026-09-26 09:59 UTC151
    Changed {
a319bae745d8atagen2026-09-26 09:59 UTC152
        diffs: Arc<[TimelineDiff]>,
a319bae745d8atagen2026-09-26 09:59 UTC153
        items: Vector<TimelineItem>,
a319bae745d8atagen2026-09-26 09:59 UTC154
    },
a319bae745d8atagen2026-09-26 09:59 UTC155
    /// a batch didn't fit. a resync is on its way and nothing changes until its reset
a319bae745d8atagen2026-09-26 09:59 UTC156
    Diverged,
a319bae745d8atagen2026-09-26 09:59 UTC157
    Closed,
a319bae745d8atagen2026-09-26 09:59 UTC158
}
a319bae745d8atagen2026-09-26 09:59 UTC159
a319bae745d8atagen2026-09-26 09:59 UTC160
/// drains the event stream into a `State` that any number of `MirrorHandle`s watch
a319bae745d8atagen2026-09-26 09:59 UTC161
pub struct Mirror {
a319bae745d8atagen2026-09-26 09:59 UTC162
    handle: ClientHandle,
a319bae745d8atagen2026-09-26 09:59 UTC163
    events: EngineBoxStream<Event>,
a319bae745d8atagen2026-09-26 09:59 UTC164
    shared: Arc<Shared>,
a319bae745d8atagen2026-09-26 09:59 UTC165
    resyncs: HashMap<TimelineId, AbortHandle>,
a319bae745d8atagen2026-09-26 09:59 UTC166
    rooms_fetch: Option<AbortHandle>,
a319bae745d8atagen2026-09-26 09:59 UTC167
    #[cfg(any(feature = "sqlite", feature = "indexeddb"))]
a319bae745d8atagen2026-09-26 09:59 UTC168
    session_store: Option<Arc<dyn crate::SessionStore>>,
a319bae745d8atagen2026-09-26 09:59 UTC169
}
a319bae745d8atagen2026-09-26 09:59 UTC170
a319bae745d8atagen2026-09-26 09:59 UTC171
impl fmt::Debug for Mirror {
a319bae745d8atagen2026-09-26 09:59 UTC172
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
a319bae745d8atagen2026-09-26 09:59 UTC173
        formatter.debug_struct("Mirror").finish_non_exhaustive()
a319bae745d8atagen2026-09-26 09:59 UTC174
    }
a319bae745d8atagen2026-09-26 09:59 UTC175
}
a319bae745d8atagen2026-09-26 09:59 UTC176
a319bae745d8atagen2026-09-26 09:59 UTC177
impl Mirror {
a319bae745d8atagen2026-09-26 09:59 UTC178
    /// takes any stream of the engine's events, so a host can still look at them on the way past
a319bae745d8atagen2026-09-26 09:59 UTC179
    pub fn new(
a319bae745d8atagen2026-09-26 09:59 UTC180
        handle: ClientHandle,
a319bae745d8atagen2026-09-26 09:59 UTC181
        events: impl Stream<Item = Event> + SendOutsideWasm + 'static,
a319bae745d8atagen2026-09-26 09:59 UTC182
    ) -> (Self, MirrorHandle) {
a319bae745d8atagen2026-09-26 09:59 UTC183
        Self::with_capacity(handle, events, DEFAULT_CAPACITY)
a319bae745d8atagen2026-09-26 09:59 UTC184
    }
a319bae745d8atagen2026-09-26 09:59 UTC185
a319bae745d8atagen2026-09-26 09:59 UTC186
    /// `capacity` updates can queue for a subscriber before it lags and starts over
a319bae745d8atagen2026-09-26 09:59 UTC187
    pub fn with_capacity(
a319bae745d8atagen2026-09-26 09:59 UTC188
        handle: ClientHandle,
a319bae745d8atagen2026-09-26 09:59 UTC189
        events: impl Stream<Item = Event> + SendOutsideWasm + 'static,
a319bae745d8atagen2026-09-26 09:59 UTC190
        capacity: usize,
a319bae745d8atagen2026-09-26 09:59 UTC191
    ) -> (Self, MirrorHandle) {
a319bae745d8atagen2026-09-26 09:59 UTC192
        let state = State {
a319bae745d8atagen2026-09-26 09:59 UTC193
            user_id: None,
a319bae745d8atagen2026-09-26 09:59 UTC194
            session: SessionStatus::Active,
a319bae745d8atagen2026-09-26 09:59 UTC195
            connection: *handle.connection_state().borrow(),
a319bae745d8atagen2026-09-26 09:59 UTC196
            encryption: handle.encryption_state(),
a319bae745d8atagen2026-09-26 09:59 UTC197
            verification: None,
a319bae745d8atagen2026-09-26 09:59 UTC198
            verification_target: None,
a319bae745d8atagen2026-09-26 09:59 UTC199
            rooms: Vector::new(),
a319bae745d8atagen2026-09-26 09:59 UTC200
            timelines: imbl::HashMap::new(),
a319bae745d8atagen2026-09-26 09:59 UTC201
            typing: imbl::HashMap::new(),
a319bae745d8atagen2026-09-26 09:59 UTC202
            uploads: imbl::HashMap::new(),
a319bae745d8atagen2026-09-26 09:59 UTC203
        };
a319bae745d8atagen2026-09-26 09:59 UTC204
        let shared = Arc::new(Shared {
a319bae745d8atagen2026-09-26 09:59 UTC205
            published: Mutex::new(Published {
a319bae745d8atagen2026-09-26 09:59 UTC206
                state: Arc::new(state),
a319bae745d8atagen2026-09-26 09:59 UTC207
                sequence: 0,
a319bae745d8atagen2026-09-26 09:59 UTC208
                updates: Some(broadcast::channel(capacity).0),
a319bae745d8atagen2026-09-26 09:59 UTC209
                rooms_fetch: RoomsFetch::Idle,
a319bae745d8atagen2026-09-26 09:59 UTC210
            }),
a319bae745d8atagen2026-09-26 09:59 UTC211
        });
a319bae745d8atagen2026-09-26 09:59 UTC212
        #[cfg(not(target_family = "wasm"))]
a319bae745d8atagen2026-09-26 09:59 UTC213
        let events = events.boxed();
a319bae745d8atagen2026-09-26 09:59 UTC214
        #[cfg(target_family = "wasm")]
a319bae745d8atagen2026-09-26 09:59 UTC215
        let events = events.boxed_local();
a319bae745d8atagen2026-09-26 09:59 UTC216
        let mirror = Self {
a319bae745d8atagen2026-09-26 09:59 UTC217
            handle,
a319bae745d8atagen2026-09-26 09:59 UTC218
            events,
a319bae745d8atagen2026-09-26 09:59 UTC219
            shared: shared.clone(),
a319bae745d8atagen2026-09-26 09:59 UTC220
            resyncs: HashMap::new(),
a319bae745d8atagen2026-09-26 09:59 UTC221
            rooms_fetch: None,
a319bae745d8atagen2026-09-26 09:59 UTC222
            #[cfg(any(feature = "sqlite", feature = "indexeddb"))]
a319bae745d8atagen2026-09-26 09:59 UTC223
            session_store: None,
a319bae745d8atagen2026-09-26 09:59 UTC224
        };
a319bae745d8atagen2026-09-26 09:59 UTC225
        (mirror, MirrorHandle { shared })
a319bae745d8atagen2026-09-26 09:59 UTC226
    }
a319bae745d8atagen2026-09-26 09:59 UTC227
a319bae745d8atagen2026-09-26 09:59 UTC228
    /// saves the record on every `SessionTokensChanged` before taking another event, and
a319bae745d8atagen2026-09-26 09:59 UTC229
    /// calls nothing but `save`. the store can't go while the client holds it open
a319bae745d8atagen2026-09-26 09:59 UTC230
    #[cfg(any(feature = "sqlite", feature = "indexeddb"))]
a319bae745d8atagen2026-09-26 09:59 UTC231
    #[must_use]
a319bae745d8atagen2026-09-26 09:59 UTC232
    pub fn with_session_store(mut self, store: Arc<dyn crate::SessionStore>) -> Self {
a319bae745d8atagen2026-09-26 09:59 UTC233
        self.session_store = Some(store);
a319bae745d8atagen2026-09-26 09:59 UTC234
        self
a319bae745d8atagen2026-09-26 09:59 UTC235
    }
a319bae745d8atagen2026-09-26 09:59 UTC236
a319bae745d8atagen2026-09-26 09:59 UTC237
    /// resolves once the event stream ends, which is just after `Stopped`
a319bae745d8atagen2026-09-26 09:59 UTC238
    pub fn run(mut self) -> EngineBoxFuture<'static, ()> {
a319bae745d8atagen2026-09-26 09:59 UTC239
        Box::pin(async move {
a319bae745d8atagen2026-09-26 09:59 UTC240
            self.refetch_rooms();
a319bae745d8atagen2026-09-26 09:59 UTC241
            while let Some(event) = self.events.next().await {
a319bae745d8atagen2026-09-26 09:59 UTC242
                self.apply(event).await;
a319bae745d8atagen2026-09-26 09:59 UTC243
            }
a319bae745d8atagen2026-09-26 09:59 UTC244
        })
a319bae745d8atagen2026-09-26 09:59 UTC245
    }
a319bae745d8atagen2026-09-26 09:59 UTC246
a319bae745d8atagen2026-09-26 09:59 UTC247
    async fn apply(&mut self, event: Event) {
a319bae745d8atagen2026-09-26 09:59 UTC248
        match event {
a319bae745d8atagen2026-09-26 09:59 UTC249
            Event::ConnectionChanged(connection) => self.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC250
                state.connection = connection;
a319bae745d8atagen2026-09-26 09:59 UTC251
                Some(StateChange::Connection(connection))
a319bae745d8atagen2026-09-26 09:59 UTC252
            }),
a319bae745d8atagen2026-09-26 09:59 UTC253
            Event::Authenticated { user_id } => self.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC254
                state.user_id = Some(user_id.clone());
a319bae745d8atagen2026-09-26 09:59 UTC255
                Some(StateChange::Authenticated(user_id))
a319bae745d8atagen2026-09-26 09:59 UTC256
            }),
a319bae745d8atagen2026-09-26 09:59 UTC257
            #[cfg(any(feature = "sqlite", feature = "indexeddb"))]
a319bae745d8atagen2026-09-26 09:59 UTC258
            Event::SessionTokensChanged => self.save_session().await,
a319bae745d8atagen2026-09-26 09:59 UTC259
            #[cfg(not(any(feature = "sqlite", feature = "indexeddb")))]
a319bae745d8atagen2026-09-26 09:59 UTC260
            Event::SessionTokensChanged => {}
a319bae745d8atagen2026-09-26 09:59 UTC261
            Event::SessionAccessRejected { soft_logout } => self.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC262
                state.session = match soft_logout {
a319bae745d8atagen2026-09-26 09:59 UTC263
                    Some(device_id) => SessionStatus::SoftLoggedOut { device_id },
a319bae745d8atagen2026-09-26 09:59 UTC264
                    None => SessionStatus::AccessRevoked,
a319bae745d8atagen2026-09-26 09:59 UTC265
                };
a319bae745d8atagen2026-09-26 09:59 UTC266
                Some(StateChange::Session(state.session.clone()))
a319bae745d8atagen2026-09-26 09:59 UTC267
            }),
a319bae745d8atagen2026-09-26 09:59 UTC268
            Event::LoggedOut => self.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC269
                state.session = SessionStatus::LoggedOut;
a319bae745d8atagen2026-09-26 09:59 UTC270
                Some(StateChange::Session(SessionStatus::LoggedOut))
a319bae745d8atagen2026-09-26 09:59 UTC271
            }),
a319bae745d8atagen2026-09-26 09:59 UTC272
            Event::Stopped => {
a319bae745d8atagen2026-09-26 09:59 UTC273
                self.cancel_resyncs();
a319bae745d8atagen2026-09-26 09:59 UTC274
                self.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC275
                    if state.session != SessionStatus::Active {
a319bae745d8atagen2026-09-26 09:59 UTC276
                        return None;
a319bae745d8atagen2026-09-26 09:59 UTC277
                    }
a319bae745d8atagen2026-09-26 09:59 UTC278
                    state.session = SessionStatus::Stopped;
a319bae745d8atagen2026-09-26 09:59 UTC279
                    Some(StateChange::Session(SessionStatus::Stopped))
a319bae745d8atagen2026-09-26 09:59 UTC280
                });
a319bae745d8atagen2026-09-26 09:59 UTC281
            }
a319bae745d8atagen2026-09-26 09:59 UTC282
            Event::Failed(failure) => self.publish(|_| Some(StateChange::Failed(failure))),
a319bae745d8atagen2026-09-26 09:59 UTC283
            Event::NotificationSettingsChanged => {
a319bae745d8atagen2026-09-26 09:59 UTC284
                self.publish(|_| Some(StateChange::NotificationSettings));
a319bae745d8atagen2026-09-26 09:59 UTC285
            }
a319bae745d8atagen2026-09-26 09:59 UTC286
            Event::RoomsChanged => self.refetch_rooms(),
a319bae745d8atagen2026-09-26 09:59 UTC287
            Event::EncryptionChanged(encryption) => self.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC288
                state.encryption = Some(encryption.clone());
a319bae745d8atagen2026-09-26 09:59 UTC289
                Some(StateChange::Encryption(encryption))
a319bae745d8atagen2026-09-26 09:59 UTC290
            }),
a319bae745d8atagen2026-09-26 09:59 UTC291
            Event::VerificationChanged(verification) => self.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC292
                state.verification = Some(verification.clone());
a319bae745d8atagen2026-09-26 09:59 UTC293
                Some(StateChange::Verification(verification))
a319bae745d8atagen2026-09-26 09:59 UTC294
            }),
a319bae745d8atagen2026-09-26 09:59 UTC295
            Event::VerificationTargetChanged(target) => self.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC296
                state.verification_target = Some(target.clone());
a319bae745d8atagen2026-09-26 09:59 UTC297
                Some(StateChange::VerificationTarget(target))
a319bae745d8atagen2026-09-26 09:59 UTC298
            }),
a319bae745d8atagen2026-09-26 09:59 UTC299
            Event::TimelineReset { timeline_id, items } => {
a319bae745d8atagen2026-09-26 09:59 UTC300
                self.cancel_resync(&timeline_id);
a319bae745d8atagen2026-09-26 09:59 UTC301
                self.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC302
                    let history = state
a319bae745d8atagen2026-09-26 09:59 UTC303
                        .timelines
a319bae745d8atagen2026-09-26 09:59 UTC304
                        .get(&timeline_id)
a319bae745d8atagen2026-09-26 09:59 UTC305
                        .map_or(HistoryState::Idle, |timeline| timeline.history.clone());
a319bae745d8atagen2026-09-26 09:59 UTC306
                    let timeline = TimelineState {
a319bae745d8atagen2026-09-26 09:59 UTC307
                        items: items.clone(),
a319bae745d8atagen2026-09-26 09:59 UTC308
                        history,
a319bae745d8atagen2026-09-26 09:59 UTC309
                        diverged: false,
a319bae745d8atagen2026-09-26 09:59 UTC310
                    };
a319bae745d8atagen2026-09-26 09:59 UTC311
                    state.timelines.insert(timeline_id.clone(), timeline);
a319bae745d8atagen2026-09-26 09:59 UTC312
                    Some(StateChange::Timeline {
a319bae745d8atagen2026-09-26 09:59 UTC313
                        timeline_id,
a319bae745d8atagen2026-09-26 09:59 UTC314
                        change: TimelineChange::Reset { items },
a319bae745d8atagen2026-09-26 09:59 UTC315
                    })
a319bae745d8atagen2026-09-26 09:59 UTC316
                });
a319bae745d8atagen2026-09-26 09:59 UTC317
            }
a319bae745d8atagen2026-09-26 09:59 UTC318
            Event::TimelineChanged { timeline_id, diffs } => self.apply_diffs(timeline_id, diffs),
a319bae745d8atagen2026-09-26 09:59 UTC319
            Event::HistoryChanged {
a319bae745d8atagen2026-09-26 09:59 UTC320
                timeline_id,
a319bae745d8atagen2026-09-26 09:59 UTC321
                state: history,
a319bae745d8atagen2026-09-26 09:59 UTC322
            } => self.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC323
                state.timelines.get_mut(&timeline_id)?.history = history.clone();
a319bae745d8atagen2026-09-26 09:59 UTC324
                Some(StateChange::History {
a319bae745d8atagen2026-09-26 09:59 UTC325
                    timeline_id,
a319bae745d8atagen2026-09-26 09:59 UTC326
                    state: history,
a319bae745d8atagen2026-09-26 09:59 UTC327
                })
a319bae745d8atagen2026-09-26 09:59 UTC328
            }),
a319bae745d8atagen2026-09-26 09:59 UTC329
            Event::TimelineClosed { timeline_id } => {
a319bae745d8atagen2026-09-26 09:59 UTC330
                self.cancel_resync(&timeline_id);
a319bae745d8atagen2026-09-26 09:59 UTC331
                self.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC332
                    state.timelines.remove(&timeline_id)?;
a319bae745d8atagen2026-09-26 09:59 UTC333
                    Some(StateChange::Timeline {
a319bae745d8atagen2026-09-26 09:59 UTC334
                        timeline_id,
a319bae745d8atagen2026-09-26 09:59 UTC335
                        change: TimelineChange::Closed,
a319bae745d8atagen2026-09-26 09:59 UTC336
                    })
a319bae745d8atagen2026-09-26 09:59 UTC337
                });
a319bae745d8atagen2026-09-26 09:59 UTC338
            }
a319bae745d8atagen2026-09-26 09:59 UTC339
            Event::TypingChanged(typing) => self.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC340
                if typing.users.is_empty() {
a319bae745d8atagen2026-09-26 09:59 UTC341
                    state.typing.remove(&typing.room_id);
a319bae745d8atagen2026-09-26 09:59 UTC342
                } else {
a319bae745d8atagen2026-09-26 09:59 UTC343
                    state
a319bae745d8atagen2026-09-26 09:59 UTC344
                        .typing
a319bae745d8atagen2026-09-26 09:59 UTC345
                        .insert(typing.room_id.clone(), typing.users.clone());
a319bae745d8atagen2026-09-26 09:59 UTC346
                }
a319bae745d8atagen2026-09-26 09:59 UTC347
                Some(StateChange::Typing(typing))
a319bae745d8atagen2026-09-26 09:59 UTC348
            }),
a319bae745d8atagen2026-09-26 09:59 UTC349
            Event::MediaUploadProgress {
a319bae745d8atagen2026-09-26 09:59 UTC350
                room_id,
a319bae745d8atagen2026-09-26 09:59 UTC351
                transaction_id,
a319bae745d8atagen2026-09-26 09:59 UTC352
                uploaded_bytes,
a319bae745d8atagen2026-09-26 09:59 UTC353
                total_bytes,
a319bae745d8atagen2026-09-26 09:59 UTC354
                complete,
a319bae745d8atagen2026-09-26 09:59 UTC355
            } => self.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC356
                let upload = UploadState {
a319bae745d8atagen2026-09-26 09:59 UTC357
                    room_id,
a319bae745d8atagen2026-09-26 09:59 UTC358
                    uploaded_bytes,
a319bae745d8atagen2026-09-26 09:59 UTC359
                    total_bytes,
a319bae745d8atagen2026-09-26 09:59 UTC360
                    complete,
a319bae745d8atagen2026-09-26 09:59 UTC361
                    failure: None,
a319bae745d8atagen2026-09-26 09:59 UTC362
                };
a319bae745d8atagen2026-09-26 09:59 UTC363
                state.uploads.insert(transaction_id.clone(), upload.clone());
a319bae745d8atagen2026-09-26 09:59 UTC364
                Some(StateChange::Upload {
a319bae745d8atagen2026-09-26 09:59 UTC365
                    transaction_id,
a319bae745d8atagen2026-09-26 09:59 UTC366
                    upload,
a319bae745d8atagen2026-09-26 09:59 UTC367
                })
a319bae745d8atagen2026-09-26 09:59 UTC368
            }),
a319bae745d8atagen2026-09-26 09:59 UTC369
            Event::MediaUploadFailed {
a319bae745d8atagen2026-09-26 09:59 UTC370
                room_id,
a319bae745d8atagen2026-09-26 09:59 UTC371
                transaction_id,
a319bae745d8atagen2026-09-26 09:59 UTC372
                kind,
a319bae745d8atagen2026-09-26 09:59 UTC373
                will_retry,
a319bae745d8atagen2026-09-26 09:59 UTC374
            } => self.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC375
                let upload = state
a319bae745d8atagen2026-09-26 09:59 UTC376
                    .uploads
a319bae745d8atagen2026-09-26 09:59 UTC377
                    .entry(transaction_id.clone())
a319bae745d8atagen2026-09-26 09:59 UTC378
                    .or_insert_with(|| UploadState {
a319bae745d8atagen2026-09-26 09:59 UTC379
                        room_id,
a319bae745d8atagen2026-09-26 09:59 UTC380
                        uploaded_bytes: 0,
a319bae745d8atagen2026-09-26 09:59 UTC381
                        total_bytes: 0,
a319bae745d8atagen2026-09-26 09:59 UTC382
                        complete: false,
a319bae745d8atagen2026-09-26 09:59 UTC383
                        failure: None,
a319bae745d8atagen2026-09-26 09:59 UTC384
                    });
a319bae745d8atagen2026-09-26 09:59 UTC385
                upload.failure = Some(UploadFailure { kind, will_retry });
a319bae745d8atagen2026-09-26 09:59 UTC386
                Some(StateChange::Upload {
a319bae745d8atagen2026-09-26 09:59 UTC387
                    transaction_id,
a319bae745d8atagen2026-09-26 09:59 UTC388
                    upload: upload.clone(),
a319bae745d8atagen2026-09-26 09:59 UTC389
                })
a319bae745d8atagen2026-09-26 09:59 UTC390
            }),
a319bae745d8atagen2026-09-26 09:59 UTC391
            Event::MediaUploadFinished {
a319bae745d8atagen2026-09-26 09:59 UTC392
                room_id,
a319bae745d8atagen2026-09-26 09:59 UTC393
                transaction_id,
a319bae745d8atagen2026-09-26 09:59 UTC394
                outcome,
a319bae745d8atagen2026-09-26 09:59 UTC395
            } => self.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC396
                state.uploads.remove(&transaction_id);
a319bae745d8atagen2026-09-26 09:59 UTC397
                Some(StateChange::UploadFinished {
a319bae745d8atagen2026-09-26 09:59 UTC398
                    room_id,
a319bae745d8atagen2026-09-26 09:59 UTC399
                    transaction_id,
a319bae745d8atagen2026-09-26 09:59 UTC400
                    outcome,
a319bae745d8atagen2026-09-26 09:59 UTC401
                })
a319bae745d8atagen2026-09-26 09:59 UTC402
            }),
a319bae745d8atagen2026-09-26 09:59 UTC403
        }
a319bae745d8atagen2026-09-26 09:59 UTC404
    }
a319bae745d8atagen2026-09-26 09:59 UTC405
a319bae745d8atagen2026-09-26 09:59 UTC406
    fn apply_diffs(&mut self, timeline_id: TimelineId, diffs: Vec<TimelineDiff>) {
a319bae745d8atagen2026-09-26 09:59 UTC407
        let mut diverged = false;
a319bae745d8atagen2026-09-26 09:59 UTC408
        self.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC409
            let timeline = state.timelines.get_mut(&timeline_id)?;
a319bae745d8atagen2026-09-26 09:59 UTC410
            if timeline.diverged {
a319bae745d8atagen2026-09-26 09:59 UTC411
                return None;
a319bae745d8atagen2026-09-26 09:59 UTC412
            }
a319bae745d8atagen2026-09-26 09:59 UTC413
            let diffs: Arc<[TimelineDiff]> = diffs.into();
a319bae745d8atagen2026-09-26 09:59 UTC414
            let change = match TimelineDiff::try_apply_all(diffs.to_vec(), &mut timeline.items) {
a319bae745d8atagen2026-09-26 09:59 UTC415
                Ok(()) => TimelineChange::Changed {
a319bae745d8atagen2026-09-26 09:59 UTC416
                    diffs,
a319bae745d8atagen2026-09-26 09:59 UTC417
                    items: timeline.items.clone(),
a319bae745d8atagen2026-09-26 09:59 UTC418
                },
a319bae745d8atagen2026-09-26 09:59 UTC419
                Err(error) => {
a319bae745d8atagen2026-09-26 09:59 UTC420
                    tracing::debug!(?timeline_id, length = error.length, "timeline diverged");
a319bae745d8atagen2026-09-26 09:59 UTC421
                    timeline.diverged = true;
a319bae745d8atagen2026-09-26 09:59 UTC422
                    diverged = true;
a319bae745d8atagen2026-09-26 09:59 UTC423
                    TimelineChange::Diverged
a319bae745d8atagen2026-09-26 09:59 UTC424
                }
a319bae745d8atagen2026-09-26 09:59 UTC425
            };
a319bae745d8atagen2026-09-26 09:59 UTC426
            Some(StateChange::Timeline {
a319bae745d8atagen2026-09-26 09:59 UTC427
                timeline_id: timeline_id.clone(),
a319bae745d8atagen2026-09-26 09:59 UTC428
                change,
a319bae745d8atagen2026-09-26 09:59 UTC429
            })
a319bae745d8atagen2026-09-26 09:59 UTC430
        });
a319bae745d8atagen2026-09-26 09:59 UTC431
        if diverged {
a319bae745d8atagen2026-09-26 09:59 UTC432
            let task = spawn(resync(self.handle.clone(), timeline_id.clone()));
a319bae745d8atagen2026-09-26 09:59 UTC433
            self.resyncs.insert(timeline_id, task.abort_handle());
a319bae745d8atagen2026-09-26 09:59 UTC434
        }
a319bae745d8atagen2026-09-26 09:59 UTC435
    }
a319bae745d8atagen2026-09-26 09:59 UTC436
a319bae745d8atagen2026-09-26 09:59 UTC437
    #[cfg(any(feature = "sqlite", feature = "indexeddb"))]
a319bae745d8atagen2026-09-26 09:59 UTC438
    async fn save_session(&mut self) {
a319bae745d8atagen2026-09-26 09:59 UTC439
        let Some(store) = &self.session_store else {
a319bae745d8atagen2026-09-26 09:59 UTC440
            return;
a319bae745d8atagen2026-09-26 09:59 UTC441
        };
a319bae745d8atagen2026-09-26 09:59 UTC442
        let saved = match self.handle.session_record() {
a319bae745d8atagen2026-09-26 09:59 UTC443
            Ok(record) => store.save(&record).await,
a319bae745d8atagen2026-09-26 09:59 UTC444
            Err(error) => Err(error),
a319bae745d8atagen2026-09-26 09:59 UTC445
        };
a319bae745d8atagen2026-09-26 09:59 UTC446
        if let Err(error) = saved {
a319bae745d8atagen2026-09-26 09:59 UTC447
            tracing::warn!(%error, "saving the session record failed");
a319bae745d8atagen2026-09-26 09:59 UTC448
            self.publish(|_| Some(StateChange::SessionSaveFailed(Arc::new(error))));
a319bae745d8atagen2026-09-26 09:59 UTC449
        }
a319bae745d8atagen2026-09-26 09:59 UTC450
    }
a319bae745d8atagen2026-09-26 09:59 UTC451
a319bae745d8atagen2026-09-26 09:59 UTC452
    fn refetch_rooms(&mut self) {
a319bae745d8atagen2026-09-26 09:59 UTC453
        let mut published = self.shared.lock();
a319bae745d8atagen2026-09-26 09:59 UTC454
        match published.rooms_fetch {
a319bae745d8atagen2026-09-26 09:59 UTC455
            RoomsFetch::Idle => {
a319bae745d8atagen2026-09-26 09:59 UTC456
                published.rooms_fetch = RoomsFetch::Running;
a319bae745d8atagen2026-09-26 09:59 UTC457
                drop(published);
a319bae745d8atagen2026-09-26 09:59 UTC458
                let task = spawn(fetch_rooms(self.handle.clone(), self.shared.clone()));
a319bae745d8atagen2026-09-26 09:59 UTC459
                self.rooms_fetch = Some(task.abort_handle());
a319bae745d8atagen2026-09-26 09:59 UTC460
            }
a319bae745d8atagen2026-09-26 09:59 UTC461
            RoomsFetch::Running => published.rooms_fetch = RoomsFetch::Again,
a319bae745d8atagen2026-09-26 09:59 UTC462
            RoomsFetch::Again => {}
a319bae745d8atagen2026-09-26 09:59 UTC463
        }
a319bae745d8atagen2026-09-26 09:59 UTC464
    }
a319bae745d8atagen2026-09-26 09:59 UTC465
a319bae745d8atagen2026-09-26 09:59 UTC466
    fn cancel_resync(&mut self, timeline_id: &TimelineId) {
a319bae745d8atagen2026-09-26 09:59 UTC467
        if let Some(task) = self.resyncs.remove(timeline_id) {
a319bae745d8atagen2026-09-26 09:59 UTC468
            task.abort();
a319bae745d8atagen2026-09-26 09:59 UTC469
        }
a319bae745d8atagen2026-09-26 09:59 UTC470
    }
a319bae745d8atagen2026-09-26 09:59 UTC471
a319bae745d8atagen2026-09-26 09:59 UTC472
    fn cancel_resyncs(&mut self) {
a319bae745d8atagen2026-09-26 09:59 UTC473
        for (_, task) in self.resyncs.drain() {
a319bae745d8atagen2026-09-26 09:59 UTC474
            task.abort();
a319bae745d8atagen2026-09-26 09:59 UTC475
        }
a319bae745d8atagen2026-09-26 09:59 UTC476
    }
a319bae745d8atagen2026-09-26 09:59 UTC477
a319bae745d8atagen2026-09-26 09:59 UTC478
    fn publish(&self, change: impl FnOnce(&mut State) -> Option<StateChange>) {
a319bae745d8atagen2026-09-26 09:59 UTC479
        self.shared.publish(change);
a319bae745d8atagen2026-09-26 09:59 UTC480
    }
a319bae745d8atagen2026-09-26 09:59 UTC481
}
a319bae745d8atagen2026-09-26 09:59 UTC482
a319bae745d8atagen2026-09-26 09:59 UTC483
impl Drop for Mirror {
a319bae745d8atagen2026-09-26 09:59 UTC484
    fn drop(&mut self) {
a319bae745d8atagen2026-09-26 09:59 UTC485
        self.cancel_resyncs();
a319bae745d8atagen2026-09-26 09:59 UTC486
        if let Some(task) = self.rooms_fetch.take() {
a319bae745d8atagen2026-09-26 09:59 UTC487
            task.abort();
a319bae745d8atagen2026-09-26 09:59 UTC488
        }
a319bae745d8atagen2026-09-26 09:59 UTC489
        self.shared.lock().updates = None;
a319bae745d8atagen2026-09-26 09:59 UTC490
    }
a319bae745d8atagen2026-09-26 09:59 UTC491
}
a319bae745d8atagen2026-09-26 09:59 UTC492
a319bae745d8atagen2026-09-26 09:59 UTC493
/// asks until the engine answers, since a busy room or a full queue is only ever for now.
a319bae745d8atagen2026-09-26 09:59 UTC494
/// a reset or a close for the timeline aborts it, and the engine sends a reset before it answers
a319bae745d8atagen2026-09-26 09:59 UTC495
async fn resync(handle: ClientHandle, timeline_id: TimelineId) {
a319bae745d8atagen2026-09-26 09:59 UTC496
    let mut backoff = RESYNC_BACKOFF;
a319bae745d8atagen2026-09-26 09:59 UTC497
    loop {
a319bae745d8atagen2026-09-26 09:59 UTC498
        let command = Command::Timeline(TimelineCommand::Resync {
a319bae745d8atagen2026-09-26 09:59 UTC499
            timeline_id: timeline_id.clone(),
a319bae745d8atagen2026-09-26 09:59 UTC500
        });
a319bae745d8atagen2026-09-26 09:59 UTC501
        match handle.execute(command).await {
a319bae745d8atagen2026-09-26 09:59 UTC502
            Ok(_) => return,
a319bae745d8atagen2026-09-26 09:59 UTC503
            Err(error) if error.kind == ErrorKind::Interrupted => return,
a319bae745d8atagen2026-09-26 09:59 UTC504
            Err(error) => {
a319bae745d8atagen2026-09-26 09:59 UTC505
                tracing::debug!(%error, ?timeline_id, "resync refused, trying again");
a319bae745d8atagen2026-09-26 09:59 UTC506
                matrix_sdk::sleep::sleep(backoff).await;
a319bae745d8atagen2026-09-26 09:59 UTC507
                backoff = (backoff * 2).min(RESYNC_BACKOFF_CAP);
a319bae745d8atagen2026-09-26 09:59 UTC508
            }
a319bae745d8atagen2026-09-26 09:59 UTC509
        }
a319bae745d8atagen2026-09-26 09:59 UTC510
    }
a319bae745d8atagen2026-09-26 09:59 UTC511
}
a319bae745d8atagen2026-09-26 09:59 UTC512
a319bae745d8atagen2026-09-26 09:59 UTC513
async fn fetch_rooms(handle: ClientHandle, shared: Arc<Shared>) {
a319bae745d8atagen2026-09-26 09:59 UTC514
    loop {
a319bae745d8atagen2026-09-26 09:59 UTC515
        let rooms: Vector<RoomSummary> = handle.rooms().await.into();
a319bae745d8atagen2026-09-26 09:59 UTC516
        shared.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC517
            state.rooms = rooms.clone();
a319bae745d8atagen2026-09-26 09:59 UTC518
            Some(StateChange::Rooms(rooms))
a319bae745d8atagen2026-09-26 09:59 UTC519
        });
a319bae745d8atagen2026-09-26 09:59 UTC520
        let mut published = shared.lock();
a319bae745d8atagen2026-09-26 09:59 UTC521
        if published.rooms_fetch != RoomsFetch::Again {
a319bae745d8atagen2026-09-26 09:59 UTC522
            published.rooms_fetch = RoomsFetch::Idle;
a319bae745d8atagen2026-09-26 09:59 UTC523
            return;
a319bae745d8atagen2026-09-26 09:59 UTC524
        }
a319bae745d8atagen2026-09-26 09:59 UTC525
        published.rooms_fetch = RoomsFetch::Running;
a319bae745d8atagen2026-09-26 09:59 UTC526
    }
a319bae745d8atagen2026-09-26 09:59 UTC527
}
a319bae745d8atagen2026-09-26 09:59 UTC528
a319bae745d8atagen2026-09-26 09:59 UTC529
/// cheap to clone, one per frontend
a319bae745d8atagen2026-09-26 09:59 UTC530
#[derive(Clone, Debug)]
a319bae745d8atagen2026-09-26 09:59 UTC531
pub struct MirrorHandle {
a319bae745d8atagen2026-09-26 09:59 UTC532
    shared: Arc<Shared>,
a319bae745d8atagen2026-09-26 09:59 UTC533
}
a319bae745d8atagen2026-09-26 09:59 UTC534
a319bae745d8atagen2026-09-26 09:59 UTC535
impl MirrorHandle {
a319bae745d8atagen2026-09-26 09:59 UTC536
    #[must_use]
a319bae745d8atagen2026-09-26 09:59 UTC537
    pub fn snapshot(&self) -> Arc<State> {
a319bae745d8atagen2026-09-26 09:59 UTC538
        self.shared.lock().state.clone()
a319bae745d8atagen2026-09-26 09:59 UTC539
    }
a319bae745d8atagen2026-09-26 09:59 UTC540
a319bae745d8atagen2026-09-26 09:59 UTC541
    /// opens with the current state, then every update after it. ends once the mirror stops
a319bae745d8atagen2026-09-26 09:59 UTC542
    #[must_use]
a319bae745d8atagen2026-09-26 09:59 UTC543
    pub fn subscribe(&self) -> Subscription {
a319bae745d8atagen2026-09-26 09:59 UTC544
        let (first, receiving) = self.shared.clone().subscribe();
a319bae745d8atagen2026-09-26 09:59 UTC545
        let rest = futures_util::stream::unfold(receiving, |(shared, receiver)| async move {
a319bae745d8atagen2026-09-26 09:59 UTC546
            let mut receiver = receiver?;
a319bae745d8atagen2026-09-26 09:59 UTC547
            match receiver.recv().await {
a319bae745d8atagen2026-09-26 09:59 UTC548
                Ok(update) => Some((update, (shared, Some(receiver)))),
a319bae745d8atagen2026-09-26 09:59 UTC549
                Err(broadcast::error::RecvError::Lagged(missed)) => {
a319bae745d8atagen2026-09-26 09:59 UTC550
                    tracing::debug!(missed, "mirror subscriber lagged, starting it over");
a319bae745d8atagen2026-09-26 09:59 UTC551
                    Some(shared.subscribe())
a319bae745d8atagen2026-09-26 09:59 UTC552
                }
a319bae745d8atagen2026-09-26 09:59 UTC553
                Err(broadcast::error::RecvError::Closed) => None,
a319bae745d8atagen2026-09-26 09:59 UTC554
            }
a319bae745d8atagen2026-09-26 09:59 UTC555
        });
a319bae745d8atagen2026-09-26 09:59 UTC556
        let updates = futures_util::stream::once(std::future::ready(first)).chain(rest);
a319bae745d8atagen2026-09-26 09:59 UTC557
        #[cfg(not(target_family = "wasm"))]
a319bae745d8atagen2026-09-26 09:59 UTC558
        let updates = updates.boxed();
a319bae745d8atagen2026-09-26 09:59 UTC559
        #[cfg(target_family = "wasm")]
a319bae745d8atagen2026-09-26 09:59 UTC560
        let updates = updates.boxed_local();
a319bae745d8atagen2026-09-26 09:59 UTC561
        Subscription(updates)
a319bae745d8atagen2026-09-26 09:59 UTC562
    }
a319bae745d8atagen2026-09-26 09:59 UTC563
}
a319bae745d8atagen2026-09-26 09:59 UTC564
a319bae745d8atagen2026-09-26 09:59 UTC565
pub struct Subscription(EngineBoxStream<Update>);
a319bae745d8atagen2026-09-26 09:59 UTC566
a319bae745d8atagen2026-09-26 09:59 UTC567
impl fmt::Debug for Subscription {
a319bae745d8atagen2026-09-26 09:59 UTC568
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
a319bae745d8atagen2026-09-26 09:59 UTC569
        formatter
a319bae745d8atagen2026-09-26 09:59 UTC570
            .debug_struct("Subscription")
a319bae745d8atagen2026-09-26 09:59 UTC571
            .finish_non_exhaustive()
a319bae745d8atagen2026-09-26 09:59 UTC572
    }
a319bae745d8atagen2026-09-26 09:59 UTC573
}
a319bae745d8atagen2026-09-26 09:59 UTC574
a319bae745d8atagen2026-09-26 09:59 UTC575
impl Stream for Subscription {
a319bae745d8atagen2026-09-26 09:59 UTC576
    type Item = Update;
a319bae745d8atagen2026-09-26 09:59 UTC577
a319bae745d8atagen2026-09-26 09:59 UTC578
    fn poll_next(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<Update>> {
a319bae745d8atagen2026-09-26 09:59 UTC579
        self.0.poll_next_unpin(context)
a319bae745d8atagen2026-09-26 09:59 UTC580
    }
a319bae745d8atagen2026-09-26 09:59 UTC581
}
a319bae745d8atagen2026-09-26 09:59 UTC582
a319bae745d8atagen2026-09-26 09:59 UTC583
#[derive(Debug)]
a319bae745d8atagen2026-09-26 09:59 UTC584
struct Shared {
a319bae745d8atagen2026-09-26 09:59 UTC585
    published: Mutex<Published>,
a319bae745d8atagen2026-09-26 09:59 UTC586
}
a319bae745d8atagen2026-09-26 09:59 UTC587
a319bae745d8atagen2026-09-26 09:59 UTC588
#[derive(Debug)]
a319bae745d8atagen2026-09-26 09:59 UTC589
struct Published {
a319bae745d8atagen2026-09-26 09:59 UTC590
    state: Arc<State>,
a319bae745d8atagen2026-09-26 09:59 UTC591
    sequence: u64,
a319bae745d8atagen2026-09-26 09:59 UTC592
    /// gone once the mirror stops, which ends every subscription
a319bae745d8atagen2026-09-26 09:59 UTC593
    updates: Option<broadcast::Sender<Update>>,
a319bae745d8atagen2026-09-26 09:59 UTC594
    rooms_fetch: RoomsFetch,
a319bae745d8atagen2026-09-26 09:59 UTC595
}
a319bae745d8atagen2026-09-26 09:59 UTC596
a319bae745d8atagen2026-09-26 09:59 UTC597
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
a319bae745d8atagen2026-09-26 09:59 UTC598
enum RoomsFetch {
a319bae745d8atagen2026-09-26 09:59 UTC599
    Idle,
a319bae745d8atagen2026-09-26 09:59 UTC600
    Running,
a319bae745d8atagen2026-09-26 09:59 UTC601
    Again,
a319bae745d8atagen2026-09-26 09:59 UTC602
}
a319bae745d8atagen2026-09-26 09:59 UTC603
a319bae745d8atagen2026-09-26 09:59 UTC604
impl Shared {
a319bae745d8atagen2026-09-26 09:59 UTC605
    fn lock(&self) -> MutexGuard<'_, Published> {
a319bae745d8atagen2026-09-26 09:59 UTC606
        self.published
a319bae745d8atagen2026-09-26 09:59 UTC607
            .lock()
a319bae745d8atagen2026-09-26 09:59 UTC608
            .unwrap_or_else(PoisonError::into_inner)
a319bae745d8atagen2026-09-26 09:59 UTC609
    }
a319bae745d8atagen2026-09-26 09:59 UTC610
a319bae745d8atagen2026-09-26 09:59 UTC611
    /// state, sequence and send move together under the one lock, which is what lets
a319bae745d8atagen2026-09-26 09:59 UTC612
    /// `subscribe` take a snapshot and a receiver with nothing between them
a319bae745d8atagen2026-09-26 09:59 UTC613
    fn publish(&self, change: impl FnOnce(&mut State) -> Option<StateChange>) {
a319bae745d8atagen2026-09-26 09:59 UTC614
        let mut published = self.lock();
a319bae745d8atagen2026-09-26 09:59 UTC615
        let Some(change) = change(Arc::make_mut(&mut published.state)) else {
a319bae745d8atagen2026-09-26 09:59 UTC616
            return;
a319bae745d8atagen2026-09-26 09:59 UTC617
        };
a319bae745d8atagen2026-09-26 09:59 UTC618
        published.sequence += 1;
a319bae745d8atagen2026-09-26 09:59 UTC619
        let update = Update {
a319bae745d8atagen2026-09-26 09:59 UTC620
            sequence: published.sequence,
a319bae745d8atagen2026-09-26 09:59 UTC621
            change,
a319bae745d8atagen2026-09-26 09:59 UTC622
        };
a319bae745d8atagen2026-09-26 09:59 UTC623
        if let Some(updates) = &published.updates {
a319bae745d8atagen2026-09-26 09:59 UTC624
            let _ = updates.send(update);
a319bae745d8atagen2026-09-26 09:59 UTC625
        }
a319bae745d8atagen2026-09-26 09:59 UTC626
    }
a319bae745d8atagen2026-09-26 09:59 UTC627
a319bae745d8atagen2026-09-26 09:59 UTC628
    fn subscribe(self: Arc<Self>) -> (Update, (Arc<Self>, Option<broadcast::Receiver<Update>>)) {
a319bae745d8atagen2026-09-26 09:59 UTC629
        let published = self.lock();
a319bae745d8atagen2026-09-26 09:59 UTC630
        let update = Update {
a319bae745d8atagen2026-09-26 09:59 UTC631
            sequence: published.sequence,
a319bae745d8atagen2026-09-26 09:59 UTC632
            change: StateChange::State(published.state.clone()),
a319bae745d8atagen2026-09-26 09:59 UTC633
        };
a319bae745d8atagen2026-09-26 09:59 UTC634
        let receiver = published.updates.as_ref().map(broadcast::Sender::subscribe);
a319bae745d8atagen2026-09-26 09:59 UTC635
        drop(published);
a319bae745d8atagen2026-09-26 09:59 UTC636
        (update, (self, receiver))
a319bae745d8atagen2026-09-26 09:59 UTC637
    }
a319bae745d8atagen2026-09-26 09:59 UTC638
}