atagen

macltok

public
Star0
Watch0
06c73391cbaec25f3bfaacf44d54a9ac657ec4e9
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
src/mirror.rsRust768 lines26.3 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
    /// 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
    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
                if recent == RecentHistoryState::Idle {
379
                    state.recent_history.remove(&room_id);
380
                } else {
381
                    state.recent_history.insert(room_id.clone(), recent.clone());
382
                }
383
                Some(StateChange::RecentHistory {
384
                    room_id,
385
                    state: recent,
386
                })
387
            }),
388
            Event::HistoryRepairChanged(report) => self.publish(|state| {
389
                state.repairs.insert(report.room_id.clone(), report.clone());
390
                Some(StateChange::HistoryRepair(report))
391
            }),
392
            Event::PublicationBarrier { acknowledgement } => {
393
                if let Some(acknowledgement) = acknowledgement {
394
                    acknowledgement.complete(true);
395
                }
396
            }
397
            Event::TimelineClosed { timeline_id } => {
398
                self.cancel_resync(&timeline_id);
399
                self.publish(|state| {
400
                    state.timelines.remove(&timeline_id)?;
401
                    if let TimelineId::Room(room_id) = &timeline_id {
402
                        state.repairs.remove(room_id);
403
                    }
404
                    Some(StateChange::Timeline {
405
                        timeline_id,
406
                        change: TimelineChange::Closed,
407
                    })
408
                });
409
            }
410
            Event::TypingChanged(typing) => self.publish(|state| {
411
                if typing.users.is_empty() {
412
                    state.typing.remove(&typing.room_id);
413
                } else {
414
                    state
415
                        .typing
416
                        .insert(typing.room_id.clone(), typing.users.clone());
417
                }
418
                Some(StateChange::Typing(typing))
419
            }),
420
            Event::MediaUploadProgress {
421
                room_id,
422
                transaction_id,
423
                uploaded_bytes,
424
                total_bytes,
425
                complete,
426
            } => self.publish(|state| {
427
                let upload = UploadState {
428
                    room_id,
429
                    uploaded_bytes,
430
                    total_bytes,
431
                    complete,
432
                    failure: None,
433
                };
434
                state.uploads.insert(transaction_id.clone(), upload.clone());
435
                Some(StateChange::Upload {
436
                    transaction_id,
437
                    upload,
438
                })
439
            }),
440
            Event::MediaUploadFailed {
441
                room_id,
442
                transaction_id,
443
                kind,
444
                will_retry,
445
            } => self.publish(|state| {
446
                let upload = state
447
                    .uploads
448
                    .entry(transaction_id.clone())
449
                    .or_insert_with(|| UploadState {
450
                        room_id,
451
                        uploaded_bytes: 0,
452
                        total_bytes: 0,
453
                        complete: false,
454
                        failure: None,
455
                    });
456
                upload.failure = Some(UploadFailure { kind, will_retry });
457
                Some(StateChange::Upload {
458
                    transaction_id,
459
                    upload: upload.clone(),
460
                })
461
            }),
462
            Event::MediaUploadFinished {
463
                room_id,
464
                transaction_id,
465
                outcome,
466
            } => self.publish(|state| {
467
                state.uploads.remove(&transaction_id);
468
                Some(StateChange::UploadFinished {
469
                    room_id,
470
                    transaction_id,
471
                    outcome,
472
                })
473
            }),
474
        }
475
    }
476
477
    fn apply_diffs(&mut self, timeline_id: TimelineId, diffs: Vec<TimelineDiff>) {
478
        let mut diverged = false;
479
        self.publish(|state| {
480
            let Some(timeline) = state.timelines.get_mut(&timeline_id) else {
481
                diverged = true;
482
                state.timelines.insert(
483
                    timeline_id.clone(),
484
                    TimelineState {
485
                        items: Vector::new(),
486
                        history: HistoryState::Idle,
487
                        diverged: true,
488
                    },
489
                );
490
                return Some(StateChange::Timeline {
491
                    timeline_id: timeline_id.clone(),
492
                    change: TimelineChange::Diverged,
493
                });
494
            };
495
            if timeline.diverged {
496
                return None;
497
            }
498
            let diffs: Arc<[TimelineDiff]> = diffs.into();
499
            let change = match TimelineDiff::try_apply_all(diffs.to_vec(), &mut timeline.items) {
500
                Ok(()) => TimelineChange::Changed {
501
                    diffs,
502
                    items: timeline.items.clone(),
503
                },
504
                Err(error) => {
505
                    tracing::debug!(?timeline_id, length = error.length, "timeline diverged");
506
                    timeline.diverged = true;
507
                    diverged = true;
508
                    TimelineChange::Diverged
509
                }
510
            };
511
            Some(StateChange::Timeline {
512
                timeline_id: timeline_id.clone(),
513
                change,
514
            })
515
        });
516
        if diverged {
517
            let task = spawn(resync(self.handle.clone(), timeline_id.clone()));
518
            self.resyncs.insert(timeline_id, task.abort_handle());
519
        }
520
    }
