atagen

macltok

public
Star0
Watch0
1f875b103b27dcb7dd14d98b1744fee5fcd7e399
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
src/mirror.rsRust775 lines26.6 KB
1
use std::{
2
    collections::HashMap,
3
    fmt,
4
    pin::Pin,
5
    sync::{Arc, Mutex, MutexGuard, PoisonError},
6
    task::{Context, Poll},
7
    time::Duration,
8
};
9
10
use futures_util::{Stream, StreamExt};
11
use imbl::Vector;
12
use matrix_sdk::{
13
    SendOutsideWasm,
14
    executor::{AbortHandle, spawn},
15
};
16
use ruma::{OwnedDeviceId, OwnedRoomId, OwnedTransactionId, OwnedUserId};
17
#[cfg(feature = "serde")]
18
use serde::{Deserialize, Serialize};
19
use tokio::sync::broadcast;
20
21
use crate::{
22
    ClientHandle, Command, ConnectionState, EncryptionState, EngineFailure, ErrorKind, Event,
23
    EventContextCloseReason, HistoryRepairReport, HistoryState, ImagePacks, RecentHistoryState, RoomSummary,
24
    TimelineCommand, TimelineDiff, TimelineId, TimelineItem, TypingState, UploadOutcome,
25
    VerificationState, VerificationTarget, engine::executor::EngineBoxFuture,
26
    transport::PublicationRegistration,
27
};
28
29
#[cfg(not(target_family = "wasm"))]
30
type EngineBoxStream<T> = futures_util::stream::BoxStream<'static, T>;
31
#[cfg(target_family = "wasm")]
32
type EngineBoxStream<T> = futures_util::stream::LocalBoxStream<'static, T>;
33
34
const DEFAULT_CAPACITY: usize = 256;
35
const RESYNC_BACKOFF: Duration = Duration::from_millis(100);
36
const RESYNC_BACKOFF_CAP: Duration = Duration::from_secs(10);
37
38
/// everything the engine has said so far, folded together
39
#[derive(Clone, Debug, PartialEq)]
40
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
41
#[non_exhaustive]
42
pub struct State {
43
    pub user_id: Option<OwnedUserId>,
44
    pub session: SessionStatus,
45
    pub connection: ConnectionState,
46
    pub encryption: Option<EncryptionState>,
47
    pub verification: Option<VerificationState>,
48
    pub verification_target: Option<VerificationTarget>,
49
    pub mentions: Option<crate::MentionsPage>,
50
    pub rooms: Vector<RoomSummary>,
51
    pub image_packs: ImagePacks,
52
    pub timelines: imbl::HashMap<TimelineId, TimelineState>,
53
    /// a room without an entry is idle. closing a room drops its entries here and in `repairs`
54
    pub recent_history: imbl::HashMap<OwnedRoomId, RecentHistoryState>,
55
    pub repairs: imbl::HashMap<OwnedRoomId, HistoryRepairReport>,
56
    pub typing: imbl::HashMap<OwnedRoomId, Vec<OwnedUserId>>,
57
    pub uploads: imbl::HashMap<OwnedTransactionId, UploadState>,
58
    /// what stopped the engine, if something went wrong
59
    pub engine_failure: Option<EngineFailure>,
60
    /// the last save of the session record that failed, until one succeeds
61
    pub session_save_failure: Option<String>,
62
}
63
64
#[derive(Clone, Debug, PartialEq)]
65
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
66
#[non_exhaustive]
67
pub struct TimelineState {
68
    pub items: Vector<TimelineItem>,
69
    pub history: HistoryState,
70
    /// a batch didn't fit, so `items` is the last good copy until the next reset
71
    pub diverged: bool,
72
}
73
74
#[derive(Clone, Debug, Eq, PartialEq)]
75
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
76
#[non_exhaustive]
77
pub struct UploadState {
78
    pub room_id: OwnedRoomId,
79
    pub uploaded_bytes: u64,
80
    pub total_bytes: u64,
81
    pub complete: bool,
82
    pub failure: Option<UploadFailure>,
83
}
84
85
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
86
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
87
#[non_exhaustive]
88
pub struct UploadFailure {
89
    pub kind: ErrorKind,
90
    pub will_retry: bool,
91
}
92
93
/// the `Termination` it'll end in, as far as the events tell
94
#[derive(Clone, Debug, Eq, PartialEq)]
95
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
96
pub enum SessionStatus {
97
    Active,
98
    SoftLoggedOut {
99
        device_id: OwnedDeviceId,
100
    },
101
    AccessRevoked,
102
    LoggedOut,
103
    /// stopped with the session still good
104
    Stopped,
105
}
106
107
#[derive(Clone, Debug)]
108
#[non_exhaustive]
109
pub struct Update {
110
    /// one more than the update before it, and a `State` carries the one it's current as of
111
    pub sequence: u64,
112
    pub change: StateChange,
113
}
114
115
#[derive(Clone, Debug)]
116
pub enum StateChange {
117
    /// start over from this. a subscription opens with one and gets another after lagging
118
    State(Arc<State>),
119
    Authenticated(OwnedUserId),
120
    Session(SessionStatus),
121
    /// the record on disk may now hold a spent refresh token
122
    SessionSaveFailed(String),
123
    /// saved again after `SessionSaveFailed`
124
    SessionSaved,
125
    Connection(ConnectionState),
126
    Encryption(EncryptionState),
127
    Verification(VerificationState),
128
    VerificationTarget(VerificationTarget),
129
    Mentions(crate::MentionsUpdate),
130
    /// room modes arrive with the `Rooms` that follows
131
    NotificationSettings,
132
    Rooms(Vector<RoomSummary>),
133
    ImagePacks(ImagePacks),
134
    Timeline {
135
        timeline_id: TimelineId,
136
        change: TimelineChange,
137
    },
138
    History {
139
        timeline_id: TimelineId,
140
        state: HistoryState,
141
    },
142
    RecentHistory {
143
        room_id: OwnedRoomId,
144
        state: RecentHistoryState,
145
    },
146
    HistoryRepair(HistoryRepairReport),
147
    Typing(TypingState),
148
    EventContextClosed {
149
        context_id: String,
150
        reason: EventContextCloseReason,
151
    },
152
    Upload {
153
        transaction_id: OwnedTransactionId,
154
        upload: UploadState,
155
    },
156
    UploadFinished {
157
        room_id: OwnedRoomId,
158
        transaction_id: OwnedTransactionId,
159
        outcome: UploadOutcome,
160
    },
161
    Failed(EngineFailure),
162
}
163
164
#[derive(Clone, Debug)]
165
pub enum TimelineChange {
166
    Reset {
167
        items: Vector<TimelineItem>,
168
    },
169
    /// `items` is what applying `diffs` left
170
    Changed {
171
        diffs: Arc<[TimelineDiff]>,
172
        items: Vector<TimelineItem>,
173
    },
174
    /// a batch didn't fit. a resync is on its way and nothing changes until its reset
175
    Diverged,
176
    Closed,
177
}
178
179
/// drains the event stream into a `State` that any number of `MirrorHandle`s watch
180
pub struct Mirror {
181
    handle: ClientHandle,
182
    events: EngineBoxStream<Event>,
183
    shared: Arc<Shared>,
184
    _publication: PublicationRegistration,
185
    resyncs: HashMap<TimelineId, AbortHandle>,
186
    rooms_fetch: Option<AbortHandle>,
187
    packs_fetch: Option<AbortHandle>,
188
}
189
190
impl fmt::Debug for Mirror {
191
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
192
        formatter.debug_struct("Mirror").finish_non_exhaustive()
193
    }
