atagen

macltok

public
Star0
Watch0
c495e7521c3aa091380ce9439c827634e53838d6
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,
66db1b3cd895atagen2026-10-25 10:28 UTC23
    EventContextCloseReason, HistoryRepairReport, HistoryState, ImagePacks, RecentHistoryState,
60454002af5fatagen2026-10-25 11:13 UTC24
    RoomSummary, ThreadListCloseReason, ThreadListEntry, TimelineCommand, TimelineDiff, TimelineId,
60454002af5fatagen2026-10-25 11:13 UTC25
    TimelineItem, TypingState, UploadOutcome, VerificationState, VerificationTarget,
60454002af5fatagen2026-10-25 11:13 UTC26
    engine::executor::EngineBoxFuture, transport::PublicationRegistration,
a319bae745d8atagen2026-09-26 09:59 UTC27
};
a319bae745d8atagen2026-09-26 09:59 UTC28
a319bae745d8atagen2026-09-26 09:59 UTC29
#[cfg(not(target_family = "wasm"))]
a319bae745d8atagen2026-09-26 09:59 UTC30
type EngineBoxStream<T> = futures_util::stream::BoxStream<'static, T>;
a319bae745d8atagen2026-09-26 09:59 UTC31
#[cfg(target_family = "wasm")]
a319bae745d8atagen2026-09-26 09:59 UTC32
type EngineBoxStream<T> = futures_util::stream::LocalBoxStream<'static, T>;
a319bae745d8atagen2026-09-26 09:59 UTC33
a319bae745d8atagen2026-09-26 09:59 UTC34
const DEFAULT_CAPACITY: usize = 256;
a319bae745d8atagen2026-09-26 09:59 UTC35
const RESYNC_BACKOFF: Duration = Duration::from_millis(100);
a319bae745d8atagen2026-09-26 09:59 UTC36
const RESYNC_BACKOFF_CAP: Duration = Duration::from_secs(10);
a319bae745d8atagen2026-09-26 09:59 UTC37
a319bae745d8atagen2026-09-26 09:59 UTC38
/// everything the engine has said so far, folded together
a319bae745d8atagen2026-09-26 09:59 UTC39
#[derive(Clone, Debug, PartialEq)]
a319bae745d8atagen2026-09-26 09:59 UTC40
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
a319bae745d8atagen2026-09-26 09:59 UTC41
#[non_exhaustive]
a319bae745d8atagen2026-09-26 09:59 UTC42
pub struct State {
a319bae745d8atagen2026-09-26 09:59 UTC43
    pub user_id: Option<OwnedUserId>,
a319bae745d8atagen2026-09-26 09:59 UTC44
    pub session: SessionStatus,
a319bae745d8atagen2026-09-26 09:59 UTC45
    pub connection: ConnectionState,
a319bae745d8atagen2026-09-26 09:59 UTC46
    pub encryption: Option<EncryptionState>,
a319bae745d8atagen2026-09-26 09:59 UTC47
    pub verification: Option<VerificationState>,
a319bae745d8atagen2026-09-26 09:59 UTC48
    pub verification_target: Option<VerificationTarget>,
cbb715e11963atagen2026-10-24 23:24 UTC49
    pub mentions: Option<crate::MentionsPage>,
a319bae745d8atagen2026-09-26 09:59 UTC50
    pub rooms: Vector<RoomSummary>,
17e042fbc331atagen2026-10-08 12:39 UTC51
    pub image_packs: ImagePacks,
a319bae745d8atagen2026-09-26 09:59 UTC52
    pub timelines: imbl::HashMap<TimelineId, TimelineState>,
66db1b3cd895atagen2026-10-25 10:28 UTC53
    pub thread_lists: imbl::HashMap<OwnedRoomId, ThreadListState>,
d9b96952ec60atagen2026-10-25 09:07 UTC54
    /// a room without an entry is idle. closing a room drops its entries here and in `repairs`
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC55
    pub recent_history: imbl::HashMap<OwnedRoomId, RecentHistoryState>,
80381e06a573atagen2026-10-25 03:02 UTC56
    pub repairs: imbl::HashMap<OwnedRoomId, HistoryRepairReport>,
a319bae745d8atagen2026-09-26 09:59 UTC57
    pub typing: imbl::HashMap<OwnedRoomId, Vec<OwnedUserId>>,
a319bae745d8atagen2026-09-26 09:59 UTC58
    pub uploads: imbl::HashMap<OwnedTransactionId, UploadState>,
0b8ce8dc9a1catagen2026-09-26 12:30 UTC59
    /// what stopped the engine, if something went wrong
0b8ce8dc9a1catagen2026-09-26 12:30 UTC60
    pub engine_failure: Option<EngineFailure>,
0b8ce8dc9a1catagen2026-09-26 12:30 UTC61
    /// the last save of the session record that failed, until one succeeds
0b8ce8dc9a1catagen2026-09-26 12:30 UTC62
    pub session_save_failure: Option<String>,
a319bae745d8atagen2026-09-26 09:59 UTC63
}
a319bae745d8atagen2026-09-26 09:59 UTC64
a319bae745d8atagen2026-09-26 09:59 UTC65
#[derive(Clone, Debug, PartialEq)]
a319bae745d8atagen2026-09-26 09:59 UTC66
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
a319bae745d8atagen2026-09-26 09:59 UTC67
#[non_exhaustive]
a319bae745d8atagen2026-09-26 09:59 UTC68
pub struct TimelineState {
a319bae745d8atagen2026-09-26 09:59 UTC69
    pub items: Vector<TimelineItem>,
a319bae745d8atagen2026-09-26 09:59 UTC70
    pub history: HistoryState,
a319bae745d8atagen2026-09-26 09:59 UTC71
    /// a batch didn't fit, so `items` is the last good copy until the next reset
a319bae745d8atagen2026-09-26 09:59 UTC72
    pub diverged: bool,
a319bae745d8atagen2026-09-26 09:59 UTC73
}
a319bae745d8atagen2026-09-26 09:59 UTC74
66db1b3cd895atagen2026-10-25 10:28 UTC75
#[derive(Clone, Debug, PartialEq)]
66db1b3cd895atagen2026-10-25 10:28 UTC76
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
66db1b3cd895atagen2026-10-25 10:28 UTC77
#[non_exhaustive]
66db1b3cd895atagen2026-10-25 10:28 UTC78
pub struct ThreadListState {
66db1b3cd895atagen2026-10-25 10:28 UTC79
    pub entries: Vector<ThreadListEntry>,
66db1b3cd895atagen2026-10-25 10:28 UTC80
    pub history: HistoryState,
66db1b3cd895atagen2026-10-25 10:28 UTC81
}
66db1b3cd895atagen2026-10-25 10:28 UTC82
a319bae745d8atagen2026-09-26 09:59 UTC83
#[derive(Clone, Debug, Eq, PartialEq)]
a319bae745d8atagen2026-09-26 09:59 UTC84
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
a319bae745d8atagen2026-09-26 09:59 UTC85
#[non_exhaustive]
a319bae745d8atagen2026-09-26 09:59 UTC86
pub struct UploadState {
a319bae745d8atagen2026-09-26 09:59 UTC87
    pub room_id: OwnedRoomId,
a319bae745d8atagen2026-09-26 09:59 UTC88
    pub uploaded_bytes: u64,
a319bae745d8atagen2026-09-26 09:59 UTC89
    pub total_bytes: u64,
a319bae745d8atagen2026-09-26 09:59 UTC90
    pub complete: bool,
a319bae745d8atagen2026-09-26 09:59 UTC91
    pub failure: Option<UploadFailure>,
a319bae745d8atagen2026-09-26 09:59 UTC92
}
a319bae745d8atagen2026-09-26 09:59 UTC93
a319bae745d8atagen2026-09-26 09:59 UTC94
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
a319bae745d8atagen2026-09-26 09:59 UTC95
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
a319bae745d8atagen2026-09-26 09:59 UTC96
#[non_exhaustive]
a319bae745d8atagen2026-09-26 09:59 UTC97
pub struct UploadFailure {
a319bae745d8atagen2026-09-26 09:59 UTC98
    pub kind: ErrorKind,
a319bae745d8atagen2026-09-26 09:59 UTC99
    pub will_retry: bool,
a319bae745d8atagen2026-09-26 09:59 UTC100
}
a319bae745d8atagen2026-09-26 09:59 UTC101
a319bae745d8atagen2026-09-26 09:59 UTC102
/// the `Termination` it'll end in, as far as the events tell
a319bae745d8atagen2026-09-26 09:59 UTC103
#[derive(Clone, Debug, Eq, PartialEq)]
a319bae745d8atagen2026-09-26 09:59 UTC104
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
a319bae745d8atagen2026-09-26 09:59 UTC105
pub enum SessionStatus {
a319bae745d8atagen2026-09-26 09:59 UTC106
    Active,
a319bae745d8atagen2026-09-26 09:59 UTC107
    SoftLoggedOut {
a319bae745d8atagen2026-09-26 09:59 UTC108
        device_id: OwnedDeviceId,
a319bae745d8atagen2026-09-26 09:59 UTC109
    },
a319bae745d8atagen2026-09-26 09:59 UTC110
    AccessRevoked,
a319bae745d8atagen2026-09-26 09:59 UTC111
    LoggedOut,
a319bae745d8atagen2026-09-26 09:59 UTC112
    /// stopped with the session still good
a319bae745d8atagen2026-09-26 09:59 UTC113
    Stopped,
a319bae745d8atagen2026-09-26 09:59 UTC114
}
a319bae745d8atagen2026-09-26 09:59 UTC115
a319bae745d8atagen2026-09-26 09:59 UTC116
#[derive(Clone, Debug)]
a319bae745d8atagen2026-09-26 09:59 UTC117
#[non_exhaustive]
a319bae745d8atagen2026-09-26 09:59 UTC118
pub struct Update {
a319bae745d8atagen2026-09-26 09:59 UTC119
    /// one more than the update before it, and a `State` carries the one it's current as of
a319bae745d8atagen2026-09-26 09:59 UTC120
    pub sequence: u64,
a319bae745d8atagen2026-09-26 09:59 UTC121
    pub change: StateChange,
a319bae745d8atagen2026-09-26 09:59 UTC122
}
a319bae745d8atagen2026-09-26 09:59 UTC123
a319bae745d8atagen2026-09-26 09:59 UTC124
#[derive(Clone, Debug)]
a319bae745d8atagen2026-09-26 09:59 UTC125
pub enum StateChange {
a319bae745d8atagen2026-09-26 09:59 UTC126
    /// start over from this. a subscription opens with one and gets another after lagging
a319bae745d8atagen2026-09-26 09:59 UTC127
    State(Arc<State>),
a319bae745d8atagen2026-09-26 09:59 UTC128
    Authenticated(OwnedUserId),
a319bae745d8atagen2026-09-26 09:59 UTC129
    Session(SessionStatus),
a319bae745d8atagen2026-09-26 09:59 UTC130
    /// the record on disk may now hold a spent refresh token
0b8ce8dc9a1catagen2026-09-26 12:30 UTC131
    SessionSaveFailed(String),
0b8ce8dc9a1catagen2026-09-26 12:30 UTC132
    /// saved again after `SessionSaveFailed`
0b8ce8dc9a1catagen2026-09-26 12:30 UTC133
    SessionSaved,
a319bae745d8atagen2026-09-26 09:59 UTC134
    Connection(ConnectionState),
a319bae745d8atagen2026-09-26 09:59 UTC135
    Encryption(EncryptionState),
a319bae745d8atagen2026-09-26 09:59 UTC136
    Verification(VerificationState),
a319bae745d8atagen2026-09-26 09:59 UTC137
    VerificationTarget(VerificationTarget),
cbb715e11963atagen2026-10-24 23:24 UTC138
    Mentions(crate::MentionsUpdate),
a319bae745d8atagen2026-09-26 09:59 UTC139
    /// room modes arrive with the `Rooms` that follows
a319bae745d8atagen2026-09-26 09:59 UTC140
    NotificationSettings,
a319bae745d8atagen2026-09-26 09:59 UTC141
    Rooms(Vector<RoomSummary>),
17e042fbc331atagen2026-10-08 12:39 UTC142
    ImagePacks(ImagePacks),
a319bae745d8atagen2026-09-26 09:59 UTC143
    Timeline {
a319bae745d8atagen2026-09-26 09:59 UTC144
        timeline_id: TimelineId,
a319bae745d8atagen2026-09-26 09:59 UTC145
        change: TimelineChange,
a319bae745d8atagen2026-09-26 09:59 UTC146
    },
a319bae745d8atagen2026-09-26 09:59 UTC147
    History {
a319bae745d8atagen2026-09-26 09:59 UTC148
        timeline_id: TimelineId,
a319bae745d8atagen2026-09-26 09:59 UTC149
        state: HistoryState,
a319bae745d8atagen2026-09-26 09:59 UTC150
    },
66db1b3cd895atagen2026-10-25 10:28 UTC151
    ThreadList {
66db1b3cd895atagen2026-10-25 10:28 UTC152
        room_id: OwnedRoomId,
66db1b3cd895atagen2026-10-25 10:28 UTC153
        change: ThreadListChange,
66db1b3cd895atagen2026-10-25 10:28 UTC154
    },
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC155
    RecentHistory {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC156
        room_id: OwnedRoomId,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC157
        state: RecentHistoryState,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC158
    },
80381e06a573atagen2026-10-25 03:02 UTC159
    HistoryRepair(HistoryRepairReport),
a319bae745d8atagen2026-09-26 09:59 UTC160
    Typing(TypingState),
1f875b103b27atagen2026-10-25 10:44 UTC161
    EventContextClosed {
1f875b103b27atagen2026-10-25 10:44 UTC162
        context_id: String,
1f875b103b27atagen2026-10-25 10:44 UTC163
        reason: EventContextCloseReason,
1f875b103b27atagen2026-10-25 10:44 UTC164
    },
74befbc72bafatagen2026-10-25 11:18 UTC165
    SearchClosed {
74befbc72bafatagen2026-10-25 11:18 UTC166
        search_id: String,
74befbc72bafatagen2026-10-25 11:18 UTC167
        reason: crate::SearchCloseReason,
74befbc72bafatagen2026-10-25 11:18 UTC168
    },
a319bae745d8atagen2026-09-26 09:59 UTC169
    Upload {
a319bae745d8atagen2026-09-26 09:59 UTC170
        transaction_id: OwnedTransactionId,
a319bae745d8atagen2026-09-26 09:59 UTC171
        upload: UploadState,
a319bae745d8atagen2026-09-26 09:59 UTC172
    },
a319bae745d8atagen2026-09-26 09:59 UTC173
    UploadFinished {
a319bae745d8atagen2026-09-26 09:59 UTC174
        room_id: OwnedRoomId,
a319bae745d8atagen2026-09-26 09:59 UTC175
        transaction_id: OwnedTransactionId,
a319bae745d8atagen2026-09-26 09:59 UTC176
        outcome: UploadOutcome,
a319bae745d8atagen2026-09-26 09:59 UTC177
    },
a319bae745d8atagen2026-09-26 09:59 UTC178
    Failed(EngineFailure),
a319bae745d8atagen2026-09-26 09:59 UTC179
}
a319bae745d8atagen2026-09-26 09:59 UTC180
a319bae745d8atagen2026-09-26 09:59 UTC181
#[derive(Clone, Debug)]
a319bae745d8atagen2026-09-26 09:59 UTC182
pub enum TimelineChange {
a319bae745d8atagen2026-09-26 09:59 UTC183
    Reset {
a319bae745d8atagen2026-09-26 09:59 UTC184
        items: Vector<TimelineItem>,
a319bae745d8atagen2026-09-26 09:59 UTC185
    },
a319bae745d8atagen2026-09-26 09:59 UTC186
    /// `items` is what applying `diffs` left
a319bae745d8atagen2026-09-26 09:59 UTC187
    Changed {
a319bae745d8atagen2026-09-26 09:59 UTC188
        diffs: Arc<[TimelineDiff]>,
a319bae745d8atagen2026-09-26 09:59 UTC189
        items: Vector<TimelineItem>,
a319bae745d8atagen2026-09-26 09:59 UTC190
    },
a319bae745d8atagen2026-09-26 09:59 UTC191
    /// a batch didn't fit. a resync is on its way and nothing changes until its reset
a319bae745d8atagen2026-09-26 09:59 UTC192
    Diverged,
a319bae745d8atagen2026-09-26 09:59 UTC193
    Closed,
a319bae745d8atagen2026-09-26 09:59 UTC194
}
a319bae745d8atagen2026-09-26 09:59 UTC195
66db1b3cd895atagen2026-10-25 10:28 UTC196
#[derive(Clone, Debug)]
66db1b3cd895atagen2026-10-25 10:28 UTC197
pub enum ThreadListChange {
66db1b3cd895atagen2026-10-25 10:28 UTC198
    Reset {
66db1b3cd895atagen2026-10-25 10:28 UTC199
        entries: Vector<ThreadListEntry>,
66db1b3cd895atagen2026-10-25 10:28 UTC200
    },
66db1b3cd895atagen2026-10-25 10:28 UTC201
    /// `entries` is what applying `upserts` left
66db1b3cd895atagen2026-10-25 10:28 UTC202
    Changed {
66db1b3cd895atagen2026-10-25 10:28 UTC203
        upserts: Arc<[ThreadListEntry]>,
66db1b3cd895atagen2026-10-25 10:28 UTC204
        entries: Vector<ThreadListEntry>,
66db1b3cd895atagen2026-10-25 10:28 UTC205
    },
66db1b3cd895atagen2026-10-25 10:28 UTC206
    History(HistoryState),
60454002af5fatagen2026-10-25 11:13 UTC207
    Closed(ThreadListCloseReason),
66db1b3cd895atagen2026-10-25 10:28 UTC208
}
66db1b3cd895atagen2026-10-25 10:28 UTC209
a319bae745d8atagen2026-09-26 09:59 UTC210
/// drains the event stream into a `State` that any number of `MirrorHandle`s watch
a319bae745d8atagen2026-09-26 09:59 UTC211
pub struct Mirror {
a319bae745d8atagen2026-09-26 09:59 UTC212
    handle: ClientHandle,
a319bae745d8atagen2026-09-26 09:59 UTC213
    events: EngineBoxStream<Event>,
a319bae745d8atagen2026-09-26 09:59 UTC214
    shared: Arc<Shared>,
80381e06a573atagen2026-10-25 03:02 UTC215
    _publication: PublicationRegistration,
a319bae745d8atagen2026-09-26 09:59 UTC216
    resyncs: HashMap<TimelineId, AbortHandle>,
a319bae745d8atagen2026-09-26 09:59 UTC217
    rooms_fetch: Option<AbortHandle>,
17e042fbc331atagen2026-10-08 12:39 UTC218
    packs_fetch: Option<AbortHandle>,
a319bae745d8atagen2026-09-26 09:59 UTC219
}
a319bae745d8atagen2026-09-26 09:59 UTC220
a319bae745d8atagen2026-09-26 09:59 UTC221
impl fmt::Debug for Mirror {
a319bae745d8atagen2026-09-26 09:59 UTC222
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
a319bae745d8atagen2026-09-26 09:59 UTC223
        formatter.debug_struct("Mirror").finish_non_exhaustive()
a319bae745d8atagen2026-09-26 09:59 UTC224
    }
