atagen

macltok

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

Blame: src/engine/watchers.rs

0bfa3f46f0beatagen2026-09-26 09:12 UTC1
use std::{collections::HashSet, 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},
0bfa3f46f0beatagen2026-09-26 09:12 UTC7
    send_queue::{LocalEchoContent, RoomSendQueueUpdate, SendQueueUpdate},
31ce2453c82fatagen2026-09-24 13:07 UTC8
};
31ce2453c82fatagen2026-09-24 13:07 UTC9
use matrix_sdk_ui::sync_service::{State as SyncState, SyncService};
0bfa3f46f0beatagen2026-09-26 09:12 UTC10
use ruma::{OwnedDeviceId, OwnedRoomId, OwnedTransactionId, time::Instant};
fe4133dbcd47atagen2026-09-26 09:05 UTC11
use tokio::sync::{broadcast::error::RecvError, mpsc, watch};
31ce2453c82fatagen2026-09-24 13:07 UTC12
1fe480c16a3catagen2026-09-26 08:44 UTC13
use super::executor::TaskSet;
31ce2453c82fatagen2026-09-24 13:07 UTC14
use crate::{
0bfa3f46f0beatagen2026-09-26 09:12 UTC15
    ConnectionState, Event, Termination, UploadOutcome, encryption::encryption_state,
0bfa3f46f0beatagen2026-09-26 09:12 UTC16
    error::classify_sdk_error, transport::EventSender,
31ce2453c82fatagen2026-09-24 13:07 UTC17
};
31ce2453c82fatagen2026-09-24 13:07 UTC18
31ce2453c82fatagen2026-09-24 13:07 UTC19
const SYNC_RESTART_MIN: Duration = Duration::from_secs(1);
31ce2453c82fatagen2026-09-24 13:07 UTC20
const SYNC_RESTART_MAX: Duration = Duration::from_secs(60);
31ce2453c82fatagen2026-09-24 13:07 UTC21
const SEND_QUEUE_RETRY: Duration = Duration::from_secs(5);
5a52012c9b1datagen2026-09-24 23:45 UTC22
const SYNC_STABLE: Duration = Duration::from_secs(30);
5a52012c9b1datagen2026-09-24 23:45 UTC23
5a52012c9b1datagen2026-09-24 23:45 UTC24
pub(super) async fn room_subscriptions(
5a52012c9b1datagen2026-09-24 23:45 UTC25
    sync: Arc<SyncService>,
5a52012c9b1datagen2026-09-24 23:45 UTC26
    mut rooms: watch::Receiver<Vec<ruma::OwnedRoomId>>,
5a52012c9b1datagen2026-09-24 23:45 UTC27
) {
5a52012c9b1datagen2026-09-24 23:45 UTC28
    while rooms.changed().await.is_ok() {
5a52012c9b1datagen2026-09-24 23:45 UTC29
        let room_ids = rooms.borrow_and_update().clone();
5a52012c9b1datagen2026-09-24 23:45 UTC30
        let room_ids = room_ids.iter().map(AsRef::as_ref).collect::<Vec<_>>();
5a52012c9b1datagen2026-09-24 23:45 UTC31
        sync.room_list_service().subscribe_to_rooms(&room_ids).await;
5a52012c9b1datagen2026-09-24 23:45 UTC32
    }
5a52012c9b1datagen2026-09-24 23:45 UTC33
}
31ce2453c82fatagen2026-09-24 13:07 UTC34
31ce2453c82fatagen2026-09-24 13:07 UTC35
pub(super) async fn session(
31ce2453c82fatagen2026-09-24 13:07 UTC36
    mut changes: tokio::sync::broadcast::Receiver<SessionChange>,
31ce2453c82fatagen2026-09-24 13:07 UTC37
    events: EventSender,
fe4133dbcd47atagen2026-09-26 09:05 UTC38
    device_id: OwnedDeviceId,
fe4133dbcd47atagen2026-09-26 09:05 UTC39
    stop: mpsc::UnboundedSender<Termination>,
31ce2453c82fatagen2026-09-24 13:07 UTC40
) {
31ce2453c82fatagen2026-09-24 13:07 UTC41
    loop {
31ce2453c82fatagen2026-09-24 13:07 UTC42
        let event = match changes.recv().await {
31ce2453c82fatagen2026-09-24 13:07 UTC43
            Ok(SessionChange::TokensRefreshed) => Event::SessionTokensChanged,
31ce2453c82fatagen2026-09-24 13:07 UTC44
            Ok(SessionChange::UnknownToken(data)) => {
31ce2453c82fatagen2026-09-24 13:07 UTC45
                let _ = events
31ce2453c82fatagen2026-09-24 13:07 UTC46
                    .send(Event::SessionAccessRejected {
31ce2453c82fatagen2026-09-24 13:07 UTC47
                        soft_logout: data.soft_logout,
31ce2453c82fatagen2026-09-24 13:07 UTC48
                    })
31ce2453c82fatagen2026-09-24 13:07 UTC49
                    .await;
fe4133dbcd47atagen2026-09-26 09:05 UTC50
                let _ = stop.send(if data.soft_logout {
fe4133dbcd47atagen2026-09-26 09:05 UTC51
                    Termination::SoftLoggedOut { device_id }
fe4133dbcd47atagen2026-09-26 09:05 UTC52
                } else {
fe4133dbcd47atagen2026-09-26 09:05 UTC53
                    Termination::AccessRevoked
fe4133dbcd47atagen2026-09-26 09:05 UTC54
                });
31ce2453c82fatagen2026-09-24 13:07 UTC55
                return;
31ce2453c82fatagen2026-09-24 13:07 UTC56
            }
31ce2453c82fatagen2026-09-24 13:07 UTC57
            // a missed refresh costs one needless save, a missed rejection comes round again
31ce2453c82fatagen2026-09-24 13:07 UTC58
            Err(RecvError::Lagged(_)) => Event::SessionTokensChanged,
31ce2453c82fatagen2026-09-24 13:07 UTC59
            Err(RecvError::Closed) => return,
31ce2453c82fatagen2026-09-24 13:07 UTC60
        };
31ce2453c82fatagen2026-09-24 13:07 UTC61
        if events.send(event).await.is_err() {
31ce2453c82fatagen2026-09-24 13:07 UTC62
            return;
31ce2453c82fatagen2026-09-24 13:07 UTC63
        }
31ce2453c82fatagen2026-09-24 13:07 UTC64
    }
31ce2453c82fatagen2026-09-24 13:07 UTC65
}
31ce2453c82fatagen2026-09-24 13:07 UTC66
31ce2453c82fatagen2026-09-24 13:07 UTC67
/// `states` must be taken before sync starts, a late subscriber never hears the transition it missed
31ce2453c82fatagen2026-09-24 13:07 UTC68
pub(super) async fn connection(
31ce2453c82fatagen2026-09-24 13:07 UTC69
    mut states: impl futures_util::Stream<Item = SyncState> + Unpin,
31ce2453c82fatagen2026-09-24 13:07 UTC70
    sync: Arc<SyncService>,
31ce2453c82fatagen2026-09-24 13:07 UTC71
    client: Client,
31ce2453c82fatagen2026-09-24 13:07 UTC72
    events: EventSender,
31ce2453c82fatagen2026-09-24 13:07 UTC73
    connection: watch::Sender<ConnectionState>,
31ce2453c82fatagen2026-09-24 13:07 UTC74
) {
31ce2453c82fatagen2026-09-24 13:07 UTC75
    let mut backoff = SYNC_RESTART_MIN;
5a52012c9b1datagen2026-09-24 23:45 UTC76
    let mut online_since: Option<Instant> = None;
31ce2453c82fatagen2026-09-24 13:07 UTC77
    while let Some(state) = states.next().await {
5a52012c9b1datagen2026-09-24 23:45 UTC78
        // only a run that lasted counts as recovered, or a server that answers /versions
5a52012c9b1datagen2026-09-24 23:45 UTC79
        // but refuses sync would have us bounce between offline and online forever
5a52012c9b1datagen2026-09-24 23:45 UTC80
        let stable = online_since
5a52012c9b1datagen2026-09-24 23:45 UTC81
            .take()
5a52012c9b1datagen2026-09-24 23:45 UTC82
            .map(|since| since.elapsed() >= SYNC_STABLE);
5a52012c9b1datagen2026-09-24 23:45 UTC83
        if stable == Some(true) {
5a52012c9b1datagen2026-09-24 23:45 UTC84
            backoff = SYNC_RESTART_MIN;
5a52012c9b1datagen2026-09-24 23:45 UTC85
        }
31ce2453c82fatagen2026-09-24 13:07 UTC86
        let (state, restart) = match state {
5a52012c9b1datagen2026-09-24 23:45 UTC87
            // only ever us stopping it for a restart, nothing a client needs to hear
5a52012c9b1datagen2026-09-24 23:45 UTC88
            SyncState::Idle => continue,
31ce2453c82fatagen2026-09-24 13:07 UTC89
            SyncState::Running => {
5a52012c9b1datagen2026-09-24 23:45 UTC90
                online_since = Some(Instant::now());
31ce2453c82fatagen2026-09-24 13:07 UTC91
                // the sdk parks a room queue on any send error and never unparks it
31ce2453c82fatagen2026-09-24 13:07 UTC92
                client.send_queue().set_enabled(true).await;
31ce2453c82fatagen2026-09-24 13:07 UTC93
                (ConnectionState::Online, false)
31ce2453c82fatagen2026-09-24 13:07 UTC94
            }
5a52012c9b1datagen2026-09-24 23:45 UTC95
            SyncState::Offline => (ConnectionState::Offline, stable == Some(false)),
31ce2453c82fatagen2026-09-24 13:07 UTC96
            SyncState::Terminated => (ConnectionState::Offline, true),
31ce2453c82fatagen2026-09-24 13:07 UTC97
            SyncState::Error(error) => {
31ce2453c82fatagen2026-09-24 13:07 UTC98
                tracing::warn!(%error, "sync service reported an error");
31ce2453c82fatagen2026-09-24 13:07 UTC99
                (ConnectionState::Offline, true)
31ce2453c82fatagen2026-09-24 13:07 UTC100
            }
31ce2453c82fatagen2026-09-24 13:07 UTC101
        };
31ce2453c82fatagen2026-09-24 13:07 UTC102
        if *connection.borrow() != state {
31ce2453c82fatagen2026-09-24 13:07 UTC103
            connection.send_replace(state);
31ce2453c82fatagen2026-09-24 13:07 UTC104
            if events.send(Event::ConnectionChanged(state)).await.is_err() {
31ce2453c82fatagen2026-09-24 13:07 UTC105
                return;
31ce2453c82fatagen2026-09-24 13:07 UTC106
            }
31ce2453c82fatagen2026-09-24 13:07 UTC107
        }
31ce2453c82fatagen2026-09-24 13:07 UTC108
        if restart {
5a52012c9b1datagen2026-09-24 23:45 UTC109
            sync.stop().await;
1fe480c16a3catagen2026-09-26 08:44 UTC110
            matrix_sdk::sleep::sleep(backoff).await;
31ce2453c82fatagen2026-09-24 13:07 UTC111
            backoff = (backoff * 2).min(SYNC_RESTART_MAX);
31ce2453c82fatagen2026-09-24 13:07 UTC112
            sync.start().await;
31ce2453c82fatagen2026-09-24 13:07 UTC113
        }
31ce2453c82fatagen2026-09-24 13:07 UTC114
    }
31ce2453c82fatagen2026-09-24 13:07 UTC115
}
31ce2453c82fatagen2026-09-24 13:07 UTC116
31ce2453c82fatagen2026-09-24 13:07 UTC117
/// a recoverable failure while we're online is a hiccup, so give the room another go
31ce2453c82fatagen2026-09-24 13:07 UTC118
pub(super) async fn send_queue_errors(
31ce2453c82fatagen2026-09-24 13:07 UTC119
    client: Client,
31ce2453c82fatagen2026-09-24 13:07 UTC120
    mut errors: tokio::sync::broadcast::Receiver<matrix_sdk::send_queue::SendQueueRoomError>,
31ce2453c82fatagen2026-09-24 13:07 UTC121
    online: watch::Receiver<ConnectionState>,
31ce2453c82fatagen2026-09-24 13:07 UTC122
) {
1fe480c16a3catagen2026-09-26 08:44 UTC123
    let mut retries = TaskSet::new();
31ce2453c82fatagen2026-09-24 13:07 UTC124
    loop {
31ce2453c82fatagen2026-09-24 13:07 UTC125
        let error = tokio::select! {
31ce2453c82fatagen2026-09-24 13:07 UTC126
            error = errors.recv() => error,
31ce2453c82fatagen2026-09-24 13:07 UTC127
            Some(_) = retries.join_next(), if !retries.is_empty() => continue,
31ce2453c82fatagen2026-09-24 13:07 UTC128
        };
31ce2453c82fatagen2026-09-24 13:07 UTC129
        match error {
31ce2453c82fatagen2026-09-24 13:07 UTC130
            Ok(error) if error.is_recoverable => {
31ce2453c82fatagen2026-09-24 13:07 UTC131
                let client = client.clone();
31ce2453c82fatagen2026-09-24 13:07 UTC132
                let online = online.clone();
31ce2453c82fatagen2026-09-24 13:07 UTC133
                retries.spawn(async move {
1fe480c16a3catagen2026-09-26 08:44 UTC134
                    matrix_sdk::sleep::sleep(SEND_QUEUE_RETRY).await;
31ce2453c82fatagen2026-09-24 13:07 UTC135
                    if *online.borrow() == ConnectionState::Online
31ce2453c82fatagen2026-09-24 13:07 UTC136
                        && let Some(room) = client.get_room(&error.room_id)
31ce2453c82fatagen2026-09-24 13:07 UTC137
                    {
31ce2453c82fatagen2026-09-24 13:07 UTC138
                        room.send_queue().set_enabled(true);
31ce2453c82fatagen2026-09-24 13:07 UTC139
                    }
31ce2453c82fatagen2026-09-24 13:07 UTC140
                });
31ce2453c82fatagen2026-09-24 13:07 UTC141
            }
31ce2453c82fatagen2026-09-24 13:07 UTC142
            Ok(_) | Err(RecvError::Lagged(_)) => {}
31ce2453c82fatagen2026-09-24 13:07 UTC143
            Err(RecvError::Closed) => return,
31ce2453c82fatagen2026-09-24 13:07 UTC144
        }
31ce2453c82fatagen2026-09-24 13:07 UTC145
    }
31ce2453c82fatagen2026-09-24 13:07 UTC146
}
31ce2453c82fatagen2026-09-24 13:07 UTC147
31ce2453c82fatagen2026-09-24 13:07 UTC148
/// upload progress only goes out on the room's own channel, never the client-wide one
31ce2453c82fatagen2026-09-24 13:07 UTC149
pub(super) async fn room_uploads(room: Room, events: EventSender, ready: watch::Sender<bool>) {
31ce2453c82fatagen2026-09-24 13:07 UTC150
    let room_id = room.room_id().to_owned();
31ce2453c82fatagen2026-09-24 13:07 UTC151
    let mut updates = match room.send_queue().subscribe().await {
31ce2453c82fatagen2026-09-24 13:07 UTC152
        Ok((_, updates)) => updates,
31ce2453c82fatagen2026-09-24 13:07 UTC153
        Err(error) => {
31ce2453c82fatagen2026-09-24 13:07 UTC154
            tracing::warn!(%room_id, %error, "reading the media send queue failed");
31ce2453c82fatagen2026-09-24 13:07 UTC155
            return;
31ce2453c82fatagen2026-09-24 13:07 UTC156
        }
31ce2453c82fatagen2026-09-24 13:07 UTC157
    };
31ce2453c82fatagen2026-09-24 13:07 UTC158
    ready.send_replace(true);
31ce2453c82fatagen2026-09-24 13:07 UTC159
    loop {
31ce2453c82fatagen2026-09-24 13:07 UTC160
        let update = match updates.recv().await {
31ce2453c82fatagen2026-09-24 13:07 UTC161
            Ok(update) => update,
5a52012c9b1datagen2026-09-24 23:45 UTC162
            // a missed tick is fine, the next one says where it's got to
5a52012c9b1datagen2026-09-24 23:45 UTC163
            Err(RecvError::Lagged(_)) => continue,
5a52012c9b1datagen2026-09-24 23:45 UTC164
            Err(RecvError::Closed) => return,
5a52012c9b1datagen2026-09-24 23:45 UTC165
        };
5a52012c9b1datagen2026-09-24 23:45 UTC166
        let RoomSendQueueUpdate::MediaUpload {
5a52012c9b1datagen2026-09-24 23:45 UTC167
            related_to,
5a52012c9b1datagen2026-09-24 23:45 UTC168
            file,
5a52012c9b1datagen2026-09-24 23:45 UTC169
            progress,
5a52012c9b1datagen2026-09-24 23:45 UTC170
            ..
5a52012c9b1datagen2026-09-24 23:45 UTC171
        } = update
5a52012c9b1datagen2026-09-24 23:45 UTC172
        else {
5a52012c9b1datagen2026-09-24 23:45 UTC173
            continue;
5a52012c9b1datagen2026-09-24 23:45 UTC174
        };
5a52012c9b1datagen2026-09-24 23:45 UTC175
        let event = Event::MediaUploadProgress {
5a52012c9b1datagen2026-09-24 23:45 UTC176
            room_id: room_id.clone(),
5a52012c9b1datagen2026-09-24 23:45 UTC177
            transaction_id: related_to,
5a52012c9b1datagen2026-09-24 23:45 UTC178
            uploaded_bytes: progress.current as u64,
5a52012c9b1datagen2026-09-24 23:45 UTC179
            total_bytes: progress.total as u64,
5a52012c9b1datagen2026-09-24 23:45 UTC180
            complete: file.is_some() && progress.current == progress.total,
5a52012c9b1datagen2026-09-24 23:45 UTC181
        };
5a52012c9b1datagen2026-09-24 23:45 UTC182
        if events.send(event).await.is_err() {
5a52012c9b1datagen2026-09-24 23:45 UTC183
            return;
5a52012c9b1datagen2026-09-24 23:45 UTC184
        }
5a52012c9b1datagen2026-09-24 23:45 UTC185
    }
5a52012c9b1datagen2026-09-24 23:45 UTC186
}
5a52012c9b1datagen2026-09-24 23:45 UTC187
0bfa3f46f0beatagen2026-09-26 09:12 UTC188
/// progress only goes out on each room's own channel, everything else reaches this
0bfa3f46f0beatagen2026-09-26 09:12 UTC189
/// one, which exists before any upload does
0bfa3f46f0beatagen2026-09-26 09:12 UTC190
pub(super) async fn uploads(
5a52012c9b1datagen2026-09-24 23:45 UTC191
    client: Client,
0bfa3f46f0beatagen2026-09-26 09:12 UTC192
    mut updates: tokio::sync::broadcast::Receiver<SendQueueUpdate>,
5a52012c9b1datagen2026-09-24 23:45 UTC193
    events: EventSender,
5a52012c9b1datagen2026-09-24 23:45 UTC194
) {
0bfa3f46f0beatagen2026-09-26 09:12 UTC195
    let (_, mut rooms) = client.rooms_stream();
0bfa3f46f0beatagen2026-09-26 09:12 UTC196
    let mut uploads = attachment_echoes(&client).await;
5a52012c9b1datagen2026-09-24 23:45 UTC197
    loop {
0bfa3f46f0beatagen2026-09-26 09:12 UTC198
        let finished = tokio::select! {
0bfa3f46f0beatagen2026-09-26 09:12 UTC199
            update = updates.recv() => match update {
0bfa3f46f0beatagen2026-09-26 09:12 UTC200
                Ok(update) => match upload_update(&mut uploads, update) {
0bfa3f46f0beatagen2026-09-26 09:12 UTC201
                    Some(event) => vec![event],
0bfa3f46f0beatagen2026-09-26 09:12 UTC202
                    None => continue,
0bfa3f46f0beatagen2026-09-26 09:12 UTC203
                },
0bfa3f46f0beatagen2026-09-26 09:12 UTC204
                Err(RecvError::Lagged(skipped)) => {
0bfa3f46f0beatagen2026-09-26 09:12 UTC205
                    tracing::warn!(skipped, "upload watcher lagged");
0bfa3f46f0beatagen2026-09-26 09:12 UTC206
                    let current = attachment_echoes(&client).await;
0bfa3f46f0beatagen2026-09-26 09:12 UTC207
                    let missed = uploads.difference(&current).cloned().collect::<Vec<_>>();
0bfa3f46f0beatagen2026-09-26 09:12 UTC208
                    uploads = current;
0bfa3f46f0beatagen2026-09-26 09:12 UTC209
                    finished_uploads(missed, UploadOutcome::Missed)
0bfa3f46f0beatagen2026-09-26 09:12 UTC210
                }
0bfa3f46f0beatagen2026-09-26 09:12 UTC211
                Err(RecvError::Closed) => return,
0bfa3f46f0beatagen2026-09-26 09:12 UTC212
            },
0bfa3f46f0beatagen2026-09-26 09:12 UTC213
            update = rooms.next() => {
0bfa3f46f0beatagen2026-09-26 09:12 UTC214
                if update.is_none() {
0bfa3f46f0beatagen2026-09-26 09:12 UTC215
                    return;
0bfa3f46f0beatagen2026-09-26 09:12 UTC216
                }
0bfa3f46f0beatagen2026-09-26 09:12 UTC217
                let forgotten = uploads
0bfa3f46f0beatagen2026-09-26 09:12 UTC218
                    .iter()
0bfa3f46f0beatagen2026-09-26 09:12 UTC219
                    .filter(|(room_id, _)| client.get_room(room_id).is_none())
0bfa3f46f0beatagen2026-09-26 09:12 UTC220
                    .cloned()
0bfa3f46f0beatagen2026-09-26 09:12 UTC221
                    .collect::<Vec<_>>();
0bfa3f46f0beatagen2026-09-26 09:12 UTC222
                for upload in &forgotten {
0bfa3f46f0beatagen2026-09-26 09:12 UTC223
                    uploads.remove(upload);
0bfa3f46f0beatagen2026-09-26 09:12 UTC224
                }
0bfa3f46f0beatagen2026-09-26 09:12 UTC225
                finished_uploads(forgotten, UploadOutcome::RoomForgotten)
31ce2453c82fatagen2026-09-24 13:07 UTC226
            }
31ce2453c82fatagen2026-09-24 13:07 UTC227
        };
0bfa3f46f0beatagen2026-09-26 09:12 UTC228
        for event in finished {
0bfa3f46f0beatagen2026-09-26 09:12 UTC229
            if events.send(event).await.is_err() {
0bfa3f46f0beatagen2026-09-26 09:12 UTC230
                return;
0bfa3f46f0beatagen2026-09-26 09:12 UTC231
            }
0bfa3f46f0beatagen2026-09-26 09:12 UTC232
        }
0bfa3f46f0beatagen2026-09-26 09:12 UTC233
    }
0bfa3f46f0beatagen2026-09-26 09:12 UTC234
}
0bfa3f46f0beatagen2026-09-26 09:12 UTC235
0bfa3f46f0beatagen2026-09-26 09:12 UTC236
type Upload = (OwnedRoomId, OwnedTransactionId);
0bfa3f46f0beatagen2026-09-26 09:12 UTC237
0bfa3f46f0beatagen2026-09-26 09:12 UTC238
async fn attachment_echoes(client: &Client) -> HashSet<Upload> {
0bfa3f46f0beatagen2026-09-26 09:12 UTC239
    let Ok(echoes) = client.send_queue().local_echoes().await else {
0bfa3f46f0beatagen2026-09-26 09:12 UTC240
        return HashSet::new();
0bfa3f46f0beatagen2026-09-26 09:12 UTC241
    };
0bfa3f46f0beatagen2026-09-26 09:12 UTC242
    echoes
0bfa3f46f0beatagen2026-09-26 09:12 UTC243
        .into_iter()
0bfa3f46f0beatagen2026-09-26 09:12 UTC244
        .flat_map(|(room_id, echoes)| {
0bfa3f46f0beatagen2026-09-26 09:12 UTC245
            echoes
0bfa3f46f0beatagen2026-09-26 09:12 UTC246
                .into_iter()
0bfa3f46f0beatagen2026-09-26 09:12 UTC247
                .filter(|echo| is_attachment_echo(&echo.content))
0bfa3f46f0beatagen2026-09-26 09:12 UTC248
                .map(move |echo| (room_id.clone(), echo.transaction_id))
0bfa3f46f0beatagen2026-09-26 09:12 UTC249
        })
0bfa3f46f0beatagen2026-09-26 09:12 UTC250
        .collect()
0bfa3f46f0beatagen2026-09-26 09:12 UTC251
}
0bfa3f46f0beatagen2026-09-26 09:12 UTC252
0bfa3f46f0beatagen2026-09-26 09:12 UTC253
fn upload_update(uploads: &mut HashSet<Upload>, update: SendQueueUpdate) -> Option<Event> {
0bfa3f46f0beatagen2026-09-26 09:12 UTC254
    let room_id = update.room_id;
0bfa3f46f0beatagen2026-09-26 09:12 UTC255
    let (transaction_id, outcome) = match update.update {
0bfa3f46f0beatagen2026-09-26 09:12 UTC256
        RoomSendQueueUpdate::NewLocalEvent(echo) => {
0bfa3f46f0beatagen2026-09-26 09:12 UTC257
            if is_attachment_echo(&echo.content) {
0bfa3f46f0beatagen2026-09-26 09:12 UTC258
                uploads.insert((room_id, echo.transaction_id));
0bfa3f46f0beatagen2026-09-26 09:12 UTC259
            }
0bfa3f46f0beatagen2026-09-26 09:12 UTC260
            return None;
0bfa3f46f0beatagen2026-09-26 09:12 UTC261
        }
0bfa3f46f0beatagen2026-09-26 09:12 UTC262
        RoomSendQueueUpdate::SendError {
5a52012c9b1datagen2026-09-24 23:45 UTC263
            transaction_id,
5a52012c9b1datagen2026-09-24 23:45 UTC264
            error,
5a52012c9b1datagen2026-09-24 23:45 UTC265
            is_recoverable,
0bfa3f46f0beatagen2026-09-26 09:12 UTC266
        } => {
0bfa3f46f0beatagen2026-09-26 09:12 UTC267
            let upload = (room_id, transaction_id);
0bfa3f46f0beatagen2026-09-26 09:12 UTC268
            if !uploads.contains(&upload) {
0bfa3f46f0beatagen2026-09-26 09:12 UTC269
                return None;
0bfa3f46f0beatagen2026-09-26 09:12 UTC270
            }
0bfa3f46f0beatagen2026-09-26 09:12 UTC271
            let (room_id, transaction_id) = upload;
0bfa3f46f0beatagen2026-09-26 09:12 UTC272
            return Some(Event::MediaUploadFailed {
0bfa3f46f0beatagen2026-09-26 09:12 UTC273
                room_id,
0bfa3f46f0beatagen2026-09-26 09:12 UTC274
                transaction_id,
0bfa3f46f0beatagen2026-09-26 09:12 UTC275
                kind: classify_sdk_error(&error),
0bfa3f46f0beatagen2026-09-26 09:12 UTC276
                will_retry: is_recoverable,
5a52012c9b1datagen2026-09-24 23:45 UTC277
            });
5a52012c9b1datagen2026-09-24 23:45 UTC278
        }
0bfa3f46f0beatagen2026-09-26 09:12 UTC279
        RoomSendQueueUpdate::SentEvent {
5a52012c9b1datagen2026-09-24 23:45 UTC280
            transaction_id,
0bfa3f46f0beatagen2026-09-26 09:12 UTC281
            event_id,
0bfa3f46f0beatagen2026-09-26 09:12 UTC282
        } => (transaction_id, UploadOutcome::Sent { event_id }),
0bfa3f46f0beatagen2026-09-26 09:12 UTC283
        RoomSendQueueUpdate::CancelledLocalEvent { transaction_id } => {
0bfa3f46f0beatagen2026-09-26 09:12 UTC284
            (transaction_id, UploadOutcome::Cancelled)
31ce2453c82fatagen2026-09-24 13:07 UTC285
        }
0bfa3f46f0beatagen2026-09-26 09:12 UTC286
        _ => return None,
0bfa3f46f0beatagen2026-09-26 09:12 UTC287
    };
0bfa3f46f0beatagen2026-09-26 09:12 UTC288
    let upload = (room_id, transaction_id);
0bfa3f46f0beatagen2026-09-26 09:12 UTC289
    if !uploads.remove(&upload) {
0bfa3f46f0beatagen2026-09-26 09:12 UTC290
        return None;
31ce2453c82fatagen2026-09-24 13:07 UTC291
    }
0bfa3f46f0beatagen2026-09-26 09:12 UTC292
    let (room_id, transaction_id) = upload;
0bfa3f46f0beatagen2026-09-26 09:12 UTC293
    Some(Event::MediaUploadFinished {
0bfa3f46f0beatagen2026-09-26 09:12 UTC294
        room_id,
0bfa3f46f0beatagen2026-09-26 09:12 UTC295
        transaction_id,
0bfa3f46f0beatagen2026-09-26 09:12 UTC296
        outcome,
0bfa3f46f0beatagen2026-09-26 09:12 UTC297
    })
0bfa3f46f0beatagen2026-09-26 09:12 UTC298
}
0bfa3f46f0beatagen2026-09-26 09:12 UTC299
0bfa3f46f0beatagen2026-09-26 09:12 UTC300
fn finished_uploads(uploads: Vec<Upload>, outcome: UploadOutcome) -> Vec<Event> {
0bfa3f46f0beatagen2026-09-26 09:12 UTC301
    uploads
0bfa3f46f0beatagen2026-09-26 09:12 UTC302
        .into_iter()
0bfa3f46f0beatagen2026-09-26 09:12 UTC303
        .map(|(room_id, transaction_id)| Event::MediaUploadFinished {
0bfa3f46f0beatagen2026-09-26 09:12 UTC304
            room_id,
0bfa3f46f0beatagen2026-09-26 09:12 UTC305
            transaction_id,
0bfa3f46f0beatagen2026-09-26 09:12 UTC306
            outcome: outcome.clone(),
0bfa3f46f0beatagen2026-09-26 09:12 UTC307
        })
0bfa3f46f0beatagen2026-09-26 09:12 UTC308
        .collect()
31ce2453c82fatagen2026-09-24 13:07 UTC309
}
31ce2453c82fatagen2026-09-24 13:07 UTC310
31ce2453c82fatagen2026-09-24 13:07 UTC311
fn is_attachment_echo(content: &LocalEchoContent) -> bool {
31ce2453c82fatagen2026-09-24 13:07 UTC312
    let LocalEchoContent::Event {
31ce2453c82fatagen2026-09-24 13:07 UTC313
        serialized_event, ..
31ce2453c82fatagen2026-09-24 13:07 UTC314
    } = content
31ce2453c82fatagen2026-09-24 13:07 UTC315
    else {
31ce2453c82fatagen2026-09-24 13:07 UTC316
        return false;
31ce2453c82fatagen2026-09-24 13:07 UTC317
    };
31ce2453c82fatagen2026-09-24 13:07 UTC318
    matches!(
31ce2453c82fatagen2026-09-24 13:07 UTC319
        serialized_event.deserialize(),
31ce2453c82fatagen2026-09-24 13:07 UTC320
        Ok(AnyMessageLikeEventContent::RoomMessage(content))
31ce2453c82fatagen2026-09-24 13:07 UTC321
            if matches!(
31ce2453c82fatagen2026-09-24 13:07 UTC322
                content.msgtype,
31ce2453c82fatagen2026-09-24 13:07 UTC323
                MessageType::Audio(_) | MessageType::File(_) | MessageType::Image(_) | MessageType::Video(_)
31ce2453c82fatagen2026-09-24 13:07 UTC324
            )
31ce2453c82fatagen2026-09-24 13:07 UTC325
    )
31ce2453c82fatagen2026-09-24 13:07 UTC326
}
31ce2453c82fatagen2026-09-24 13:07 UTC327
31ce2453c82fatagen2026-09-24 13:07 UTC328
pub(super) async fn encryption(client: Client, events: EventSender) {
31ce2453c82fatagen2026-09-24 13:07 UTC329
    let Some(device_id) = client.device_id().map(ToOwned::to_owned) else {
31ce2453c82fatagen2026-09-24 13:07 UTC330
        tracing::warn!("encryption watcher stopped because the client has no device id");
31ce2453c82fatagen2026-09-24 13:07 UTC331
        return;
31ce2453c82fatagen2026-09-24 13:07 UTC332
    };
31ce2453c82fatagen2026-09-24 13:07 UTC333
    let recovery = client.encryption().recovery();
31ce2453c82fatagen2026-09-24 13:07 UTC334
    let backups = client.encryption().backups();
31ce2453c82fatagen2026-09-24 13:07 UTC335
    let mut recovery_stream = recovery.state_stream();
31ce2453c82fatagen2026-09-24 13:07 UTC336
    let mut backup_stream = backups.state_stream();
31ce2453c82fatagen2026-09-24 13:07 UTC337
    let mut verification_stream = client.encryption().verification_state();
31ce2453c82fatagen2026-09-24 13:07 UTC338
    let mut recovery_state = recovery.state();
31ce2453c82fatagen2026-09-24 13:07 UTC339
    let mut backup_state = backups.state();
31ce2453c82fatagen2026-09-24 13:07 UTC340
    let mut verification_state = verification_stream.get();
31ce2453c82fatagen2026-09-24 13:07 UTC341
    loop {
31ce2453c82fatagen2026-09-24 13:07 UTC342
        let state = encryption_state(
31ce2453c82fatagen2026-09-24 13:07 UTC343
            device_id.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC344
            recovery_state,
31ce2453c82fatagen2026-09-24 13:07 UTC345
            verification_state,
31ce2453c82fatagen2026-09-24 13:07 UTC346
            backup_state,
31ce2453c82fatagen2026-09-24 13:07 UTC347
        );
31ce2453c82fatagen2026-09-24 13:07 UTC348
        if events.send(Event::EncryptionChanged(state)).await.is_err() {
31ce2453c82fatagen2026-09-24 13:07 UTC349
            return;
31ce2453c82fatagen2026-09-24 13:07 UTC350
        }
31ce2453c82fatagen2026-09-24 13:07 UTC351
        tokio::select! {
31ce2453c82fatagen2026-09-24 13:07 UTC352
            Some(update) = recovery_stream.next() => recovery_state = update,
31ce2453c82fatagen2026-09-24 13:07 UTC353
            Some(update) = verification_stream.next() => verification_state = update,
31ce2453c82fatagen2026-09-24 13:07 UTC354
            Some(update) = backup_stream.next() => {
31ce2453c82fatagen2026-09-24 13:07 UTC355
                backup_state = update.unwrap_or_else(|_| backups.state());
31ce2453c82fatagen2026-09-24 13:07 UTC356
            }
31ce2453c82fatagen2026-09-24 13:07 UTC357
            else => return,
31ce2453c82fatagen2026-09-24 13:07 UTC358
        }
31ce2453c82fatagen2026-09-24 13:07 UTC359
    }
31ce2453c82fatagen2026-09-24 13:07 UTC360
}
31ce2453c82fatagen2026-09-24 13:07 UTC361
31ce2453c82fatagen2026-09-24 13:07 UTC362
pub(super) async fn rooms(client: Client, events: EventSender) {
31ce2453c82fatagen2026-09-24 13:07 UTC363
    let (_, mut rooms) = client.rooms_stream();
31ce2453c82fatagen2026-09-24 13:07 UTC364
    let mut info = client.room_info_notable_update_receiver();
31ce2453c82fatagen2026-09-24 13:07 UTC365
    loop {
31ce2453c82fatagen2026-09-24 13:07 UTC366
        if events.send(Event::RoomsChanged).await.is_err() {
31ce2453c82fatagen2026-09-24 13:07 UTC367
            return;
31ce2453c82fatagen2026-09-24 13:07 UTC368
        }
31ce2453c82fatagen2026-09-24 13:07 UTC369
        tokio::select! {
31ce2453c82fatagen2026-09-24 13:07 UTC370
            update = rooms.next() => if update.is_none() { return },
31ce2453c82fatagen2026-09-24 13:07 UTC371
            update = info.recv() => if matches!(update, Err(RecvError::Closed)) { return },
31ce2453c82fatagen2026-09-24 13:07 UTC372
        }
31ce2453c82fatagen2026-09-24 13:07 UTC373
        // a sync touching fifty rooms is one invalidation, not fifty
31ce2453c82fatagen2026-09-24 13:07 UTC374
        while let Ok(_) | Err(tokio::sync::broadcast::error::TryRecvError::Lagged(_)) =
31ce2453c82fatagen2026-09-24 13:07 UTC375
            info.try_recv()
31ce2453c82fatagen2026-09-24 13:07 UTC376
        {}
31ce2453c82fatagen2026-09-24 13:07 UTC377
    }
31ce2453c82fatagen2026-09-24 13:07 UTC378
}
31ce2453c82fatagen2026-09-24 13:07 UTC379
31ce2453c82fatagen2026-09-24 13:07 UTC380
pub(super) async fn notification_settings(client: Client, events: EventSender) {
31ce2453c82fatagen2026-09-24 13:07 UTC381
    let settings = client.notification_settings().await;
31ce2453c82fatagen2026-09-24 13:07 UTC382
    let mut changes = settings.subscribe_to_changes();
31ce2453c82fatagen2026-09-24 13:07 UTC383
    while let Ok(()) | Err(RecvError::Lagged(_)) = changes.recv().await {
0bfa3f46f0beatagen2026-09-26 09:12 UTC384
        // room summaries carry the effective mode, and push rule changes never touch room info
0bfa3f46f0beatagen2026-09-26 09:12 UTC385
        for event in [Event::NotificationSettingsChanged, Event::RoomsChanged] {
0bfa3f46f0beatagen2026-09-26 09:12 UTC386
            if events.send(event).await.is_err() {
0bfa3f46f0beatagen2026-09-26 09:12 UTC387
                return;
0bfa3f46f0beatagen2026-09-26 09:12 UTC388
            }
31ce2453c82fatagen2026-09-24 13:07 UTC389
        }
31ce2453c82fatagen2026-09-24 13:07 UTC390
    }
31ce2453c82fatagen2026-09-24 13:07 UTC391
}