194
}
195
196
impl Mirror {
197
    /// takes any stream of the engine's events, so a host can still look at them on the way past
198
    pub fn new(
199
        handle: ClientHandle,
200
        events: impl Stream<Item = Event> + SendOutsideWasm + 'static,
201
    ) -> (Self, MirrorHandle) {
202
        Self::with_capacity(handle, events, DEFAULT_CAPACITY)
203
    }
204
205
    /// `capacity` updates can queue for a subscriber before it lags and starts over. a zero
206
    /// capacity counts as one
207
    pub fn with_capacity(
208
        handle: ClientHandle,
209
        events: impl Stream<Item = Event> + SendOutsideWasm + 'static,
210
        capacity: usize,
211
    ) -> (Self, MirrorHandle) {
212
        let state = State {
213
            user_id: None,
214
            session: SessionStatus::Active,
215
            connection: *handle.connection_state().borrow(),
216
            encryption: handle.encryption_state(),
217
            verification: None,
218
            verification_target: None,
219
            mentions: None,
220
            rooms: Vector::new(),
221
            image_packs: ImagePacks::default(),
222
            timelines: imbl::HashMap::new(),
223
            recent_history: imbl::HashMap::new(),
224
            repairs: imbl::HashMap::new(),
225
            typing: imbl::HashMap::new(),
226
            uploads: imbl::HashMap::new(),
227
            engine_failure: None,
228
            session_save_failure: None,
229
        };
230
        let shared = Arc::new(Shared {
231
            published: Mutex::new(Published {
232
                state: Arc::new(state),
233
                sequence: 0,
234
                updates: Some(broadcast::channel(capacity.max(1)).0),
235
                rooms_fetch: Fetch::Idle,
236
                packs_fetch: Fetch::Idle,
237
            }),
238
        });
239
        let publication = handle.register_publication_mirror();
240
        #[cfg(not(target_family = "wasm"))]
241
        let events = events.boxed();
242
        #[cfg(target_family = "wasm")]
243
        let events = events.boxed_local();
244
        let mirror = Self {
245
            handle,
246
            events,
247
            shared: shared.clone(),
248
            _publication: publication,
249
            resyncs: HashMap::new(),
250
            rooms_fetch: None,
251
            packs_fetch: None,
252
        };
253
        (mirror, MirrorHandle { shared })
254
    }
255
256
    /// resolves once the event stream ends, which is just after `Stopped`
257
    pub fn run(mut self) -> EngineBoxFuture<'static, ()> {
258
        Box::pin(async move {
259
            self.refetch_rooms();
260
            self.refetch_image_packs();
261
            while let Some(event) = self.events.next().await {
262
                self.apply(event);
263
            }
264
        })
265
    }