a319bae745d8atagen2026-09-26 09:59 UTC225
}
a319bae745d8atagen2026-09-26 09:59 UTC226
a319bae745d8atagen2026-09-26 09:59 UTC227
impl Mirror {
a319bae745d8atagen2026-09-26 09:59 UTC228
    /// 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 UTC229
    pub fn new(
a319bae745d8atagen2026-09-26 09:59 UTC230
        handle: ClientHandle,
a319bae745d8atagen2026-09-26 09:59 UTC231
        events: impl Stream<Item = Event> + SendOutsideWasm + 'static,
a319bae745d8atagen2026-09-26 09:59 UTC232
    ) -> (Self, MirrorHandle) {
a319bae745d8atagen2026-09-26 09:59 UTC233
        Self::with_capacity(handle, events, DEFAULT_CAPACITY)
a319bae745d8atagen2026-09-26 09:59 UTC234
    }
a319bae745d8atagen2026-09-26 09:59 UTC235
0b8ce8dc9a1catagen2026-09-26 12:30 UTC236
    /// `capacity` updates can queue for a subscriber before it lags and starts over. a zero
0b8ce8dc9a1catagen2026-09-26 12:30 UTC237
    /// capacity counts as one
a319bae745d8atagen2026-09-26 09:59 UTC238
    pub fn with_capacity(
a319bae745d8atagen2026-09-26 09:59 UTC239
        handle: ClientHandle,
a319bae745d8atagen2026-09-26 09:59 UTC240
        events: impl Stream<Item = Event> + SendOutsideWasm + 'static,
a319bae745d8atagen2026-09-26 09:59 UTC241
        capacity: usize,
a319bae745d8atagen2026-09-26 09:59 UTC242
    ) -> (Self, MirrorHandle) {
a319bae745d8atagen2026-09-26 09:59 UTC243
        let state = State {
a319bae745d8atagen2026-09-26 09:59 UTC244
            user_id: None,
a319bae745d8atagen2026-09-26 09:59 UTC245
            session: SessionStatus::Active,
a319bae745d8atagen2026-09-26 09:59 UTC246
            connection: *handle.connection_state().borrow(),
a319bae745d8atagen2026-09-26 09:59 UTC247
            encryption: handle.encryption_state(),
a319bae745d8atagen2026-09-26 09:59 UTC248
            verification: None,
a319bae745d8atagen2026-09-26 09:59 UTC249
            verification_target: None,
cbb715e11963atagen2026-10-24 23:24 UTC250
            mentions: None,
a319bae745d8atagen2026-09-26 09:59 UTC251
            rooms: Vector::new(),
17e042fbc331atagen2026-10-08 12:39 UTC252
            image_packs: ImagePacks::default(),
a319bae745d8atagen2026-09-26 09:59 UTC253
            timelines: imbl::HashMap::new(),
66db1b3cd895atagen2026-10-25 10:28 UTC254
            thread_lists: imbl::HashMap::new(),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC255
            recent_history: imbl::HashMap::new(),
80381e06a573atagen2026-10-25 03:02 UTC256
            repairs: imbl::HashMap::new(),
a319bae745d8atagen2026-09-26 09:59 UTC257
            typing: imbl::HashMap::new(),
a319bae745d8atagen2026-09-26 09:59 UTC258
            uploads: imbl::HashMap::new(),
0b8ce8dc9a1catagen2026-09-26 12:30 UTC259
            engine_failure: None,
0b8ce8dc9a1catagen2026-09-26 12:30 UTC260
            session_save_failure: None,
a319bae745d8atagen2026-09-26 09:59 UTC261
        };
a319bae745d8atagen2026-09-26 09:59 UTC262
        let shared = Arc::new(Shared {
a319bae745d8atagen2026-09-26 09:59 UTC263
            published: Mutex::new(Published {
a319bae745d8atagen2026-09-26 09:59 UTC264
                state: Arc::new(state),
a319bae745d8atagen2026-09-26 09:59 UTC265
                sequence: 0,
0b8ce8dc9a1catagen2026-09-26 12:30 UTC266
                updates: Some(broadcast::channel(capacity.max(1)).0),
17e042fbc331atagen2026-10-08 12:39 UTC267
                rooms_fetch: Fetch::Idle,
17e042fbc331atagen2026-10-08 12:39 UTC268
                packs_fetch: Fetch::Idle,
a319bae745d8atagen2026-09-26 09:59 UTC269
            }),
a319bae745d8atagen2026-09-26 09:59 UTC270
        });
80381e06a573atagen2026-10-25 03:02 UTC271
        let publication = handle.register_publication_mirror();
a319bae745d8atagen2026-09-26 09:59 UTC272
        #[cfg(not(target_family = "wasm"))]
a319bae745d8atagen2026-09-26 09:59 UTC273
        let events = events.boxed();
a319bae745d8atagen2026-09-26 09:59 UTC274
        #[cfg(target_family = "wasm")]
a319bae745d8atagen2026-09-26 09:59 UTC275
        let events = events.boxed_local();
a319bae745d8atagen2026-09-26 09:59 UTC276
        let mirror = Self {
a319bae745d8atagen2026-09-26 09:59 UTC277
            handle,
a319bae745d8atagen2026-09-26 09:59 UTC278
            events,
a319bae745d8atagen2026-09-26 09:59 UTC279
            shared: shared.clone(),
80381e06a573atagen2026-10-25 03:02 UTC280
            _publication: publication,
a319bae745d8atagen2026-09-26 09:59 UTC281
            resyncs: HashMap::new(),
a319bae745d8atagen2026-09-26 09:59 UTC282
            rooms_fetch: None,
17e042fbc331atagen2026-10-08 12:39 UTC283
            packs_fetch: None,
a319bae745d8atagen2026-09-26 09:59 UTC284
        };
a319bae745d8atagen2026-09-26 09:59 UTC285
        (mirror, MirrorHandle { shared })
a319bae745d8atagen2026-09-26 09:59 UTC286
    }
a319bae745d8atagen2026-09-26 09:59 UTC287
a319bae745d8atagen2026-09-26 09:59 UTC288
    /// resolves once the event stream ends, which is just after `Stopped`
a319bae745d8atagen2026-09-26 09:59 UTC289
    pub fn run(mut self) -> EngineBoxFuture<'static, ()> {
a319bae745d8atagen2026-09-26 09:59 UTC290
        Box::pin(async move {
a319bae745d8atagen2026-09-26 09:59 UTC291
            self.refetch_rooms();
17e042fbc331atagen2026-10-08 12:39 UTC292
            self.refetch_image_packs();
a319bae745d8atagen2026-09-26 09:59 UTC293
            while let Some(event) = self.events.next().await {
0b8ce8dc9a1catagen2026-09-26 12:30 UTC294
                self.apply(event);
a319bae745d8atagen2026-09-26 09:59 UTC295
            }
a319bae745d8atagen2026-09-26 09:59 UTC296
        })
a319bae745d8atagen2026-09-26 09:59 UTC297
    }
