atagen

macltok

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