266
267
    fn apply(&mut self, event: Event) {
268
        match event {
269
            Event::ConnectionChanged(connection) => self.publish(|state| {
270
                state.connection = connection;
271
                Some(StateChange::Connection(connection))
272
            }),
273
            Event::Authenticated { user_id } => self.publish(|state| {
274
                state.user_id = Some(user_id.clone());
275
                Some(StateChange::Authenticated(user_id))
276
            }),
277
            Event::SessionSaveFailed { message } => self.publish(|state| {
278
                state.session_save_failure = Some(message.clone());
279
                Some(StateChange::SessionSaveFailed(message))
280
            }),
281
            Event::SessionTokensChanged => self.publish(|state| {
282
                state.session_save_failure.take()?;
283
                Some(StateChange::SessionSaved)
284
            }),
285
            Event::SessionAccessRejected { soft_logout } => self.publish(|state| {
286
                state.mentions = None;
287
                state.session = match soft_logout {
288
                    Some(device_id) => SessionStatus::SoftLoggedOut { device_id },
289
                    None => SessionStatus::AccessRevoked,
290
                };
291
                Some(StateChange::Session(state.session.clone()))
292
            }),
293
            Event::LoggedOut => self.publish(|state| {
294
                state.mentions = None;
295
                state.session = SessionStatus::LoggedOut;
296
                Some(StateChange::Session(SessionStatus::LoggedOut))
297
            }),
298
            Event::Stopped => {
299
                self.cancel_resyncs();
300
                self.publish(|state| {
301
                    if state.session != SessionStatus::Active {
302
                        return None;
303
                    }
304
                    state.session = SessionStatus::Stopped;
305
                    Some(StateChange::Session(SessionStatus::Stopped))
306
                });
307
            }
308
            Event::Failed(failure) => self.publish(|state| {
309
                state.engine_failure = Some(failure.clone());
310
                Some(StateChange::Failed(failure))
311
            }),
312
            Event::NotificationSettingsChanged => {
313
                self.publish(|_| Some(StateChange::NotificationSettings));
314
            }
315
            Event::RoomsChanged => self.refetch_rooms(),
316
            Event::MentionsChanged(update) => self.publish(|state| {
317
                if state.session != SessionStatus::Active
318
                    || state
319
                        .user_id
320
                        .as_ref()
321
                        .is_some_and(|account| account != &update.snapshot.boundary.account)
322
                {
323
                    return None;
324
                }
325
                if state
326
                    .mentions
327
                    .as_ref()
328
                    .map_or(false, |current| update.snapshot.revision < current.revision)
329
                {
330
                    return None;
331
                }
332
                state.mentions = Some(update.snapshot.clone());
333
                Some(StateChange::Mentions(update))
334
            }),
335
            Event::ImagePacksChanged => self.refetch_image_packs(),
336
            Event::EncryptionChanged(encryption) => self.publish(|state| {
337
                state.encryption = Some(encryption.clone());
338
                Some(StateChange::Encryption(encryption))
339
            }),
340
            Event::VerificationChanged(verification) => self.publish(|state| {
341
                state.verification = Some(verification.clone());
342
                Some(StateChange::Verification(verification))
343
            }),
344
            Event::VerificationTargetChanged(target) => self.publish(|state| {
345
                state.verification_target = Some(target.clone());
346
                Some(StateChange::VerificationTarget(target))
347
            }),
348
            Event::TimelineReset { timeline_id, items } => {
349
                self.cancel_resync(&timeline_id);
350
                self.publish(|state| {
351
                    let history = state
352
                        .timelines
353
                        .get(&timeline_id)
354
                        .map_or(HistoryState::Idle, |timeline| timeline.history.clone());
355
                    let timeline = TimelineState {
356
                        items: items.clone(),
357
                        history,
358
                        diverged: false,
359
                    };
360
                    state.timelines.insert(timeline_id.clone(), timeline);
361
                    Some(StateChange::Timeline {
362
                        timeline_id,
363
                        change: TimelineChange::Reset { items },
364
                    })
365
                });
366
            }
367
            Event::TimelineChanged { timeline_id, diffs } => self.apply_diffs(timeline_id, diffs),
368
            Event::HistoryChanged {
369
                timeline_id,
370
                state: history,
371
            } => self.publish(|state| {
372
                state.timelines.get_mut(&timeline_id)?.history = history.clone();
373
                Some(StateChange::History {
374
                    timeline_id,
375
                    state: history,
376
                })
377
            }),
378
            Event::RecentHistoryChanged {
379
                room_id,
380
                state: recent,
381
            } => self.publish(|state| {
382
                if recent == RecentHistoryState::Idle {
383
                    state.recent_history.remove(&room_id);
384
                } else {
385
                    state.recent_history.insert(room_id.clone(), recent.clone());
386
                }
387
                Some(StateChange::RecentHistory {
388
                    room_id,
389
                    state: recent,
390
                })
391
            }),
392
            Event::HistoryRepairChanged(report) => self.publish(|state| {
393
                state.repairs.insert(report.room_id.clone(), report.clone());
394
                Some(StateChange::HistoryRepair(report))
395
            }),
396
            Event::PublicationBarrier { acknowledgement } => {
397
                if let Some(acknowledgement) = acknowledgement {
398
                    acknowledgement.complete(true);
399
                }
400
            }
401
            Event::TimelineClosed { timeline_id } => {
402
                self.cancel_resync(&timeline_id);
403
                self.publish(|state| {
404
                    state.timelines.remove(&timeline_id)?;
405
                    if let TimelineId::Room(room_id) = &timeline_id {
406
                        state.repairs.remove(room_id);
407
                    }
408
                    Some(StateChange::Timeline {
409
                        timeline_id,
410
                        change: TimelineChange::Closed,
411
                    })
412
                });
413
            }
414
            Event::EventContextClosed { context_id, reason } => {
415
                self.publish(|_| Some(StateChange::EventContextClosed { context_id, reason }));
416
            }
417
            Event::TypingChanged(typing) => self.publish(|state| {
418
                if typing.users.is_empty() {
419
                    state.typing.remove(&typing.room_id);
420
                } else {
421
                    state
422
                        .typing
423
                        .insert(typing.room_id.clone(), typing.users.clone());
424
                }
425
                Some(StateChange::Typing(typing))
426
            }),
427
            Event::MediaUploadProgress {
428
                room_id,
429
                transaction_id,
430
                uploaded_bytes,
431
                total_bytes,
432
                complete,
433
            } => self.publish(|state| {
434
                let upload = UploadState {
435
                    room_id,
436
                    uploaded_bytes,
437
                    total_bytes,
438
                    complete,
439
                    failure: None,
440
                };
441
                state.uploads.insert(transaction_id.clone(), upload.clone());
442
                Some(StateChange::Upload {
443
                    transaction_id,
444
                    upload,
445
                })
446
            }),
447
            Event::MediaUploadFailed {
448
                room_id,
449
                transaction_id,
450
                kind,
451
                will_retry,
452
            } => self.publish(|state| {
453
                let upload = state
454
                    .uploads
455
                    .entry(transaction_id.clone())
456
                    .or_insert_with(|| UploadState {
457
                        room_id,
458
                        uploaded_bytes: 0,
459
                        total_bytes: 0,
460
                        complete: false,
461
                        failure: None,
462
                    });
463
                upload.failure = Some(UploadFailure { kind, will_retry });
464
                Some(StateChange::Upload {
465
                    transaction_id,
466
                    upload: upload.clone(),
467
                })
468
            }),
469
            Event::MediaUploadFinished {
470
                room_id,
471
                transaction_id,
472
                outcome,
473
            } => self.publish(|state| {
474
                state.uploads.remove(&transaction_id);
475
                Some(StateChange::UploadFinished {
476
                    room_id,
477
                    transaction_id,
478
                    outcome,
479
                })
480
            }),
481
        }
482
    }