a319bae745d8atagen2026-09-26 09:59 UTC298
0b8ce8dc9a1catagen2026-09-26 12:30 UTC299
    fn apply(&mut self, event: Event) {
a319bae745d8atagen2026-09-26 09:59 UTC300
        match event {
a319bae745d8atagen2026-09-26 09:59 UTC301
            Event::ConnectionChanged(connection) => self.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC302
                state.connection = connection;
a319bae745d8atagen2026-09-26 09:59 UTC303
                Some(StateChange::Connection(connection))
a319bae745d8atagen2026-09-26 09:59 UTC304
            }),
a319bae745d8atagen2026-09-26 09:59 UTC305
            Event::Authenticated { user_id } => self.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC306
                state.user_id = Some(user_id.clone());
a319bae745d8atagen2026-09-26 09:59 UTC307
                Some(StateChange::Authenticated(user_id))
a319bae745d8atagen2026-09-26 09:59 UTC308
            }),
0b8ce8dc9a1catagen2026-09-26 12:30 UTC309
            Event::SessionSaveFailed { message } => self.publish(|state| {
0b8ce8dc9a1catagen2026-09-26 12:30 UTC310
                state.session_save_failure = Some(message.clone());
0b8ce8dc9a1catagen2026-09-26 12:30 UTC311
                Some(StateChange::SessionSaveFailed(message))
0b8ce8dc9a1catagen2026-09-26 12:30 UTC312
            }),
0b8ce8dc9a1catagen2026-09-26 12:30 UTC313
            Event::SessionTokensChanged => self.publish(|state| {
0b8ce8dc9a1catagen2026-09-26 12:30 UTC314
                state.session_save_failure.take()?;
0b8ce8dc9a1catagen2026-09-26 12:30 UTC315
                Some(StateChange::SessionSaved)
0b8ce8dc9a1catagen2026-09-26 12:30 UTC316
            }),
a319bae745d8atagen2026-09-26 09:59 UTC317
            Event::SessionAccessRejected { soft_logout } => self.publish(|state| {
cbb715e11963atagen2026-10-24 23:24 UTC318
                state.mentions = None;
a319bae745d8atagen2026-09-26 09:59 UTC319
                state.session = match soft_logout {
a319bae745d8atagen2026-09-26 09:59 UTC320
                    Some(device_id) => SessionStatus::SoftLoggedOut { device_id },
a319bae745d8atagen2026-09-26 09:59 UTC321
                    None => SessionStatus::AccessRevoked,
a319bae745d8atagen2026-09-26 09:59 UTC322
                };
a319bae745d8atagen2026-09-26 09:59 UTC323
                Some(StateChange::Session(state.session.clone()))
a319bae745d8atagen2026-09-26 09:59 UTC324
            }),
