atagen

macltok

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