483
484
    fn apply_diffs(&mut self, timeline_id: TimelineId, diffs: Vec<TimelineDiff>) {
485
        let mut diverged = false;
486
        self.publish(|state| {
487
            let Some(timeline) = state.timelines.get_mut(&timeline_id) else {
488
                diverged = true;
489
                state.timelines.insert(
490
                    timeline_id.clone(),
491
                    TimelineState {
492
                        items: Vector::new(),
493
                        history: HistoryState::Idle,
494
                        diverged: true,
495
                    },
496
                );
497
                return Some(StateChange::Timeline {
498
                    timeline_id: timeline_id.clone(),
499
                    change: TimelineChange::Diverged,
500
                });
501
            };
502
            if timeline.diverged {
503
                return None;
504
            }
505
            let diffs: Arc<[TimelineDiff]> = diffs.into();
506
            let change = match TimelineDiff::try_apply_all(diffs.to_vec(), &mut timeline.items) {
507
                Ok(()) => TimelineChange::Changed {
508
                    diffs,
509
                    items: timeline.items.clone(),
510
                },
511
                Err(error) => {
512
                    tracing::debug!(?timeline_id, length = error.length, "timeline diverged");
513
                    timeline.diverged = true;
514
                    diverged = true;
515
                    TimelineChange::Diverged
516
                }
517
            };
518
            Some(StateChange::Timeline {
519
                timeline_id: timeline_id.clone(),
520
                change,
521
            })
522
        });
523
        if diverged {
524
            let task = spawn(resync(self.handle.clone(), timeline_id.clone()));
525
            self.resyncs.insert(timeline_id, task.abort_handle());
526
        }
527
    }