a319bae745d8atagen2026-09-26 09:59 UTC325
            Event::LoggedOut => self.publish(|state| {
cbb715e11963atagen2026-10-24 23:24 UTC326
                state.mentions = None;
a319bae745d8atagen2026-09-26 09:59 UTC327
                state.session = SessionStatus::LoggedOut;
a319bae745d8atagen2026-09-26 09:59 UTC328
                Some(StateChange::Session(SessionStatus::LoggedOut))
a319bae745d8atagen2026-09-26 09:59 UTC329
            }),
a319bae745d8atagen2026-09-26 09:59 UTC330
            Event::Stopped => {
a319bae745d8atagen2026-09-26 09:59 UTC331
                self.cancel_resyncs();
a319bae745d8atagen2026-09-26 09:59 UTC332
                self.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC333
                    if state.session != SessionStatus::Active {
a319bae745d8atagen2026-09-26 09:59 UTC334
                        return None;
a319bae745d8atagen2026-09-26 09:59 UTC335
                    }
a319bae745d8atagen2026-09-26 09:59 UTC336
                    state.session = SessionStatus::Stopped;
a319bae745d8atagen2026-09-26 09:59 UTC337
                    Some(StateChange::Session(SessionStatus::Stopped))
a319bae745d8atagen2026-09-26 09:59 UTC338
                });
a319bae745d8atagen2026-09-26 09:59 UTC339
            }
0b8ce8dc9a1catagen2026-09-26 12:30 UTC340
            Event::Failed(failure) => self.publish(|state| {
0b8ce8dc9a1catagen2026-09-26 12:30 UTC341
                state.engine_failure = Some(failure.clone());
0b8ce8dc9a1catagen2026-09-26 12:30 UTC342
                Some(StateChange::Failed(failure))
0b8ce8dc9a1catagen2026-09-26 12:30 UTC343
            }),
a319bae745d8atagen2026-09-26 09:59 UTC344
            Event::NotificationSettingsChanged => {
a319bae745d8atagen2026-09-26 09:59 UTC345
                self.publish(|_| Some(StateChange::NotificationSettings));
a319bae745d8atagen2026-09-26 09:59 UTC346
            }
a319bae745d8atagen2026-09-26 09:59 UTC347
            Event::RoomsChanged => self.refetch_rooms(),
cbb715e11963atagen2026-10-24 23:24 UTC348
            Event::MentionsChanged(update) => self.publish(|state| {
cbb715e11963atagen2026-10-24 23:24 UTC349
                if state.session != SessionStatus::Active
cbb715e11963atagen2026-10-24 23:24 UTC350
                    || state
cbb715e11963atagen2026-10-24 23:24 UTC351
                        .user_id
cbb715e11963atagen2026-10-24 23:24 UTC352
                        .as_ref()
cbb715e11963atagen2026-10-24 23:24 UTC353
                        .is_some_and(|account| account != &update.snapshot.boundary.account)
cbb715e11963atagen2026-10-24 23:24 UTC354
                {
cbb715e11963atagen2026-10-24 23:24 UTC355
                    return None;
cbb715e11963atagen2026-10-24 23:24 UTC356
                }
cbb715e11963atagen2026-10-24 23:24 UTC357
                if state
cbb715e11963atagen2026-10-24 23:24 UTC358
                    .mentions
cbb715e11963atagen2026-10-24 23:24 UTC359
                    .as_ref()
cbb715e11963atagen2026-10-24 23:24 UTC360
                    .map_or(false, |current| update.snapshot.revision < current.revision)
cbb715e11963atagen2026-10-24 23:24 UTC361
                {
cbb715e11963atagen2026-10-24 23:24 UTC362
                    return None;
cbb715e11963atagen2026-10-24 23:24 UTC363
                }
cbb715e11963atagen2026-10-24 23:24 UTC364
                state.mentions = Some(update.snapshot.clone());
cbb715e11963atagen2026-10-24 23:24 UTC365
                Some(StateChange::Mentions(update))
cbb715e11963atagen2026-10-24 23:24 UTC366
            }),
17e042fbc331atagen2026-10-08 12:39 UTC367
            Event::ImagePacksChanged => self.refetch_image_packs(),
a319bae745d8atagen2026-09-26 09:59 UTC368
            Event::EncryptionChanged(encryption) => self.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC369
                state.encryption = Some(encryption.clone());
a319bae745d8atagen2026-09-26 09:59 UTC370
                Some(StateChange::Encryption(encryption))
a319bae745d8atagen2026-09-26 09:59 UTC371
            }),
a319bae745d8atagen2026-09-26 09:59 UTC372
            Event::VerificationChanged(verification) => self.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC373
                state.verification = Some(verification.clone());
a319bae745d8atagen2026-09-26 09:59 UTC374
                Some(StateChange::Verification(verification))
a319bae745d8atagen2026-09-26 09:59 UTC375
            }),
a319bae745d8atagen2026-09-26 09:59 UTC376
            Event::VerificationTargetChanged(target) => self.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC377
                state.verification_target = Some(target.clone());
a319bae745d8atagen2026-09-26 09:59 UTC378
                Some(StateChange::VerificationTarget(target))
a319bae745d8atagen2026-09-26 09:59 UTC379
            }),
a319bae745d8atagen2026-09-26 09:59 UTC380
            Event::TimelineReset { timeline_id, items } => {
a319bae745d8atagen2026-09-26 09:59 UTC381
                self.cancel_resync(&timeline_id);
a319bae745d8atagen2026-09-26 09:59 UTC382
                self.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC383
                    let history = state
a319bae745d8atagen2026-09-26 09:59 UTC384
                        .timelines
a319bae745d8atagen2026-09-26 09:59 UTC385
                        .get(&timeline_id)
a319bae745d8atagen2026-09-26 09:59 UTC386
                        .map_or(HistoryState::Idle, |timeline| timeline.history.clone());
a319bae745d8atagen2026-09-26 09:59 UTC387
                    let timeline = TimelineState {
a319bae745d8atagen2026-09-26 09:59 UTC388
                        items: items.clone(),
a319bae745d8atagen2026-09-26 09:59 UTC389
                        history,
a319bae745d8atagen2026-09-26 09:59 UTC390
                        diverged: false,
a319bae745d8atagen2026-09-26 09:59 UTC391
                    };
a319bae745d8atagen2026-09-26 09:59 UTC392
                    state.timelines.insert(timeline_id.clone(), timeline);
a319bae745d8atagen2026-09-26 09:59 UTC393
                    Some(StateChange::Timeline {
a319bae745d8atagen2026-09-26 09:59 UTC394
                        timeline_id,
a319bae745d8atagen2026-09-26 09:59 UTC395
                        change: TimelineChange::Reset { items },
a319bae745d8atagen2026-09-26 09:59 UTC396
                    })
a319bae745d8atagen2026-09-26 09:59 UTC397
                });
a319bae745d8atagen2026-09-26 09:59 UTC398
            }
a319bae745d8atagen2026-09-26 09:59 UTC399
            Event::TimelineChanged { timeline_id, diffs } => self.apply_diffs(timeline_id, diffs),
a319bae745d8atagen2026-09-26 09:59 UTC400
            Event::HistoryChanged {
a319bae745d8atagen2026-09-26 09:59 UTC401
                timeline_id,
a319bae745d8atagen2026-09-26 09:59 UTC402
                state: history,
a319bae745d8atagen2026-09-26 09:59 UTC403
            } => self.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC404
                state.timelines.get_mut(&timeline_id)?.history = history.clone();
a319bae745d8atagen2026-09-26 09:59 UTC405
                Some(StateChange::History {
a319bae745d8atagen2026-09-26 09:59 UTC406
                    timeline_id,
a319bae745d8atagen2026-09-26 09:59 UTC407
                    state: history,
a319bae745d8atagen2026-09-26 09:59 UTC408
                })
a319bae745d8atagen2026-09-26 09:59 UTC409
            }),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC410
            Event::RecentHistoryChanged {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC411
                room_id,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC412
                state: recent,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC413
            } => self.publish(|state| {
d9b96952ec60atagen2026-10-25 09:07 UTC414
                if recent == RecentHistoryState::Idle {
d9b96952ec60atagen2026-10-25 09:07 UTC415
                    state.recent_history.remove(&room_id);
d9b96952ec60atagen2026-10-25 09:07 UTC416
                } else {
d9b96952ec60atagen2026-10-25 09:07 UTC417
                    state.recent_history.insert(room_id.clone(), recent.clone());
d9b96952ec60atagen2026-10-25 09:07 UTC418
                }
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC419
                Some(StateChange::RecentHistory {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC420
                    room_id,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC421
                    state: recent,
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC422
                })
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC423
            }),
80381e06a573atagen2026-10-25 03:02 UTC424
            Event::HistoryRepairChanged(report) => self.publish(|state| {
80381e06a573atagen2026-10-25 03:02 UTC425
                state.repairs.insert(report.room_id.clone(), report.clone());
80381e06a573atagen2026-10-25 03:02 UTC426
                Some(StateChange::HistoryRepair(report))
80381e06a573atagen2026-10-25 03:02 UTC427
            }),
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC428
            Event::PublicationBarrier { acknowledgement } => {
c9f0fd0ee1ccatagen2026-10-25 06:16 UTC429
                if let Some(acknowledgement) = acknowledgement {
80381e06a573atagen2026-10-25 03:02 UTC430
                    acknowledgement.complete(true);
80381e06a573atagen2026-10-25 03:02 UTC431
                }
80381e06a573atagen2026-10-25 03:02 UTC432
            }