521
522
    fn refetch_rooms(&mut self) {
523
        let fetch = fetch_rooms(self.handle.clone(), self.shared.clone());
524
        if let Some(task) = self
525
            .shared
526
            .start(|published| &mut published.rooms_fetch, fetch)
527
        {
528
            self.rooms_fetch = Some(task);
529
        }
530
    }
531
532
    fn refetch_image_packs(&mut self) {
533
        let fetch = fetch_image_packs(self.handle.clone(), self.shared.clone());
534
        if let Some(task) = self
535
            .shared
536
            .start(|published| &mut published.packs_fetch, fetch)
537
        {
538
            self.packs_fetch = Some(task);
539
        }
540
    }
541
542
    fn cancel_resync(&mut self, timeline_id: &TimelineId) {
543
        if let Some(task) = self.resyncs.remove(timeline_id) {
544
            task.abort();
545
        }
546
    }
547
548
    fn cancel_resyncs(&mut self) {
549
        for (_, task) in self.resyncs.drain() {
550
            task.abort();
551
        }
552
    }
553
554
    fn publish(&self, change: impl FnOnce(&mut State) -> Option<StateChange>) {
555
        self.shared.publish(change);
556
    }
557
}
558
559
impl Drop for Mirror {
560
    fn drop(&mut self) {
561
        self.cancel_resyncs();
562
        for task in [self.rooms_fetch.take(), self.packs_fetch.take()]
563
            .into_iter()
564
            .flatten()
565
        {
566
            task.abort();
567
        }
568
        self.shared.lock().updates = None;
569
    }
570
}
571
572
/// asks until the engine answers, since a busy room or a full queue is only ever for now.
573
/// a reset or a close for the timeline aborts it, and the engine sends a reset before it answers
574
async fn resync(handle: ClientHandle, timeline_id: TimelineId) {
575
    let mut backoff = RESYNC_BACKOFF;
576
    loop {
577
        let command = Command::Timeline(TimelineCommand::Resync {
578
            timeline_id: timeline_id.clone(),
579
        });
580
        match handle.execute(command).await {
581
            Ok(_) => return,
582
            Err(error) if error.kind == ErrorKind::Interrupted => return,
583
            Err(error) => {
584
                tracing::debug!(%error, ?timeline_id, "resync refused, trying again");
585
                matrix_sdk::sleep::sleep(backoff).await;
586
                backoff = (backoff * 2).min(RESYNC_BACKOFF_CAP);
587
            }
588
        }
589
    }
590
}
591
592
async fn fetch_rooms(handle: ClientHandle, shared: Arc<Shared>) {
593
    loop {
594
        let rooms: Vector<RoomSummary> = handle.rooms().await.into();
595
        shared.publish(|state| {
596
            state.rooms = rooms.clone();
597
            Some(StateChange::Rooms(rooms))
598
        });
599
        if !shared.fetched(|published| &mut published.rooms_fetch) {
600
            return;
601
        }
602
    }
603
}
604
605
/// a failed read keeps the packs from before, the next change asks again
606
async fn fetch_image_packs(handle: ClientHandle, shared: Arc<Shared>) {
607
    loop {
608
        match handle.image_packs().await {
609
            Ok(packs) => shared.publish(|state| {
610
                if state.image_packs == packs {
611
                    return None;
612
                }
613
                state.image_packs = packs.clone();
614
                Some(StateChange::ImagePacks(packs))
615
            }),
616
            Err(error) => tracing::warn!(%error, "reading image packs failed"),
617
        }
618
        if !shared.fetched(|published| &mut published.packs_fetch) {
619
            return;
620
        }
621
    }
622
}
623
624
/// cheap to clone, one per frontend
625
#[derive(Clone, Debug)]
626
pub struct MirrorHandle {
627
    shared: Arc<Shared>,
628
}
629
630
impl MirrorHandle {
631
    #[must_use]
632
    pub fn snapshot(&self) -> Arc<State> {
633
        self.shared.lock().state.clone()
634
    }
635
636
    /// opens with the current state, then every update after it. ends once the mirror stops
637
    #[must_use]
638
    pub fn subscribe(&self) -> Subscription {
639
        let (first, receiving) = self.shared.clone().subscribe();
640
        let rest = futures_util::stream::unfold(receiving, |(shared, receiver)| async move {
641
            let mut receiver = receiver?;
642
            match receiver.recv().await {
643
                Ok(update) => Some((update, (shared, Some(receiver)))),
644
                Err(broadcast::error::RecvError::Lagged(missed)) => {
645
                    tracing::debug!(missed, "mirror subscriber lagged, starting it over");
646
                    Some(shared.subscribe())
647
                }
648
                Err(broadcast::error::RecvError::Closed) => None,
649
            }
650
        });
651
        let updates = futures_util::stream::once(std::future::ready(first)).chain(rest);
652
        #[cfg(not(target_family = "wasm"))]
653
        let updates = updates.boxed();
654
        #[cfg(target_family = "wasm")]
655
        let updates = updates.boxed_local();
656
        Subscription(updates)
657
    }
658
}
659
660
pub struct Subscription(EngineBoxStream<Update>);
661
662
impl fmt::Debug for Subscription {
663
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
664
        formatter
665
            .debug_struct("Subscription")
666
            .finish_non_exhaustive()
667
    }