528
529
    fn refetch_rooms(&mut self) {
530
        let fetch = fetch_rooms(self.handle.clone(), self.shared.clone());
531
        if let Some(task) = self
532
            .shared
533
            .start(|published| &mut published.rooms_fetch, fetch)
534
        {
535
            self.rooms_fetch = Some(task);
536
        }
537
    }
538
539
    fn refetch_image_packs(&mut self) {
540
        let fetch = fetch_image_packs(self.handle.clone(), self.shared.clone());
541
        if let Some(task) = self
542
            .shared
543
            .start(|published| &mut published.packs_fetch, fetch)
544
        {
545
            self.packs_fetch = Some(task);
546
        }
547
    }
548
549
    fn cancel_resync(&mut self, timeline_id: &TimelineId) {
550
        if let Some(task) = self.resyncs.remove(timeline_id) {
551
            task.abort();
552
        }
553
    }
554
555
    fn cancel_resyncs(&mut self) {
556
        for (_, task) in self.resyncs.drain() {
557
            task.abort();
558
        }
559
    }
560
561
    fn publish(&self, change: impl FnOnce(&mut State) -> Option<StateChange>) {
562
        self.shared.publish(change);
563
    }
564
}
565
566
impl Drop for Mirror {
567
    fn drop(&mut self) {
568
        self.cancel_resyncs();
569
        for task in [self.rooms_fetch.take(), self.packs_fetch.take()]
570
            .into_iter()
571
            .flatten()
572
        {
573
            task.abort();
574
        }
575
        self.shared.lock().updates = None;
576
    }
577
}
578
579
/// asks until the engine answers, since a busy room or a full queue is only ever for now.
580
/// a reset or a close for the timeline aborts it, and the engine sends a reset before it answers
581
async fn resync(handle: ClientHandle, timeline_id: TimelineId) {
582
    let mut backoff = RESYNC_BACKOFF;
583
    loop {
584
        let command = Command::Timeline(TimelineCommand::Resync {
585
            timeline_id: timeline_id.clone(),
586
        });
587
        match handle.execute(command).await {
588
            Ok(_) => return,
589
            Err(error) if error.kind == ErrorKind::Interrupted => return,
590
            Err(error) => {
591
                tracing::debug!(%error, ?timeline_id, "resync refused, trying again");
592
                matrix_sdk::sleep::sleep(backoff).await;
593
                backoff = (backoff * 2).min(RESYNC_BACKOFF_CAP);
594
            }
595
        }
596
    }
597
}
598
599
async fn fetch_rooms(handle: ClientHandle, shared: Arc<Shared>) {
600
    loop {
601
        let rooms: Vector<RoomSummary> = handle.rooms().await.into();
602
        shared.publish(|state| {
603
            state.rooms = rooms.clone();
604
            Some(StateChange::Rooms(rooms))
605
        });
606
        if !shared.fetched(|published| &mut published.rooms_fetch) {
607
            return;
608
        }
609
    }
610
}
611
612
/// a failed read keeps the packs from before, the next change asks again
613
async fn fetch_image_packs(handle: ClientHandle, shared: Arc<Shared>) {
614
    loop {
615
        match handle.image_packs().await {
616
            Ok(packs) => shared.publish(|state| {
617
                if state.image_packs == packs {
618
                    return None;
619
                }
620
                state.image_packs = packs.clone();
621
                Some(StateChange::ImagePacks(packs))
622
            }),
623
            Err(error) => tracing::warn!(%error, "reading image packs failed"),
624
        }
625
        if !shared.fetched(|published| &mut published.packs_fetch) {
626
            return;
627
        }
628
    }
629
}
630
631
/// cheap to clone, one per frontend
632
#[derive(Clone, Debug)]
633
pub struct MirrorHandle {
634
    shared: Arc<Shared>,
635
}
636
637
impl MirrorHandle {
638
    #[must_use]
639
    pub fn snapshot(&self) -> Arc<State> {
640
        self.shared.lock().state.clone()
641
    }
642
643
    /// opens with the current state, then every update after it. ends once the mirror stops
644
    #[must_use]
645
    pub fn subscribe(&self) -> Subscription {
646
        let (first, receiving) = self.shared.clone().subscribe();
647
        let rest = futures_util::stream::unfold(receiving, |(shared, receiver)| async move {
648
            let mut receiver = receiver?;
649
            match receiver.recv().await {
650
                Ok(update) => Some((update, (shared, Some(receiver)))),
651
                Err(broadcast::error::RecvError::Lagged(missed)) => {
652
                    tracing::debug!(missed, "mirror subscriber lagged, starting it over");
653
                    Some(shared.subscribe())
654
                }
655
                Err(broadcast::error::RecvError::Closed) => None,
656
            }
657
        });
658
        let updates = futures_util::stream::once(std::future::ready(first)).chain(rest);
659
        #[cfg(not(target_family = "wasm"))]
660
        let updates = updates.boxed();
661
        #[cfg(target_family = "wasm")]
662
        let updates = updates.boxed_local();
663
        Subscription(updates)
664
    }
665
}
666
667
pub struct Subscription(EngineBoxStream<Update>);
668
669
impl fmt::Debug for Subscription {
670
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
671
        formatter
672
            .debug_struct("Subscription")
673
            .finish_non_exhaustive()
674
    }