a319bae745d8atagen2026-09-26 09:59 UTC433
            Event::TimelineClosed { timeline_id } => {
a319bae745d8atagen2026-09-26 09:59 UTC434
                self.cancel_resync(&timeline_id);
a319bae745d8atagen2026-09-26 09:59 UTC435
                self.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC436
                    state.timelines.remove(&timeline_id)?;
d9b96952ec60atagen2026-10-25 09:07 UTC437
                    if let TimelineId::Room(room_id) = &timeline_id {
d9b96952ec60atagen2026-10-25 09:07 UTC438
                        state.repairs.remove(room_id);
d9b96952ec60atagen2026-10-25 09:07 UTC439
                    }
a319bae745d8atagen2026-09-26 09:59 UTC440
                    Some(StateChange::Timeline {
a319bae745d8atagen2026-09-26 09:59 UTC441
                        timeline_id,
a319bae745d8atagen2026-09-26 09:59 UTC442
                        change: TimelineChange::Closed,
a319bae745d8atagen2026-09-26 09:59 UTC443
                    })
a319bae745d8atagen2026-09-26 09:59 UTC444
                });
a319bae745d8atagen2026-09-26 09:59 UTC445
            }
1f875b103b27atagen2026-10-25 10:44 UTC446
            Event::EventContextClosed { context_id, reason } => {
1f875b103b27atagen2026-10-25 10:44 UTC447
                self.publish(|_| Some(StateChange::EventContextClosed { context_id, reason }));
1f875b103b27atagen2026-10-25 10:44 UTC448
            }
74befbc72bafatagen2026-10-25 11:18 UTC449
            Event::SearchClosed { search_id, reason } => {
74befbc72bafatagen2026-10-25 11:18 UTC450
                self.publish(|_| Some(StateChange::SearchClosed { search_id, reason }));
74befbc72bafatagen2026-10-25 11:18 UTC451
            }
66db1b3cd895atagen2026-10-25 10:28 UTC452
            Event::ThreadListReset { room_id, entries } => self.publish(|state| {
66db1b3cd895atagen2026-10-25 10:28 UTC453
                let history = state
66db1b3cd895atagen2026-10-25 10:28 UTC454
                    .thread_lists
66db1b3cd895atagen2026-10-25 10:28 UTC455
                    .get(&room_id)
66db1b3cd895atagen2026-10-25 10:28 UTC456
                    .map_or(HistoryState::Idle, |list| list.history.clone());
66db1b3cd895atagen2026-10-25 10:28 UTC457
                state.thread_lists.insert(
66db1b3cd895atagen2026-10-25 10:28 UTC458
                    room_id.clone(),
66db1b3cd895atagen2026-10-25 10:28 UTC459
                    ThreadListState {
66db1b3cd895atagen2026-10-25 10:28 UTC460
                        entries: entries.clone(),
66db1b3cd895atagen2026-10-25 10:28 UTC461
                        history,
66db1b3cd895atagen2026-10-25 10:28 UTC462
                    },
66db1b3cd895atagen2026-10-25 10:28 UTC463
                );
66db1b3cd895atagen2026-10-25 10:28 UTC464
                Some(StateChange::ThreadList {
66db1b3cd895atagen2026-10-25 10:28 UTC465
                    room_id,
66db1b3cd895atagen2026-10-25 10:28 UTC466
                    change: ThreadListChange::Reset { entries },
66db1b3cd895atagen2026-10-25 10:28 UTC467
                })
66db1b3cd895atagen2026-10-25 10:28 UTC468
            }),
66db1b3cd895atagen2026-10-25 10:28 UTC469
            Event::ThreadListChanged { room_id, upserts } => self.publish(|state| {
66db1b3cd895atagen2026-10-25 10:28 UTC470
                let list = state.thread_lists.get_mut(&room_id)?;
66db1b3cd895atagen2026-10-25 10:28 UTC471
                for entry in &upserts {
66db1b3cd895atagen2026-10-25 10:28 UTC472
                    ThreadListEntry::upsert(&mut list.entries, entry.clone());
66db1b3cd895atagen2026-10-25 10:28 UTC473
                }
66db1b3cd895atagen2026-10-25 10:28 UTC474
                Some(StateChange::ThreadList {
66db1b3cd895atagen2026-10-25 10:28 UTC475
                    room_id,
66db1b3cd895atagen2026-10-25 10:28 UTC476
                    change: ThreadListChange::Changed {
66db1b3cd895atagen2026-10-25 10:28 UTC477
                        upserts: upserts.into(),
66db1b3cd895atagen2026-10-25 10:28 UTC478
                        entries: list.entries.clone(),
66db1b3cd895atagen2026-10-25 10:28 UTC479
                    },
66db1b3cd895atagen2026-10-25 10:28 UTC480
                })
66db1b3cd895atagen2026-10-25 10:28 UTC481
            }),
66db1b3cd895atagen2026-10-25 10:28 UTC482
            Event::ThreadListHistoryChanged {
66db1b3cd895atagen2026-10-25 10:28 UTC483
                room_id,
66db1b3cd895atagen2026-10-25 10:28 UTC484
                state: history,
66db1b3cd895atagen2026-10-25 10:28 UTC485
            } => self.publish(|state| {
66db1b3cd895atagen2026-10-25 10:28 UTC486
                state.thread_lists.get_mut(&room_id)?.history = history.clone();
66db1b3cd895atagen2026-10-25 10:28 UTC487
                Some(StateChange::ThreadList {
66db1b3cd895atagen2026-10-25 10:28 UTC488
                    room_id,
66db1b3cd895atagen2026-10-25 10:28 UTC489
                    change: ThreadListChange::History(history),
66db1b3cd895atagen2026-10-25 10:28 UTC490
                })
66db1b3cd895atagen2026-10-25 10:28 UTC491
            }),
21b9227729a4atagen2026-10-25 11:03 UTC492
            Event::ThreadListClosed { room_id, reason } => self.publish(|state| {
66db1b3cd895atagen2026-10-25 10:28 UTC493
                state.thread_lists.remove(&room_id)?;
66db1b3cd895atagen2026-10-25 10:28 UTC494
                Some(StateChange::ThreadList {
66db1b3cd895atagen2026-10-25 10:28 UTC495
                    room_id,
21b9227729a4atagen2026-10-25 11:03 UTC496
                    change: ThreadListChange::Closed(reason),
66db1b3cd895atagen2026-10-25 10:28 UTC497
                })
66db1b3cd895atagen2026-10-25 10:28 UTC498
            }),
a319bae745d8atagen2026-09-26 09:59 UTC499
            Event::TypingChanged(typing) => self.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC500
                if typing.users.is_empty() {
a319bae745d8atagen2026-09-26 09:59 UTC501
                    state.typing.remove(&typing.room_id);
a319bae745d8atagen2026-09-26 09:59 UTC502
                } else {
a319bae745d8atagen2026-09-26 09:59 UTC503
                    state
a319bae745d8atagen2026-09-26 09:59 UTC504
                        .typing
a319bae745d8atagen2026-09-26 09:59 UTC505
                        .insert(typing.room_id.clone(), typing.users.clone());
a319bae745d8atagen2026-09-26 09:59 UTC506
                }
a319bae745d8atagen2026-09-26 09:59 UTC507
                Some(StateChange::Typing(typing))
a319bae745d8atagen2026-09-26 09:59 UTC508
            }),
a319bae745d8atagen2026-09-26 09:59 UTC509
            Event::MediaUploadProgress {
a319bae745d8atagen2026-09-26 09:59 UTC510
                room_id,
a319bae745d8atagen2026-09-26 09:59 UTC511
                transaction_id,
a319bae745d8atagen2026-09-26 09:59 UTC512
                uploaded_bytes,
a319bae745d8atagen2026-09-26 09:59 UTC513
                total_bytes,
a319bae745d8atagen2026-09-26 09:59 UTC514
                complete,
a319bae745d8atagen2026-09-26 09:59 UTC515
            } => self.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC516
                let upload = UploadState {
a319bae745d8atagen2026-09-26 09:59 UTC517
                    room_id,
a319bae745d8atagen2026-09-26 09:59 UTC518
                    uploaded_bytes,
a319bae745d8atagen2026-09-26 09:59 UTC519
                    total_bytes,
a319bae745d8atagen2026-09-26 09:59 UTC520
                    complete,
a319bae745d8atagen2026-09-26 09:59 UTC521
                    failure: None,
a319bae745d8atagen2026-09-26 09:59 UTC522
                };
a319bae745d8atagen2026-09-26 09:59 UTC523
                state.uploads.insert(transaction_id.clone(), upload.clone());
a319bae745d8atagen2026-09-26 09:59 UTC524
                Some(StateChange::Upload {
a319bae745d8atagen2026-09-26 09:59 UTC525
                    transaction_id,
a319bae745d8atagen2026-09-26 09:59 UTC526
                    upload,
a319bae745d8atagen2026-09-26 09:59 UTC527
                })
a319bae745d8atagen2026-09-26 09:59 UTC528
            }),
a319bae745d8atagen2026-09-26 09:59 UTC529
            Event::MediaUploadFailed {
a319bae745d8atagen2026-09-26 09:59 UTC530
                room_id,
a319bae745d8atagen2026-09-26 09:59 UTC531
                transaction_id,
a319bae745d8atagen2026-09-26 09:59 UTC532
                kind,
a319bae745d8atagen2026-09-26 09:59 UTC533
                will_retry,
a319bae745d8atagen2026-09-26 09:59 UTC534
            } => self.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC535
                let upload = state
a319bae745d8atagen2026-09-26 09:59 UTC536
                    .uploads
a319bae745d8atagen2026-09-26 09:59 UTC537
                    .entry(transaction_id.clone())
a319bae745d8atagen2026-09-26 09:59 UTC538
                    .or_insert_with(|| UploadState {
a319bae745d8atagen2026-09-26 09:59 UTC539
                        room_id,
a319bae745d8atagen2026-09-26 09:59 UTC540
                        uploaded_bytes: 0,
a319bae745d8atagen2026-09-26 09:59 UTC541
                        total_bytes: 0,
a319bae745d8atagen2026-09-26 09:59 UTC542
                        complete: false,
a319bae745d8atagen2026-09-26 09:59 UTC543
                        failure: None,
a319bae745d8atagen2026-09-26 09:59 UTC544
                    });
