atagen

macltok

public
Star0
Watch0
1fe480c16a3c6db3270dce9e11f0afe67ebc17fc
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
src/engine/watchers.rsRust314 lines11.0 KB
1
use std::{sync::Arc, time::Duration};
2
3
use futures_util::StreamExt;
4
use matrix_sdk::{
5
    Client, Room, SessionChange,
6
    ruma::events::{AnyMessageLikeEventContent, room::message::MessageType},
7
    send_queue::{LocalEchoContent, RoomSendQueueUpdate},
8
};
9
use matrix_sdk_ui::sync_service::{State as SyncState, SyncService};
10
use ruma::time::Instant;
11
use tokio::sync::{Notify, broadcast::error::RecvError, watch};
12
13
use super::executor::TaskSet;
14
use crate::{
15
    ConnectionState, Event, encryption::encryption_state, error::classify_sdk_error,
16
    transport::EventSender,
17
};
18
19
const SYNC_RESTART_MIN: Duration = Duration::from_secs(1);
20
const SYNC_RESTART_MAX: Duration = Duration::from_secs(60);
21
const SEND_QUEUE_RETRY: Duration = Duration::from_secs(5);
22
const SYNC_STABLE: Duration = Duration::from_secs(30);
23
24
pub(super) async fn room_subscriptions(
25
    sync: Arc<SyncService>,
26
    mut rooms: watch::Receiver<Vec<ruma::OwnedRoomId>>,
27
) {
28
    while rooms.changed().await.is_ok() {
29
        let room_ids = rooms.borrow_and_update().clone();
30
        let room_ids = room_ids.iter().map(AsRef::as_ref).collect::<Vec<_>>();
31
        sync.room_list_service().subscribe_to_rooms(&room_ids).await;
32
    }
33
}
34
35
pub(super) async fn session(
36
    mut changes: tokio::sync::broadcast::Receiver<SessionChange>,
37
    events: EventSender,
38
    stop: Arc<Notify>,
39
) {
40
    loop {
41
        let event = match changes.recv().await {
42
            Ok(SessionChange::TokensRefreshed) => Event::SessionTokensChanged,
43
            Ok(SessionChange::UnknownToken(data)) => {
44
                let _ = events
45
                    .send(Event::SessionAccessRejected {
46
                        soft_logout: data.soft_logout,
47
                    })
48
                    .await;
49
                stop.notify_one();
50
                return;
51
            }
52
            // a missed refresh costs one needless save, a missed rejection comes round again
53
            Err(RecvError::Lagged(_)) => Event::SessionTokensChanged,
54
            Err(RecvError::Closed) => return,
55
        };
56
        if events.send(event).await.is_err() {
57
            return;
58
        }
59
    }
60
}
61
62
/// `states` must be taken before sync starts, a late subscriber never hears the transition it missed
63
pub(super) async fn connection(
64
    mut states: impl futures_util::Stream<Item = SyncState> + Unpin,
65
    sync: Arc<SyncService>,
66
    client: Client,
67
    events: EventSender,
68
    connection: watch::Sender<ConnectionState>,
69
) {
70
    let mut backoff = SYNC_RESTART_MIN;
71
    let mut online_since: Option<Instant> = None;
72
    while let Some(state) = states.next().await {
73
        // only a run that lasted counts as recovered, or a server that answers /versions
74
        // but refuses sync would have us bounce between offline and online forever
75
        let stable = online_since
76
            .take()
77
            .map(|since| since.elapsed() >= SYNC_STABLE);
78
        if stable == Some(true) {
79
            backoff = SYNC_RESTART_MIN;
80
        }
81
        let (state, restart) = match state {
82
            // only ever us stopping it for a restart, nothing a client needs to hear
83
            SyncState::Idle => continue,
84
            SyncState::Running => {
85
                online_since = Some(Instant::now());
86
                // the sdk parks a room queue on any send error and never unparks it
87
                client.send_queue().set_enabled(true).await;
88
                (ConnectionState::Online, false)
89
            }
90
            SyncState::Offline => (ConnectionState::Offline, stable == Some(false)),
91
            SyncState::Terminated => (ConnectionState::Offline, true),
92
            SyncState::Error(error) => {
93
                tracing::warn!(%error, "sync service reported an error");
94
                (ConnectionState::Offline, true)
95
            }
96
        };
97
        if *connection.borrow() != state {
98
            connection.send_replace(state);
99
            if events.send(Event::ConnectionChanged(state)).await.is_err() {
100
                return;
101
            }
102
        }
103
        if restart {
104
            sync.stop().await;
105
            matrix_sdk::sleep::sleep(backoff).await;
106
            backoff = (backoff * 2).min(SYNC_RESTART_MAX);
107
            sync.start().await;
108
        }
109
    }
110
}
111
112
/// a recoverable failure while we're online is a hiccup, so give the room another go
113
pub(super) async fn send_queue_errors(
114
    client: Client,
115
    mut errors: tokio::sync::broadcast::Receiver<matrix_sdk::send_queue::SendQueueRoomError>,
116
    online: watch::Receiver<ConnectionState>,
117
) {
118
    let mut retries = TaskSet::new();
119
    loop {
120
        let error = tokio::select! {
121
            error = errors.recv() => error,
122
            Some(_) = retries.join_next(), if !retries.is_empty() => continue,
123
        };
124
        match error {
125
            Ok(error) if error.is_recoverable => {
126
                let client = client.clone();
127
                let online = online.clone();
128
                retries.spawn(async move {
129
                    matrix_sdk::sleep::sleep(SEND_QUEUE_RETRY).await;
130
                    if *online.borrow() == ConnectionState::Online
131
                        && let Some(room) = client.get_room(&error.room_id)
132
                    {
133
                        room.send_queue().set_enabled(true);
134
                    }
135
                });
136
            }
137
            Ok(_) | Err(RecvError::Lagged(_)) => {}
138
            Err(RecvError::Closed) => return,
139
        }
140
    }
141
}
142
143
/// upload progress only goes out on the room's own channel, never the client-wide one
144
pub(super) async fn room_uploads(room: Room, events: EventSender, ready: watch::Sender<bool>) {
145
    let room_id = room.room_id().to_owned();
146
    let mut updates = match room.send_queue().subscribe().await {
147
        Ok((_, updates)) => updates,
148
        Err(error) => {
149
            tracing::warn!(%room_id, %error, "reading the media send queue failed");
150
            return;
151
        }
152
    };
153
    ready.send_replace(true);
154
    loop {
155
        let update = match updates.recv().await {
156
            Ok(update) => update,
157
            // a missed tick is fine, the next one says where it's got to
158
            Err(RecvError::Lagged(_)) => continue,
159
            Err(RecvError::Closed) => return,
160
        };
161
        let RoomSendQueueUpdate::MediaUpload {
162
            related_to,
163
            file,
164
            progress,
165
            ..
166
        } = update
167
        else {
168
            continue;
169
        };
170
        let event = Event::MediaUploadProgress {
171
            room_id: room_id.clone(),
172
            transaction_id: related_to,
173
            uploaded_bytes: progress.current as u64,
174
            total_bytes: progress.total as u64,
175
            complete: file.is_some() && progress.current == progress.total,
176
        };
177
        if events.send(event).await.is_err() {
178
            return;
179
        }
180
    }
181
}
182
183
/// failures do reach the client-wide channel, and this one exists before any upload does
184
pub(super) async fn upload_failures(
185
    client: Client,
186
    mut updates: tokio::sync::broadcast::Receiver<matrix_sdk::send_queue::SendQueueUpdate>,
187
    events: EventSender,
188
) {
189
    loop {
190
        let update = match updates.recv().await {
191
            Ok(update) => update,
192
            Err(RecvError::Lagged(skipped)) => {
193
                tracing::warn!(skipped, "upload failure watcher lagged");
194
                continue;
195
            }
196
            Err(RecvError::Closed) => return,
197
        };
198
        let RoomSendQueueUpdate::SendError {
199
            transaction_id,
200
            error,
201
            is_recoverable,
202
        } = update.update
203
        else {
204
            continue;
205
        };
206
        let Some(room) = client.get_room(&update.room_id) else {
207
            continue;
208
        };
209
        let is_attachment = room
210
            .send_queue()
211
            .subscribe()
212
            .await
213
            .is_ok_and(|(echoes, _)| {
214
                echoes.iter().any(|echo| {
215
                    echo.transaction_id == transaction_id && is_attachment_echo(&echo.content)
216
                })
217
            });
218
        if !is_attachment {
219
            continue;
220
        }
221
        let event = Event::MediaUploadFailed {
222
            room_id: update.room_id,
223
            transaction_id,
224
            kind: classify_sdk_error(&error),
225
            recoverable: is_recoverable,
226
        };
227
        if events.send(event).await.is_err() {
228
            return;
229
        }
230
    }
231
}
232
233
fn is_attachment_echo(content: &LocalEchoContent) -> bool {
234
    let LocalEchoContent::Event {
235
        serialized_event, ..
236
    } = content
237
    else {
238
        return false;
239
    };
240
    matches!(
241
        serialized_event.deserialize(),
242
        Ok(AnyMessageLikeEventContent::RoomMessage(content))
243
            if matches!(
244
                content.msgtype,
245
                MessageType::Audio(_) | MessageType::File(_) | MessageType::Image(_) | MessageType::Video(_)
246
            )
247
    )
248
}
249
250
pub(super) async fn encryption(client: Client, events: EventSender) {
251
    let Some(device_id) = client.device_id().map(ToOwned::to_owned) else {
252
        tracing::warn!("encryption watcher stopped because the client has no device id");
253
        return;
254
    };
255
    let recovery = client.encryption().recovery();
256
    let backups = client.encryption().backups();
257
    let mut recovery_stream = recovery.state_stream();
258
    let mut backup_stream = backups.state_stream();
259
    let mut verification_stream = client.encryption().verification_state();
260
    let mut recovery_state = recovery.state();
261
    let mut backup_state = backups.state();
262
    let mut verification_state = verification_stream.get();
263
    loop {
264
        let state = encryption_state(
265
            device_id.clone(),
266
            recovery_state,
267
            verification_state,
268
            backup_state,
269
        );
270
        if events.send(Event::EncryptionChanged(state)).await.is_err() {
271
            return;
272
        }
273
        tokio::select! {
274
            Some(update) = recovery_stream.next() => recovery_state = update,
275
            Some(update) = verification_stream.next() => verification_state = update,
276
            Some(update) = backup_stream.next() => {
277
                backup_state = update.unwrap_or_else(|_| backups.state());
278
            }
279
            else => return,
280
        }
281
    }
282
}
283
284
pub(super) async fn rooms(client: Client, events: EventSender) {
285
    let (_, mut rooms) = client.rooms_stream();
286
    let mut info = client.room_info_notable_update_receiver();
287
    loop {
288
        if events.send(Event::RoomsChanged).await.is_err() {
289
            return;
290
        }
291
        tokio::select! {
292
            update = rooms.next() => if update.is_none() { return },
293
            update = info.recv() => if matches!(update, Err(RecvError::Closed)) { return },
294
        }
295
        // a sync touching fifty rooms is one invalidation, not fifty
296
        while let Ok(_) | Err(tokio::sync::broadcast::error::TryRecvError::Lagged(_)) =
297
            info.try_recv()
298
        {}
299
    }
300
}
301
302
pub(super) async fn notification_settings(client: Client, events: EventSender) {
303
    let settings = client.notification_settings().await;
304
    let mut changes = settings.subscribe_to_changes();
305
    while let Ok(()) | Err(RecvError::Lagged(_)) = changes.recv().await {
306
        if events
307
            .send(Event::NotificationSettingsChanged)
308
            .await
309
            .is_err()
310
        {
311
            return;
312
        }
313
    }
314
}