675
}
676
677
impl Stream for Subscription {
678
    type Item = Update;
679
680
    fn poll_next(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<Update>> {
681
        self.0.poll_next_unpin(context)
682
    }
683
}
684
685
#[derive(Debug)]
686
struct Shared {
687
    published: Mutex<Published>,
688
}
689
690
#[derive(Debug)]
691
struct Published {
692
    state: Arc<State>,
693
    sequence: u64,
694
    /// gone once the mirror stops, which ends every subscription
695
    updates: Option<broadcast::Sender<Update>>,
696
    rooms_fetch: Fetch,
697
    packs_fetch: Fetch,
698
}
699
700
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
701
enum Fetch {
702
    Idle,
703
    Running,
704
    Again,
705
}
706
707
impl Shared {
708
    fn lock(&self) -> MutexGuard<'_, Published> {
709
        self.published
710
            .lock()
711
            .unwrap_or_else(PoisonError::into_inner)
712
    }
713
714
    /// one fetch at a time, with at most one more owed
715
    fn start(
716
        &self,
717
        fetch: impl FnOnce(&mut Published) -> &mut Fetch,
718
        task: impl Future<Output = ()> + SendOutsideWasm + 'static,
719
    ) -> Option<AbortHandle> {
720
        let mut published = self.lock();
721
        let fetch = fetch(&mut published);
722
        match *fetch {
723
            Fetch::Idle => {
724
                *fetch = Fetch::Running;
725
                drop(published);
726
                Some(spawn(task).abort_handle())
727
            }
728
            Fetch::Running => {
729
                *fetch = Fetch::Again;
730
                None
731
            }
732
            Fetch::Again => None,
733
        }
734
    }
735
736
    /// whether another fetch was asked for while this one ran
737
    fn fetched(&self, fetch: impl FnOnce(&mut Published) -> &mut Fetch) -> bool {
738
        let mut published = self.lock();
739
        let fetch = fetch(&mut published);
740
        if *fetch == Fetch::Again {
741
            *fetch = Fetch::Running;
742
            return true;
743
        }
744
        *fetch = Fetch::Idle;
745
        false
746
    }
747
748
    /// state, sequence and send move together under the one lock, which is what lets
749
    /// `subscribe` take a snapshot and a receiver with nothing between them
750
    fn publish(&self, change: impl FnOnce(&mut State) -> Option<StateChange>) {
751
        let mut published = self.lock();
752
        let Some(change) = change(Arc::make_mut(&mut published.state)) else {
753
            return;
754
        };
755
        published.sequence += 1;
756
        let update = Update {
757
            sequence: published.sequence,
758
            change,
759
        };
760
        if let Some(updates) = &published.updates {
761
            let _ = updates.send(update);
762
        }
763
    }
764
765
    fn subscribe(self: Arc<Self>) -> (Update, (Arc<Self>, Option<broadcast::Receiver<Update>>)) {
766
        let published = self.lock();
767
        let update = Update {
768
            sequence: published.sequence,
769
            change: StateChange::State(published.state.clone()),
770
        };
771
        let receiver = published.updates.as_ref().map(broadcast::Sender::subscribe);
772
        drop(published);
773
        (update, (self, receiver))
774
    }
775
}