a319bae745d8atagen2026-09-26 09:59 UTC545
                upload.failure = Some(UploadFailure { kind, will_retry });
a319bae745d8atagen2026-09-26 09:59 UTC546
                Some(StateChange::Upload {
a319bae745d8atagen2026-09-26 09:59 UTC547
                    transaction_id,
a319bae745d8atagen2026-09-26 09:59 UTC548
                    upload: upload.clone(),
a319bae745d8atagen2026-09-26 09:59 UTC549
                })
a319bae745d8atagen2026-09-26 09:59 UTC550
            }),
a319bae745d8atagen2026-09-26 09:59 UTC551
            Event::MediaUploadFinished {
a319bae745d8atagen2026-09-26 09:59 UTC552
                room_id,
a319bae745d8atagen2026-09-26 09:59 UTC553
                transaction_id,
a319bae745d8atagen2026-09-26 09:59 UTC554
                outcome,
a319bae745d8atagen2026-09-26 09:59 UTC555
            } => self.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC556
                state.uploads.remove(&transaction_id);
a319bae745d8atagen2026-09-26 09:59 UTC557
                Some(StateChange::UploadFinished {
a319bae745d8atagen2026-09-26 09:59 UTC558
                    room_id,
a319bae745d8atagen2026-09-26 09:59 UTC559
                    transaction_id,
a319bae745d8atagen2026-09-26 09:59 UTC560
                    outcome,
a319bae745d8atagen2026-09-26 09:59 UTC561
                })
a319bae745d8atagen2026-09-26 09:59 UTC562
            }),
a319bae745d8atagen2026-09-26 09:59 UTC563
        }
a319bae745d8atagen2026-09-26 09:59 UTC564
    }
a319bae745d8atagen2026-09-26 09:59 UTC565
a319bae745d8atagen2026-09-26 09:59 UTC566
    fn apply_diffs(&mut self, timeline_id: TimelineId, diffs: Vec<TimelineDiff>) {
a319bae745d8atagen2026-09-26 09:59 UTC567
        let mut diverged = false;
a319bae745d8atagen2026-09-26 09:59 UTC568
        self.publish(|state| {
0b8ce8dc9a1catagen2026-09-26 12:30 UTC569
            let Some(timeline) = state.timelines.get_mut(&timeline_id) else {
0b8ce8dc9a1catagen2026-09-26 12:30 UTC570
                diverged = true;
0b8ce8dc9a1catagen2026-09-26 12:30 UTC571
                state.timelines.insert(
0b8ce8dc9a1catagen2026-09-26 12:30 UTC572
                    timeline_id.clone(),
0b8ce8dc9a1catagen2026-09-26 12:30 UTC573
                    TimelineState {
0b8ce8dc9a1catagen2026-09-26 12:30 UTC574
                        items: Vector::new(),
0b8ce8dc9a1catagen2026-09-26 12:30 UTC575
                        history: HistoryState::Idle,
0b8ce8dc9a1catagen2026-09-26 12:30 UTC576
                        diverged: true,
0b8ce8dc9a1catagen2026-09-26 12:30 UTC577
                    },
0b8ce8dc9a1catagen2026-09-26 12:30 UTC578
                );
0b8ce8dc9a1catagen2026-09-26 12:30 UTC579
                return Some(StateChange::Timeline {
0b8ce8dc9a1catagen2026-09-26 12:30 UTC580
                    timeline_id: timeline_id.clone(),
0b8ce8dc9a1catagen2026-09-26 12:30 UTC581
                    change: TimelineChange::Diverged,
0b8ce8dc9a1catagen2026-09-26 12:30 UTC582
                });
0b8ce8dc9a1catagen2026-09-26 12:30 UTC583
            };
a319bae745d8atagen2026-09-26 09:59 UTC584
            if timeline.diverged {
a319bae745d8atagen2026-09-26 09:59 UTC585
                return None;
a319bae745d8atagen2026-09-26 09:59 UTC586
            }
a319bae745d8atagen2026-09-26 09:59 UTC587
            let diffs: Arc<[TimelineDiff]> = diffs.into();
a319bae745d8atagen2026-09-26 09:59 UTC588
            let change = match TimelineDiff::try_apply_all(diffs.to_vec(), &mut timeline.items) {
a319bae745d8atagen2026-09-26 09:59 UTC589
                Ok(()) => TimelineChange::Changed {
a319bae745d8atagen2026-09-26 09:59 UTC590
                    diffs,
a319bae745d8atagen2026-09-26 09:59 UTC591
                    items: timeline.items.clone(),
a319bae745d8atagen2026-09-26 09:59 UTC592
                },
a319bae745d8atagen2026-09-26 09:59 UTC593
                Err(error) => {
a319bae745d8atagen2026-09-26 09:59 UTC594
                    tracing::debug!(?timeline_id, length = error.length, "timeline diverged");
a319bae745d8atagen2026-09-26 09:59 UTC595
                    timeline.diverged = true;
a319bae745d8atagen2026-09-26 09:59 UTC596
                    diverged = true;
a319bae745d8atagen2026-09-26 09:59 UTC597
                    TimelineChange::Diverged
a319bae745d8atagen2026-09-26 09:59 UTC598
                }
a319bae745d8atagen2026-09-26 09:59 UTC599
            };
a319bae745d8atagen2026-09-26 09:59 UTC600
            Some(StateChange::Timeline {
a319bae745d8atagen2026-09-26 09:59 UTC601
                timeline_id: timeline_id.clone(),
a319bae745d8atagen2026-09-26 09:59 UTC602
                change,
a319bae745d8atagen2026-09-26 09:59 UTC603
            })
a319bae745d8atagen2026-09-26 09:59 UTC604
        });
a319bae745d8atagen2026-09-26 09:59 UTC605
        if diverged {
a319bae745d8atagen2026-09-26 09:59 UTC606
            let task = spawn(resync(self.handle.clone(), timeline_id.clone()));
a319bae745d8atagen2026-09-26 09:59 UTC607
            self.resyncs.insert(timeline_id, task.abort_handle());
a319bae745d8atagen2026-09-26 09:59 UTC608
        }
a319bae745d8atagen2026-09-26 09:59 UTC609
    }
a319bae745d8atagen2026-09-26 09:59 UTC610
a319bae745d8atagen2026-09-26 09:59 UTC611
    fn refetch_rooms(&mut self) {
17e042fbc331atagen2026-10-08 12:39 UTC612
        let fetch = fetch_rooms(self.handle.clone(), self.shared.clone());
17e042fbc331atagen2026-10-08 12:39 UTC613
        if let Some(task) = self
17e042fbc331atagen2026-10-08 12:39 UTC614
            .shared
17e042fbc331atagen2026-10-08 12:39 UTC615
            .start(|published| &mut published.rooms_fetch, fetch)
17e042fbc331atagen2026-10-08 12:39 UTC616
        {
17e042fbc331atagen2026-10-08 12:39 UTC617
            self.rooms_fetch = Some(task);
17e042fbc331atagen2026-10-08 12:39 UTC618
        }
17e042fbc331atagen2026-10-08 12:39 UTC619
    }
17e042fbc331atagen2026-10-08 12:39 UTC620
17e042fbc331atagen2026-10-08 12:39 UTC621
    fn refetch_image_packs(&mut self) {
17e042fbc331atagen2026-10-08 12:39 UTC622
        let fetch = fetch_image_packs(self.handle.clone(), self.shared.clone());
17e042fbc331atagen2026-10-08 12:39 UTC623
        if let Some(task) = self
17e042fbc331atagen2026-10-08 12:39 UTC624
            .shared
17e042fbc331atagen2026-10-08 12:39 UTC625
            .start(|published| &mut published.packs_fetch, fetch)
17e042fbc331atagen2026-10-08 12:39 UTC626
        {
17e042fbc331atagen2026-10-08 12:39 UTC627
            self.packs_fetch = Some(task);
a319bae745d8atagen2026-09-26 09:59 UTC628
        }
a319bae745d8atagen2026-09-26 09:59 UTC629
    }
a319bae745d8atagen2026-09-26 09:59 UTC630
a319bae745d8atagen2026-09-26 09:59 UTC631
    fn cancel_resync(&mut self, timeline_id: &TimelineId) {
a319bae745d8atagen2026-09-26 09:59 UTC632
        if let Some(task) = self.resyncs.remove(timeline_id) {
a319bae745d8atagen2026-09-26 09:59 UTC633
            task.abort();
a319bae745d8atagen2026-09-26 09:59 UTC634
        }
a319bae745d8atagen2026-09-26 09:59 UTC635
    }
a319bae745d8atagen2026-09-26 09:59 UTC636
a319bae745d8atagen2026-09-26 09:59 UTC637
    fn cancel_resyncs(&mut self) {
a319bae745d8atagen2026-09-26 09:59 UTC638
        for (_, task) in self.resyncs.drain() {
a319bae745d8atagen2026-09-26 09:59 UTC639
            task.abort();
a319bae745d8atagen2026-09-26 09:59 UTC640
        }
a319bae745d8atagen2026-09-26 09:59 UTC641
    }
a319bae745d8atagen2026-09-26 09:59 UTC642
a319bae745d8atagen2026-09-26 09:59 UTC643
    fn publish(&self, change: impl FnOnce(&mut State) -> Option<StateChange>) {
a319bae745d8atagen2026-09-26 09:59 UTC644
        self.shared.publish(change);
a319bae745d8atagen2026-09-26 09:59 UTC645
    }