668
}
669
670
impl Stream for Subscription {
671
    type Item = Update;
672
673
    fn poll_next(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<Update>> {
674
        self.0.poll_next_unpin(context)
675
    }
676
}
677
678
#[derive(Debug)]
679
struct Shared {
680
    published: Mutex<Published>,
681
}
682
683
#[derive(Debug)]
684
struct Published {
685
    state: Arc<State>,
686
    sequence: u64,
687
    /// gone once the mirror stops, which ends every subscription
688
    updates: Option<broadcast::Sender<Update>>,
689
    rooms_fetch: Fetch,
690
    packs_fetch: Fetch,
691
}
692
693
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
694
enum Fetch {
695
    Idle,
696
    Running,
697
    Again,
698
}
699
700
impl Shared {
701
    fn lock(&self) -> MutexGuard<'_, Published> {
702
        self.published
703
            .lock()
704
            .unwrap_or_else(PoisonError::into_inner)
705
    }
706
707
    /// one fetch at a time, with at most one more owed
708
    fn start(
709
        &self,
710
        fetch: impl FnOnce(&mut Published) -> &mut Fetch,
711
        task: impl Future<Output = ()> + SendOutsideWasm + 'static,
712
    ) -> Option<AbortHandle> {
713
        let mut published = self.lock();
714
        let fetch = fetch(&mut published);
715
        match *fetch {
716
            Fetch::Idle => {
717
                *fetch = Fetch::Running;
718
                drop(published);
719
                Some(spawn(task).abort_handle())
720
            }
721
            Fetch::Running => {
722
                *fetch = Fetch::Again;
723
                None
724
            }
725
            Fetch::Again => None,
726
        }
727
    }
728
729
    /// whether another fetch was asked for while this one ran
730
    fn fetched(&self, fetch: impl FnOnce(&mut Published) -> &mut Fetch) -> bool {
731
        let mut published = self.lock();
732
        let fetch = fetch(&mut published);
733
        if *fetch == Fetch::Again {
734
            *fetch = Fetch::Running;
735
            return true;
736
        }
737
        *fetch = Fetch::Idle;
738
        false
739
    }
740
741
    /// state, sequence and send move together under the one lock, which is what lets
742
    /// `subscribe` take a snapshot and a receiver with nothing between them
743
    fn publish(&self, change: impl FnOnce(&mut State) -> Option<StateChange>) {
744
        let mut published = self.lock();
745
        let Some(change) = change(Arc::make_mut(&mut published.state)) else {
746
            return;
747
        };
748
        published.sequence += 1;
749
        let update = Update {
750
            sequence: published.sequence,
751
            change,
752
        };
753
        if let Some(updates) = &published.updates {
754
            let _ = updates.send(update);
755
        }
756
    }
757
758
    fn subscribe(self: Arc<Self>) -> (Update, (Arc<Self>, Option<broadcast::Receiver<Update>>)) {
759
        let published = self.lock();
760
        let update = Update {
761
            sequence: published.sequence,
762
            change: StateChange::State(published.state.clone()),
763
        };
764
        let receiver = published.updates.as_ref().map(broadcast::Sender::subscribe);
765
        drop(published);
766
        (update, (self, receiver))
767
    }
768
}