atagen

macltok

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