a319bae745d8atagen2026-09-26 09:59 UTC646
}
a319bae745d8atagen2026-09-26 09:59 UTC647
a319bae745d8atagen2026-09-26 09:59 UTC648
impl Drop for Mirror {
a319bae745d8atagen2026-09-26 09:59 UTC649
    fn drop(&mut self) {
a319bae745d8atagen2026-09-26 09:59 UTC650
        self.cancel_resyncs();
17e042fbc331atagen2026-10-08 12:39 UTC651
        for task in [self.rooms_fetch.take(), self.packs_fetch.take()]
17e042fbc331atagen2026-10-08 12:39 UTC652
            .into_iter()
17e042fbc331atagen2026-10-08 12:39 UTC653
            .flatten()
17e042fbc331atagen2026-10-08 12:39 UTC654
        {
a319bae745d8atagen2026-09-26 09:59 UTC655
            task.abort();
a319bae745d8atagen2026-09-26 09:59 UTC656
        }
a319bae745d8atagen2026-09-26 09:59 UTC657
        self.shared.lock().updates = None;
a319bae745d8atagen2026-09-26 09:59 UTC658
    }
a319bae745d8atagen2026-09-26 09:59 UTC659
}
a319bae745d8atagen2026-09-26 09:59 UTC660
a319bae745d8atagen2026-09-26 09:59 UTC661
/// asks until the engine answers, since a busy room or a full queue is only ever for now.
a319bae745d8atagen2026-09-26 09:59 UTC662
/// a reset or a close for the timeline aborts it, and the engine sends a reset before it answers
a319bae745d8atagen2026-09-26 09:59 UTC663
async fn resync(handle: ClientHandle, timeline_id: TimelineId) {
a319bae745d8atagen2026-09-26 09:59 UTC664
    let mut backoff = RESYNC_BACKOFF;
a319bae745d8atagen2026-09-26 09:59 UTC665
    loop {
a319bae745d8atagen2026-09-26 09:59 UTC666
        let command = Command::Timeline(TimelineCommand::Resync {
a319bae745d8atagen2026-09-26 09:59 UTC667
            timeline_id: timeline_id.clone(),
a319bae745d8atagen2026-09-26 09:59 UTC668
        });
a319bae745d8atagen2026-09-26 09:59 UTC669
        match handle.execute(command).await {
a319bae745d8atagen2026-09-26 09:59 UTC670
            Ok(_) => return,
a319bae745d8atagen2026-09-26 09:59 UTC671
            Err(error) if error.kind == ErrorKind::Interrupted => return,
a319bae745d8atagen2026-09-26 09:59 UTC672
            Err(error) => {
a319bae745d8atagen2026-09-26 09:59 UTC673
                tracing::debug!(%error, ?timeline_id, "resync refused, trying again");
a319bae745d8atagen2026-09-26 09:59 UTC674
                matrix_sdk::sleep::sleep(backoff).await;
a319bae745d8atagen2026-09-26 09:59 UTC675
                backoff = (backoff * 2).min(RESYNC_BACKOFF_CAP);
a319bae745d8atagen2026-09-26 09:59 UTC676
            }
a319bae745d8atagen2026-09-26 09:59 UTC677
        }
a319bae745d8atagen2026-09-26 09:59 UTC678
    }
a319bae745d8atagen2026-09-26 09:59 UTC679
}
a319bae745d8atagen2026-09-26 09:59 UTC680
a319bae745d8atagen2026-09-26 09:59 UTC681
async fn fetch_rooms(handle: ClientHandle, shared: Arc<Shared>) {
a319bae745d8atagen2026-09-26 09:59 UTC682
    loop {
a319bae745d8atagen2026-09-26 09:59 UTC683
        let rooms: Vector<RoomSummary> = handle.rooms().await.into();
a319bae745d8atagen2026-09-26 09:59 UTC684
        shared.publish(|state| {
a319bae745d8atagen2026-09-26 09:59 UTC685
            state.rooms = rooms.clone();
a319bae745d8atagen2026-09-26 09:59 UTC686
            Some(StateChange::Rooms(rooms))
a319bae745d8atagen2026-09-26 09:59 UTC687
        });
17e042fbc331atagen2026-10-08 12:39 UTC688
        if !shared.fetched(|published| &mut published.rooms_fetch) {
17e042fbc331atagen2026-10-08 12:39 UTC689
            return;
17e042fbc331atagen2026-10-08 12:39 UTC690
        }
17e042fbc331atagen2026-10-08 12:39 UTC691
    }
17e042fbc331atagen2026-10-08 12:39 UTC692
}
17e042fbc331atagen2026-10-08 12:39 UTC693
17e042fbc331atagen2026-10-08 12:39 UTC694
/// a failed read keeps the packs from before, the next change asks again
17e042fbc331atagen2026-10-08 12:39 UTC695
async fn fetch_image_packs(handle: ClientHandle, shared: Arc<Shared>) {
17e042fbc331atagen2026-10-08 12:39 UTC696
    loop {
17e042fbc331atagen2026-10-08 12:39 UTC697
        match handle.image_packs().await {
17e042fbc331atagen2026-10-08 12:39 UTC698
            Ok(packs) => shared.publish(|state| {
17e042fbc331atagen2026-10-08 12:39 UTC699
                if state.image_packs == packs {
17e042fbc331atagen2026-10-08 12:39 UTC700
                    return None;
17e042fbc331atagen2026-10-08 12:39 UTC701
                }
17e042fbc331atagen2026-10-08 12:39 UTC702
                state.image_packs = packs.clone();
17e042fbc331atagen2026-10-08 12:39 UTC703
                Some(StateChange::ImagePacks(packs))
17e042fbc331atagen2026-10-08 12:39 UTC704
            }),
17e042fbc331atagen2026-10-08 12:39 UTC705
            Err(error) => tracing::warn!(%error, "reading image packs failed"),
17e042fbc331atagen2026-10-08 12:39 UTC706
        }
17e042fbc331atagen2026-10-08 12:39 UTC707
        if !shared.fetched(|published| &mut published.packs_fetch) {
a319bae745d8atagen2026-09-26 09:59 UTC708
            return;
a319bae745d8atagen2026-09-26 09:59 UTC709
        }
a319bae745d8atagen2026-09-26 09:59 UTC710
    }
a319bae745d8atagen2026-09-26 09:59 UTC711
}
a319bae745d8atagen2026-09-26 09:59 UTC712
a319bae745d8atagen2026-09-26 09:59 UTC713
/// cheap to clone, one per frontend
a319bae745d8atagen2026-09-26 09:59 UTC714
#[derive(Clone, Debug)]
a319bae745d8atagen2026-09-26 09:59 UTC715
pub struct MirrorHandle {
a319bae745d8atagen2026-09-26 09:59 UTC716
    shared: Arc<Shared>,
a319bae745d8atagen2026-09-26 09:59 UTC717
}
a319bae745d8atagen2026-09-26 09:59 UTC718
a319bae745d8atagen2026-09-26 09:59 UTC719
impl MirrorHandle {
a319bae745d8atagen2026-09-26 09:59 UTC720
    #[must_use]
a319bae745d8atagen2026-09-26 09:59 UTC721
    pub fn snapshot(&self) -> Arc<State> {
a319bae745d8atagen2026-09-26 09:59 UTC722
        self.shared.lock().state.clone()
a319bae745d8atagen2026-09-26 09:59 UTC723
    }
a319bae745d8atagen2026-09-26 09:59 UTC724
a319bae745d8atagen2026-09-26 09:59 UTC725
    /// opens with the current state, then every update after it. ends once the mirror stops
a319bae745d8atagen2026-09-26 09:59 UTC726
    #[must_use]
a319bae745d8atagen2026-09-26 09:59 UTC727
    pub fn subscribe(&self) -> Subscription {
a319bae745d8atagen2026-09-26 09:59 UTC728
        let (first, receiving) = self.shared.clone().subscribe();
a319bae745d8atagen2026-09-26 09:59 UTC729
        let rest = futures_util::stream::unfold(receiving, |(shared, receiver)| async move {
a319bae745d8atagen2026-09-26 09:59 UTC730
            let mut receiver = receiver?;
a319bae745d8atagen2026-09-26 09:59 UTC731
            match receiver.recv().await {
a319bae745d8atagen2026-09-26 09:59 UTC732
                Ok(update) => Some((update, (shared, Some(receiver)))),
a319bae745d8atagen2026-09-26 09:59 UTC733
                Err(broadcast::error::RecvError::Lagged(missed)) => {
a319bae745d8atagen2026-09-26 09:59 UTC734
                    tracing::debug!(missed, "mirror subscriber lagged, starting it over");
a319bae745d8atagen2026-09-26 09:59 UTC735
                    Some(shared.subscribe())
a319bae745d8atagen2026-09-26 09:59 UTC736
                }
a319bae745d8atagen2026-09-26 09:59 UTC737
                Err(broadcast::error::RecvError::Closed) => None,
a319bae745d8atagen2026-09-26 09:59 UTC738
            }
a319bae745d8atagen2026-09-26 09:59 UTC739
        });
a319bae745d8atagen2026-09-26 09:59 UTC740
        let updates = futures_util::stream::once(std::future::ready(first)).chain(rest);
a319bae745d8atagen2026-09-26 09:59 UTC741
        #[cfg(not(target_family = "wasm"))]
a319bae745d8atagen2026-09-26 09:59 UTC742
        let updates = updates.boxed();
a319bae745d8atagen2026-09-26 09:59 UTC743
        #[cfg(target_family = "wasm")]
a319bae745d8atagen2026-09-26 09:59 UTC744
        let updates = updates.boxed_local();
a319bae745d8atagen2026-09-26 09:59 UTC745
        Subscription(updates)
a319bae745d8atagen2026-09-26 09:59 UTC746
    }
a319bae745d8atagen2026-09-26 09:59 UTC747
}
a319bae745d8atagen2026-09-26 09:59 UTC748
a319bae745d8atagen2026-09-26 09:59 UTC749
pub struct Subscription(EngineBoxStream<Update>);
a319bae745d8atagen2026-09-26 09:59 UTC750
a319bae745d8atagen2026-09-26 09:59 UTC751
impl fmt::Debug for Subscription {
a319bae745d8atagen2026-09-26 09:59 UTC752
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
a319bae745d8atagen2026-09-26 09:59 UTC753
        formatter
a319bae745d8atagen2026-09-26 09:59 UTC754
            .debug_struct("Subscription")
a319bae745d8atagen2026-09-26 09:59 UTC755
            .finish_non_exhaustive()
a319bae745d8atagen2026-09-26 09:59 UTC756
    }
