atagen

macltok

public
Star0
Watch0
960ac07210797dcdf9149ef8465c1ccd46dafbda
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git

Blame: src/engine/watchers.rs

31ce2453c82fatagen2026-09-24 13:07 UTC1
use std::{sync::Arc, time::Duration};
31ce2453c82fatagen2026-09-24 13:07 UTC2
31ce2453c82fatagen2026-09-24 13:07 UTC3
use futures_util::StreamExt;
31ce2453c82fatagen2026-09-24 13:07 UTC4
use matrix_sdk::{
31ce2453c82fatagen2026-09-24 13:07 UTC5
    Client, Room, SessionChange,
31ce2453c82fatagen2026-09-24 13:07 UTC6
    ruma::events::{AnyMessageLikeEventContent, room::message::MessageType},
31ce2453c82fatagen2026-09-24 13:07 UTC7
    send_queue::{LocalEchoContent, RoomSendQueueUpdate},
31ce2453c82fatagen2026-09-24 13:07 UTC8
};
31ce2453c82fatagen2026-09-24 13:07 UTC9
use matrix_sdk_ui::sync_service::{State as SyncState, SyncService};
31ce2453c82fatagen2026-09-24 13:07 UTC10
use tokio::{
31ce2453c82fatagen2026-09-24 13:07 UTC11
    sync::{Notify, broadcast::error::RecvError, watch},
31ce2453c82fatagen2026-09-24 13:07 UTC12
    task::JoinSet,
31ce2453c82fatagen2026-09-24 13:07 UTC13
};
31ce2453c82fatagen2026-09-24 13:07 UTC14
31ce2453c82fatagen2026-09-24 13:07 UTC15
use crate::{
31ce2453c82fatagen2026-09-24 13:07 UTC16
    ConnectionState, Event, encryption::encryption_state, error::classify_sdk_error,
31ce2453c82fatagen2026-09-24 13:07 UTC17
    transport::EventSender,
31ce2453c82fatagen2026-09-24 13:07 UTC18
};
31ce2453c82fatagen2026-09-24 13:07 UTC19
31ce2453c82fatagen2026-09-24 13:07 UTC20
const SYNC_RESTART_MIN: Duration = Duration::from_secs(1);
31ce2453c82fatagen2026-09-24 13:07 UTC21
const SYNC_RESTART_MAX: Duration = Duration::from_secs(60);
31ce2453c82fatagen2026-09-24 13:07 UTC22
const SEND_QUEUE_RETRY: Duration = Duration::from_secs(5);
31ce2453c82fatagen2026-09-24 13:07 UTC23
31ce2453c82fatagen2026-09-24 13:07 UTC24
pub(super) async fn session(
31ce2453c82fatagen2026-09-24 13:07 UTC25
    mut changes: tokio::sync::broadcast::Receiver<SessionChange>,
31ce2453c82fatagen2026-09-24 13:07 UTC26
    events: EventSender,
31ce2453c82fatagen2026-09-24 13:07 UTC27
    stop: Arc<Notify>,
31ce2453c82fatagen2026-09-24 13:07 UTC28
) {
31ce2453c82fatagen2026-09-24 13:07 UTC29
    loop {
31ce2453c82fatagen2026-09-24 13:07 UTC30
        let event = match changes.recv().await {
31ce2453c82fatagen2026-09-24 13:07 UTC31
            Ok(SessionChange::TokensRefreshed) => Event::SessionTokensChanged,
31ce2453c82fatagen2026-09-24 13:07 UTC32
            Ok(SessionChange::UnknownToken(data)) => {
31ce2453c82fatagen2026-09-24 13:07 UTC33
                let _ = events
31ce2453c82fatagen2026-09-24 13:07 UTC34
                    .send(Event::SessionAccessRejected {
31ce2453c82fatagen2026-09-24 13:07 UTC35
                        soft_logout: data.soft_logout,
31ce2453c82fatagen2026-09-24 13:07 UTC36
                    })
31ce2453c82fatagen2026-09-24 13:07 UTC37
                    .await;
31ce2453c82fatagen2026-09-24 13:07 UTC38
                stop.notify_one();
31ce2453c82fatagen2026-09-24 13:07 UTC39
                return;
31ce2453c82fatagen2026-09-24 13:07 UTC40
            }
31ce2453c82fatagen2026-09-24 13:07 UTC41
            // a missed refresh costs one needless save, a missed rejection comes round again
31ce2453c82fatagen2026-09-24 13:07 UTC42
            Err(RecvError::Lagged(_)) => Event::SessionTokensChanged,
31ce2453c82fatagen2026-09-24 13:07 UTC43
            Err(RecvError::Closed) => return,
31ce2453c82fatagen2026-09-24 13:07 UTC44
        };
31ce2453c82fatagen2026-09-24 13:07 UTC45
        if events.send(event).await.is_err() {
31ce2453c82fatagen2026-09-24 13:07 UTC46
            return;
31ce2453c82fatagen2026-09-24 13:07 UTC47
        }
31ce2453c82fatagen2026-09-24 13:07 UTC48
    }
31ce2453c82fatagen2026-09-24 13:07 UTC49
}
31ce2453c82fatagen2026-09-24 13:07 UTC50
31ce2453c82fatagen2026-09-24 13:07 UTC51
/// `states` must be taken before sync starts, a late subscriber never hears the transition it missed
31ce2453c82fatagen2026-09-24 13:07 UTC52
pub(super) async fn connection(
31ce2453c82fatagen2026-09-24 13:07 UTC53
    mut states: impl futures_util::Stream<Item = SyncState> + Unpin,
31ce2453c82fatagen2026-09-24 13:07 UTC54
    sync: Arc<SyncService>,
31ce2453c82fatagen2026-09-24 13:07 UTC55
    client: Client,
31ce2453c82fatagen2026-09-24 13:07 UTC56
    events: EventSender,
31ce2453c82fatagen2026-09-24 13:07 UTC57
    connection: watch::Sender<ConnectionState>,
31ce2453c82fatagen2026-09-24 13:07 UTC58
) {
31ce2453c82fatagen2026-09-24 13:07 UTC59
    let mut backoff = SYNC_RESTART_MIN;
31ce2453c82fatagen2026-09-24 13:07 UTC60
    while let Some(state) = states.next().await {
31ce2453c82fatagen2026-09-24 13:07 UTC61
        let (state, restart) = match state {
31ce2453c82fatagen2026-09-24 13:07 UTC62
            SyncState::Idle => (ConnectionState::Idle, false),
31ce2453c82fatagen2026-09-24 13:07 UTC63
            SyncState::Running => {
31ce2453c82fatagen2026-09-24 13:07 UTC64
                backoff = SYNC_RESTART_MIN;
31ce2453c82fatagen2026-09-24 13:07 UTC65
                // the sdk parks a room queue on any send error and never unparks it
31ce2453c82fatagen2026-09-24 13:07 UTC66
                client.send_queue().set_enabled(true).await;
31ce2453c82fatagen2026-09-24 13:07 UTC67
                (ConnectionState::Online, false)
31ce2453c82fatagen2026-09-24 13:07 UTC68
            }
31ce2453c82fatagen2026-09-24 13:07 UTC69
            SyncState::Offline => (ConnectionState::Offline, false),
31ce2453c82fatagen2026-09-24 13:07 UTC70
            SyncState::Terminated => (ConnectionState::Offline, true),
31ce2453c82fatagen2026-09-24 13:07 UTC71
            SyncState::Error(error) => {
31ce2453c82fatagen2026-09-24 13:07 UTC72
                tracing::warn!(%error, "sync service reported an error");
31ce2453c82fatagen2026-09-24 13:07 UTC73
                (ConnectionState::Offline, true)
31ce2453c82fatagen2026-09-24 13:07 UTC74
            }
31ce2453c82fatagen2026-09-24 13:07 UTC75
        };
31ce2453c82fatagen2026-09-24 13:07 UTC76
        if *connection.borrow() != state {
31ce2453c82fatagen2026-09-24 13:07 UTC77
            connection.send_replace(state);
31ce2453c82fatagen2026-09-24 13:07 UTC78
            if events.send(Event::ConnectionChanged(state)).await.is_err() {
31ce2453c82fatagen2026-09-24 13:07 UTC79
                return;
31ce2453c82fatagen2026-09-24 13:07 UTC80
            }
31ce2453c82fatagen2026-09-24 13:07 UTC81
        }
31ce2453c82fatagen2026-09-24 13:07 UTC82
        if restart {
31ce2453c82fatagen2026-09-24 13:07 UTC83
            tokio::time::sleep(backoff).await;
31ce2453c82fatagen2026-09-24 13:07 UTC84
            backoff = (backoff * 2).min(SYNC_RESTART_MAX);
31ce2453c82fatagen2026-09-24 13:07 UTC85
            sync.start().await;
31ce2453c82fatagen2026-09-24 13:07 UTC86
        }
31ce2453c82fatagen2026-09-24 13:07 UTC87
    }
31ce2453c82fatagen2026-09-24 13:07 UTC88
}
31ce2453c82fatagen2026-09-24 13:07 UTC89
31ce2453c82fatagen2026-09-24 13:07 UTC90
/// a recoverable failure while we're online is a hiccup, so give the room another go
31ce2453c82fatagen2026-09-24 13:07 UTC91
pub(super) async fn send_queue_errors(
31ce2453c82fatagen2026-09-24 13:07 UTC92
    client: Client,
31ce2453c82fatagen2026-09-24 13:07 UTC93
    mut errors: tokio::sync::broadcast::Receiver<matrix_sdk::send_queue::SendQueueRoomError>,
31ce2453c82fatagen2026-09-24 13:07 UTC94
    online: watch::Receiver<ConnectionState>,
31ce2453c82fatagen2026-09-24 13:07 UTC95
) {
31ce2453c82fatagen2026-09-24 13:07 UTC96
    let mut retries = JoinSet::new();
31ce2453c82fatagen2026-09-24 13:07 UTC97
    loop {
31ce2453c82fatagen2026-09-24 13:07 UTC98
        let error = tokio::select! {
31ce2453c82fatagen2026-09-24 13:07 UTC99
            error = errors.recv() => error,
31ce2453c82fatagen2026-09-24 13:07 UTC100
            Some(_) = retries.join_next(), if !retries.is_empty() => continue,
31ce2453c82fatagen2026-09-24 13:07 UTC101
        };
31ce2453c82fatagen2026-09-24 13:07 UTC102
        match error {
31ce2453c82fatagen2026-09-24 13:07 UTC103
            Ok(error) if error.is_recoverable => {
31ce2453c82fatagen2026-09-24 13:07 UTC104
                let client = client.clone();
31ce2453c82fatagen2026-09-24 13:07 UTC105
                let online = online.clone();
31ce2453c82fatagen2026-09-24 13:07 UTC106
                retries.spawn(async move {
31ce2453c82fatagen2026-09-24 13:07 UTC107
                    tokio::time::sleep(SEND_QUEUE_RETRY).await;
31ce2453c82fatagen2026-09-24 13:07 UTC108
                    if *online.borrow() == ConnectionState::Online
31ce2453c82fatagen2026-09-24 13:07 UTC109
                        && let Some(room) = client.get_room(&error.room_id)
31ce2453c82fatagen2026-09-24 13:07 UTC110
                    {
31ce2453c82fatagen2026-09-24 13:07 UTC111
                        room.send_queue().set_enabled(true);
31ce2453c82fatagen2026-09-24 13:07 UTC112
                    }
31ce2453c82fatagen2026-09-24 13:07 UTC113
                });
31ce2453c82fatagen2026-09-24 13:07 UTC114
            }
31ce2453c82fatagen2026-09-24 13:07 UTC115
            Ok(_) | Err(RecvError::Lagged(_)) => {}
31ce2453c82fatagen2026-09-24 13:07 UTC116
            Err(RecvError::Closed) => return,
31ce2453c82fatagen2026-09-24 13:07 UTC117
        }
31ce2453c82fatagen2026-09-24 13:07 UTC118
    }
31ce2453c82fatagen2026-09-24 13:07 UTC119
}
31ce2453c82fatagen2026-09-24 13:07 UTC120
31ce2453c82fatagen2026-09-24 13:07 UTC121
/// upload progress only goes out on the room's own channel, never the client-wide one
31ce2453c82fatagen2026-09-24 13:07 UTC122
pub(super) async fn room_uploads(room: Room, events: EventSender, ready: watch::Sender<bool>) {
31ce2453c82fatagen2026-09-24 13:07 UTC123
    let room_id = room.room_id().to_owned();
31ce2453c82fatagen2026-09-24 13:07 UTC124
    let mut updates = match room.send_queue().subscribe().await {
31ce2453c82fatagen2026-09-24 13:07 UTC125
        Ok((_, updates)) => updates,
31ce2453c82fatagen2026-09-24 13:07 UTC126
        Err(error) => {
31ce2453c82fatagen2026-09-24 13:07 UTC127
            tracing::warn!(%room_id, %error, "reading the media send queue failed");
31ce2453c82fatagen2026-09-24 13:07 UTC128
            return;
31ce2453c82fatagen2026-09-24 13:07 UTC129
        }
31ce2453c82fatagen2026-09-24 13:07 UTC130
    };
31ce2453c82fatagen2026-09-24 13:07 UTC131
    ready.send_replace(true);
31ce2453c82fatagen2026-09-24 13:07 UTC132
    loop {
31ce2453c82fatagen2026-09-24 13:07 UTC133
        let update = match updates.recv().await {
31ce2453c82fatagen2026-09-24 13:07 UTC134
            Ok(update) => update,
31ce2453c82fatagen2026-09-24 13:07 UTC135
            Err(RecvError::Lagged(skipped)) => {
31ce2453c82fatagen2026-09-24 13:07 UTC136
                tracing::warn!(%room_id, skipped, "upload watcher lagged");
31ce2453c82fatagen2026-09-24 13:07 UTC137
                continue;
31ce2453c82fatagen2026-09-24 13:07 UTC138
            }
31ce2453c82fatagen2026-09-24 13:07 UTC139
            Err(RecvError::Closed) => return,
31ce2453c82fatagen2026-09-24 13:07 UTC140
        };
31ce2453c82fatagen2026-09-24 13:07 UTC141
        let event = match update {
31ce2453c82fatagen2026-09-24 13:07 UTC142
            RoomSendQueueUpdate::MediaUpload {
31ce2453c82fatagen2026-09-24 13:07 UTC143
                related_to,
31ce2453c82fatagen2026-09-24 13:07 UTC144
                file,
31ce2453c82fatagen2026-09-24 13:07 UTC145
                progress,
31ce2453c82fatagen2026-09-24 13:07 UTC146
                ..
31ce2453c82fatagen2026-09-24 13:07 UTC147
            } => Event::MediaUploadProgress {
31ce2453c82fatagen2026-09-24 13:07 UTC148
                room_id: room_id.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC149
                transaction_id: related_to,
31ce2453c82fatagen2026-09-24 13:07 UTC150
                uploaded_bytes: progress.current as u64,
31ce2453c82fatagen2026-09-24 13:07 UTC151
                total_bytes: progress.total as u64,
31ce2453c82fatagen2026-09-24 13:07 UTC152
                complete: file.is_some() && progress.current == progress.total,
31ce2453c82fatagen2026-09-24 13:07 UTC153
            },
31ce2453c82fatagen2026-09-24 13:07 UTC154
            RoomSendQueueUpdate::SendError {
31ce2453c82fatagen2026-09-24 13:07 UTC155
                transaction_id,
31ce2453c82fatagen2026-09-24 13:07 UTC156
                error,
31ce2453c82fatagen2026-09-24 13:07 UTC157
                is_recoverable,
31ce2453c82fatagen2026-09-24 13:07 UTC158
            } => {
31ce2453c82fatagen2026-09-24 13:07 UTC159
                let is_attachment = room
31ce2453c82fatagen2026-09-24 13:07 UTC160
                    .send_queue()
31ce2453c82fatagen2026-09-24 13:07 UTC161
                    .subscribe()
31ce2453c82fatagen2026-09-24 13:07 UTC162
                    .await
31ce2453c82fatagen2026-09-24 13:07 UTC163
                    .is_ok_and(|(echoes, _)| {
31ce2453c82fatagen2026-09-24 13:07 UTC164
                        echoes.iter().any(|echo| {
31ce2453c82fatagen2026-09-24 13:07 UTC165
                            echo.transaction_id == transaction_id
31ce2453c82fatagen2026-09-24 13:07 UTC166
                                && is_attachment_echo(&echo.content)
31ce2453c82fatagen2026-09-24 13:07 UTC167
                        })
31ce2453c82fatagen2026-09-24 13:07 UTC168
                    });
31ce2453c82fatagen2026-09-24 13:07 UTC169
                if !is_attachment {
31ce2453c82fatagen2026-09-24 13:07 UTC170
                    continue;
31ce2453c82fatagen2026-09-24 13:07 UTC171
                }
31ce2453c82fatagen2026-09-24 13:07 UTC172
                Event::MediaUploadFailed {
31ce2453c82fatagen2026-09-24 13:07 UTC173
                    room_id: room_id.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC174
                    transaction_id,
31ce2453c82fatagen2026-09-24 13:07 UTC175
                    kind: classify_sdk_error(&error),
31ce2453c82fatagen2026-09-24 13:07 UTC176
                    recoverable: is_recoverable,
31ce2453c82fatagen2026-09-24 13:07 UTC177
                }
31ce2453c82fatagen2026-09-24 13:07 UTC178
            }
31ce2453c82fatagen2026-09-24 13:07 UTC179
            _ => continue,
31ce2453c82fatagen2026-09-24 13:07 UTC180
        };
31ce2453c82fatagen2026-09-24 13:07 UTC181
        if events.send(event).await.is_err() {
31ce2453c82fatagen2026-09-24 13:07 UTC182
            return;
31ce2453c82fatagen2026-09-24 13:07 UTC183
        }
31ce2453c82fatagen2026-09-24 13:07 UTC184
    }
31ce2453c82fatagen2026-09-24 13:07 UTC185
}
31ce2453c82fatagen2026-09-24 13:07 UTC186
31ce2453c82fatagen2026-09-24 13:07 UTC187
fn is_attachment_echo(content: &LocalEchoContent) -> bool {
31ce2453c82fatagen2026-09-24 13:07 UTC188
    let LocalEchoContent::Event {
31ce2453c82fatagen2026-09-24 13:07 UTC189
        serialized_event, ..
31ce2453c82fatagen2026-09-24 13:07 UTC190
    } = content
31ce2453c82fatagen2026-09-24 13:07 UTC191
    else {
31ce2453c82fatagen2026-09-24 13:07 UTC192
        return false;
31ce2453c82fatagen2026-09-24 13:07 UTC193
    };
31ce2453c82fatagen2026-09-24 13:07 UTC194
    matches!(
31ce2453c82fatagen2026-09-24 13:07 UTC195
        serialized_event.deserialize(),
31ce2453c82fatagen2026-09-24 13:07 UTC196
        Ok(AnyMessageLikeEventContent::RoomMessage(content))
31ce2453c82fatagen2026-09-24 13:07 UTC197
            if matches!(
31ce2453c82fatagen2026-09-24 13:07 UTC198
                content.msgtype,
31ce2453c82fatagen2026-09-24 13:07 UTC199
                MessageType::Audio(_) | MessageType::File(_) | MessageType::Image(_) | MessageType::Video(_)
31ce2453c82fatagen2026-09-24 13:07 UTC200
            )
31ce2453c82fatagen2026-09-24 13:07 UTC201
    )
31ce2453c82fatagen2026-09-24 13:07 UTC202
}
31ce2453c82fatagen2026-09-24 13:07 UTC203
31ce2453c82fatagen2026-09-24 13:07 UTC204
pub(super) async fn encryption(client: Client, events: EventSender) {
31ce2453c82fatagen2026-09-24 13:07 UTC205
    let Some(device_id) = client.device_id().map(ToOwned::to_owned) else {
31ce2453c82fatagen2026-09-24 13:07 UTC206
        tracing::warn!("encryption watcher stopped because the client has no device id");
31ce2453c82fatagen2026-09-24 13:07 UTC207
        return;
31ce2453c82fatagen2026-09-24 13:07 UTC208
    };
31ce2453c82fatagen2026-09-24 13:07 UTC209
    let recovery = client.encryption().recovery();
31ce2453c82fatagen2026-09-24 13:07 UTC210
    let backups = client.encryption().backups();
31ce2453c82fatagen2026-09-24 13:07 UTC211
    let mut recovery_stream = recovery.state_stream();
31ce2453c82fatagen2026-09-24 13:07 UTC212
    let mut backup_stream = backups.state_stream();
31ce2453c82fatagen2026-09-24 13:07 UTC213
    let mut verification_stream = client.encryption().verification_state();
31ce2453c82fatagen2026-09-24 13:07 UTC214
    let mut recovery_state = recovery.state();
31ce2453c82fatagen2026-09-24 13:07 UTC215
    let mut backup_state = backups.state();
31ce2453c82fatagen2026-09-24 13:07 UTC216
    let mut verification_state = verification_stream.get();
31ce2453c82fatagen2026-09-24 13:07 UTC217
    loop {
31ce2453c82fatagen2026-09-24 13:07 UTC218
        let state = encryption_state(
31ce2453c82fatagen2026-09-24 13:07 UTC219
            device_id.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC220
            recovery_state,
31ce2453c82fatagen2026-09-24 13:07 UTC221
            verification_state,
31ce2453c82fatagen2026-09-24 13:07 UTC222
            backup_state,
31ce2453c82fatagen2026-09-24 13:07 UTC223
        );
31ce2453c82fatagen2026-09-24 13:07 UTC224
        if events.send(Event::EncryptionChanged(state)).await.is_err() {
31ce2453c82fatagen2026-09-24 13:07 UTC225
            return;
31ce2453c82fatagen2026-09-24 13:07 UTC226
        }
31ce2453c82fatagen2026-09-24 13:07 UTC227
        tokio::select! {
31ce2453c82fatagen2026-09-24 13:07 UTC228
            Some(update) = recovery_stream.next() => recovery_state = update,
31ce2453c82fatagen2026-09-24 13:07 UTC229
            Some(update) = verification_stream.next() => verification_state = update,
31ce2453c82fatagen2026-09-24 13:07 UTC230
            Some(update) = backup_stream.next() => {
31ce2453c82fatagen2026-09-24 13:07 UTC231
                backup_state = update.unwrap_or_else(|_| backups.state());
31ce2453c82fatagen2026-09-24 13:07 UTC232
            }
31ce2453c82fatagen2026-09-24 13:07 UTC233
            else => return,
31ce2453c82fatagen2026-09-24 13:07 UTC234
        }
31ce2453c82fatagen2026-09-24 13:07 UTC235
    }
31ce2453c82fatagen2026-09-24 13:07 UTC236
}
31ce2453c82fatagen2026-09-24 13:07 UTC237
31ce2453c82fatagen2026-09-24 13:07 UTC238
pub(super) async fn rooms(client: Client, events: EventSender) {
31ce2453c82fatagen2026-09-24 13:07 UTC239
    let (_, mut rooms) = client.rooms_stream();
31ce2453c82fatagen2026-09-24 13:07 UTC240
    let mut info = client.room_info_notable_update_receiver();
31ce2453c82fatagen2026-09-24 13:07 UTC241
    loop {
31ce2453c82fatagen2026-09-24 13:07 UTC242
        if events.send(Event::RoomsChanged).await.is_err() {
31ce2453c82fatagen2026-09-24 13:07 UTC243
            return;
31ce2453c82fatagen2026-09-24 13:07 UTC244
        }
31ce2453c82fatagen2026-09-24 13:07 UTC245
        tokio::select! {
31ce2453c82fatagen2026-09-24 13:07 UTC246
            update = rooms.next() => if update.is_none() { return },
31ce2453c82fatagen2026-09-24 13:07 UTC247
            update = info.recv() => if matches!(update, Err(RecvError::Closed)) { return },
31ce2453c82fatagen2026-09-24 13:07 UTC248
        }
31ce2453c82fatagen2026-09-24 13:07 UTC249
        // a sync touching fifty rooms is one invalidation, not fifty
31ce2453c82fatagen2026-09-24 13:07 UTC250
        while let Ok(_) | Err(tokio::sync::broadcast::error::TryRecvError::Lagged(_)) =
31ce2453c82fatagen2026-09-24 13:07 UTC251
            info.try_recv()
31ce2453c82fatagen2026-09-24 13:07 UTC252
        {}
31ce2453c82fatagen2026-09-24 13:07 UTC253
    }
31ce2453c82fatagen2026-09-24 13:07 UTC254
}
31ce2453c82fatagen2026-09-24 13:07 UTC255
31ce2453c82fatagen2026-09-24 13:07 UTC256
pub(super) async fn notification_settings(client: Client, events: EventSender) {
31ce2453c82fatagen2026-09-24 13:07 UTC257
    let settings = client.notification_settings().await;
31ce2453c82fatagen2026-09-24 13:07 UTC258
    let mut changes = settings.subscribe_to_changes();
31ce2453c82fatagen2026-09-24 13:07 UTC259
    while let Ok(()) | Err(RecvError::Lagged(_)) = changes.recv().await {
31ce2453c82fatagen2026-09-24 13:07 UTC260
        if events
31ce2453c82fatagen2026-09-24 13:07 UTC261
            .send(Event::NotificationSettingsChanged)
31ce2453c82fatagen2026-09-24 13:07 UTC262
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC263
            .is_err()
31ce2453c82fatagen2026-09-24 13:07 UTC264
        {
31ce2453c82fatagen2026-09-24 13:07 UTC265
            return;
31ce2453c82fatagen2026-09-24 13:07 UTC266
        }
31ce2453c82fatagen2026-09-24 13:07 UTC267
    }
31ce2453c82fatagen2026-09-24 13:07 UTC268
}