a319bae745d8atagen2026-09-26 09:59 UTC757
}
a319bae745d8atagen2026-09-26 09:59 UTC758
a319bae745d8atagen2026-09-26 09:59 UTC759
impl Stream for Subscription {
a319bae745d8atagen2026-09-26 09:59 UTC760
    type Item = Update;
a319bae745d8atagen2026-09-26 09:59 UTC761
a319bae745d8atagen2026-09-26 09:59 UTC762
    fn poll_next(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<Update>> {
a319bae745d8atagen2026-09-26 09:59 UTC763
        self.0.poll_next_unpin(context)
a319bae745d8atagen2026-09-26 09:59 UTC764
    }
a319bae745d8atagen2026-09-26 09:59 UTC765
}
a319bae745d8atagen2026-09-26 09:59 UTC766
a319bae745d8atagen2026-09-26 09:59 UTC767
#[derive(Debug)]
a319bae745d8atagen2026-09-26 09:59 UTC768
struct Shared {
a319bae745d8atagen2026-09-26 09:59 UTC769
    published: Mutex<Published>,
a319bae745d8atagen2026-09-26 09:59 UTC770
}
a319bae745d8atagen2026-09-26 09:59 UTC771
a319bae745d8atagen2026-09-26 09:59 UTC772
#[derive(Debug)]
a319bae745d8atagen2026-09-26 09:59 UTC773
struct Published {
a319bae745d8atagen2026-09-26 09:59 UTC774
    state: Arc<State>,
a319bae745d8atagen2026-09-26 09:59 UTC775
    sequence: u64,
a319bae745d8atagen2026-09-26 09:59 UTC776
    /// gone once the mirror stops, which ends every subscription
a319bae745d8atagen2026-09-26 09:59 UTC777
    updates: Option<broadcast::Sender<Update>>,
17e042fbc331atagen2026-10-08 12:39 UTC778
    rooms_fetch: Fetch,
17e042fbc331atagen2026-10-08 12:39 UTC779
    packs_fetch: Fetch,
a319bae745d8atagen2026-09-26 09:59 UTC780
}
a319bae745d8atagen2026-09-26 09:59 UTC781
a319bae745d8atagen2026-09-26 09:59 UTC782
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17e042fbc331atagen2026-10-08 12:39 UTC783
enum Fetch {
a319bae745d8atagen2026-09-26 09:59 UTC784
    Idle,
a319bae745d8atagen2026-09-26 09:59 UTC785
    Running,
a319bae745d8atagen2026-09-26 09:59 UTC786
    Again,
a319bae745d8atagen2026-09-26 09:59 UTC787
}
a319bae745d8atagen2026-09-26 09:59 UTC788
a319bae745d8atagen2026-09-26 09:59 UTC789
impl Shared {
a319bae745d8atagen2026-09-26 09:59 UTC790
    fn lock(&self) -> MutexGuard<'_, Published> {
a319bae745d8atagen2026-09-26 09:59 UTC791
        self.published
a319bae745d8atagen2026-09-26 09:59 UTC792
            .lock()
a319bae745d8atagen2026-09-26 09:59 UTC793
            .unwrap_or_else(PoisonError::into_inner)
a319bae745d8atagen2026-09-26 09:59 UTC794
    }
a319bae745d8atagen2026-09-26 09:59 UTC795
17e042fbc331atagen2026-10-08 12:39 UTC796
    /// one fetch at a time, with at most one more owed
17e042fbc331atagen2026-10-08 12:39 UTC797
    fn start(
17e042fbc331atagen2026-10-08 12:39 UTC798
        &self,
17e042fbc331atagen2026-10-08 12:39 UTC799
        fetch: impl FnOnce(&mut Published) -> &mut Fetch,
17e042fbc331atagen2026-10-08 12:39 UTC800
        task: impl Future<Output = ()> + SendOutsideWasm + 'static,
17e042fbc331atagen2026-10-08 12:39 UTC801
    ) -> Option<AbortHandle> {
17e042fbc331atagen2026-10-08 12:39 UTC802
        let mut published = self.lock();
17e042fbc331atagen2026-10-08 12:39 UTC803
        let fetch = fetch(&mut published);
17e042fbc331atagen2026-10-08 12:39 UTC804
        match *fetch {
17e042fbc331atagen2026-10-08 12:39 UTC805
            Fetch::Idle => {
17e042fbc331atagen2026-10-08 12:39 UTC806
                *fetch = Fetch::Running;
17e042fbc331atagen2026-10-08 12:39 UTC807
                drop(published);
17e042fbc331atagen2026-10-08 12:39 UTC808
                Some(spawn(task).abort_handle())
17e042fbc331atagen2026-10-08 12:39 UTC809
            }
17e042fbc331atagen2026-10-08 12:39 UTC810
            Fetch::Running => {
17e042fbc331atagen2026-10-08 12:39 UTC811
                *fetch = Fetch::Again;
17e042fbc331atagen2026-10-08 12:39 UTC812
                None
17e042fbc331atagen2026-10-08 12:39 UTC813
            }
17e042fbc331atagen2026-10-08 12:39 UTC814
            Fetch::Again => None,
17e042fbc331atagen2026-10-08 12:39 UTC815
        }
17e042fbc331atagen2026-10-08 12:39 UTC816
    }
17e042fbc331atagen2026-10-08 12:39 UTC817
17e042fbc331atagen2026-10-08 12:39 UTC818
    /// whether another fetch was asked for while this one ran
17e042fbc331atagen2026-10-08 12:39 UTC819
    fn fetched(&self, fetch: impl FnOnce(&mut Published) -> &mut Fetch) -> bool {
17e042fbc331atagen2026-10-08 12:39 UTC820
        let mut published = self.lock();
17e042fbc331atagen2026-10-08 12:39 UTC821
        let fetch = fetch(&mut published);
17e042fbc331atagen2026-10-08 12:39 UTC822
        if *fetch == Fetch::Again {
17e042fbc331atagen2026-10-08 12:39 UTC823
            *fetch = Fetch::Running;
17e042fbc331atagen2026-10-08 12:39 UTC824
            return true;
17e042fbc331atagen2026-10-08 12:39 UTC825
        }
17e042fbc331atagen2026-10-08 12:39 UTC826
        *fetch = Fetch::Idle;
17e042fbc331atagen2026-10-08 12:39 UTC827
        false
17e042fbc331atagen2026-10-08 12:39 UTC828
    }
17e042fbc331atagen2026-10-08 12:39 UTC829
a319bae745d8atagen2026-09-26 09:59 UTC830
    /// state, sequence and send move together under the one lock, which is what lets
a319bae745d8atagen2026-09-26 09:59 UTC831
    /// `subscribe` take a snapshot and a receiver with nothing between them
a319bae745d8atagen2026-09-26 09:59 UTC832
    fn publish(&self, change: impl FnOnce(&mut State) -> Option<StateChange>) {
a319bae745d8atagen2026-09-26 09:59 UTC833
        let mut published = self.lock();
a319bae745d8atagen2026-09-26 09:59 UTC834
        let Some(change) = change(Arc::make_mut(&mut published.state)) else {
a319bae745d8atagen2026-09-26 09:59 UTC835
            return;
a319bae745d8atagen2026-09-26 09:59 UTC836
        };
a319bae745d8atagen2026-09-26 09:59 UTC837
        published.sequence += 1;
a319bae745d8atagen2026-09-26 09:59 UTC838
        let update = Update {
a319bae745d8atagen2026-09-26 09:59 UTC839
            sequence: published.sequence,
a319bae745d8atagen2026-09-26 09:59 UTC840
            change,
a319bae745d8atagen2026-09-26 09:59 UTC841
        };
a319bae745d8atagen2026-09-26 09:59 UTC842
        if let Some(updates) = &published.updates {
a319bae745d8atagen2026-09-26 09:59 UTC843
            let _ = updates.send(update);
a319bae745d8atagen2026-09-26 09:59 UTC844
        }
a319bae745d8atagen2026-09-26 09:59 UTC845
    }
a319bae745d8atagen2026-09-26 09:59 UTC846
a319bae745d8atagen2026-09-26 09:59 UTC847
    fn subscribe(self: Arc<Self>) -> (Update, (Arc<Self>, Option<broadcast::Receiver<Update>>)) {
a319bae745d8atagen2026-09-26 09:59 UTC848
        let published = self.lock();
a319bae745d8atagen2026-09-26 09:59 UTC849
        let update = Update {
a319bae745d8atagen2026-09-26 09:59 UTC850
            sequence: published.sequence,
a319bae745d8atagen2026-09-26 09:59 UTC851
            change: StateChange::State(published.state.clone()),
a319bae745d8atagen2026-09-26 09:59 UTC852
        };
a319bae745d8atagen2026-09-26 09:59 UTC853
        let receiver = published.updates.as_ref().map(broadcast::Sender::subscribe);
a319bae745d8atagen2026-09-26 09:59 UTC854
        drop(published);
a319bae745d8atagen2026-09-26 09:59 UTC855
        (update, (self, receiver))
a319bae745d8atagen2026-09-26 09:59 UTC856
    }
a319bae745d8atagen2026-09-26 09:59 UTC857
}