atagen

macltok

public
Star0
Watch0
d5d8097c3afd94c250affd252a3e9ea63ecec8a5
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
src/transport.rsRust1079 lines33.3 KB
1
use std::{
2
    fmt,
3
    future::Future,
4
    pin::Pin,
5
    sync::Arc,
6
    task::{Context, Poll},
7
};
8
9
use matrix_sdk::{Client, RoomMemberships};
10
use matrix_sdk_ui::{
11
    notification_client::{
12
        NotificationClient, NotificationProcessSetup, NotificationStatus, RawNotificationEvent,
13
    },
14
    spaces::{SpaceRoomList, SpaceService, room_list::SpaceRoomListPaginationState},
15
    sync_service::SyncService,
16
};
17
use ruma::{
18
    EventId, MxcUri, OwnedDeviceId, OwnedEventId, OwnedMxcUri, OwnedRoomId, OwnedRoomOrAliasId,
19
    OwnedServerName, OwnedTransactionId, OwnedUserId, RoomId, UserId,
20
    events::{
21
        AnyGlobalAccountDataEventContent, AnyRoomAccountDataEvent, AnyRoomAccountDataEventContent,
22
        GlobalAccountDataEventType, RoomAccountDataEventType,
23
    },
24
    serde::Raw,
25
};
26
use tokio::sync::{OnceCell, mpsc, oneshot, watch};
27
28
use crate::{
29
    AccountCommand, AttachmentUpload, Command, CommandOutput, ConnectionState, CreateRoomOptions,
30
    EncryptionCommand, EncryptionState, Error, ErrorKind, Event, EventRef, InteractiveAuth,
31
    InteractiveAuthOutcome, LifecycleCommand, Media, MediaCommand, MediaId, MediaSize,
32
    MemberFilter, MemberSummary, MessageContent, MessageEditContent, NotificationCommand,
33
    OwnDevice, PowerLevelChanges, Profile, PushNotification, PushNotificationEvent,
34
    PushNotificationStatus, RoomCommand, RoomListFilter, RoomNotificationMode,
35
    RoomNotificationSettings, RoomPowerLevels, RoomSummary, SpaceChild, TimelineCommand,
36
    TimelineId, VerificationAction, VerificationTarget,
37
    encryption::encryption_state,
38
    engine::MediaService,
39
    model::{PushNotificationRoom, PushNotificationSender},
40
    normalise::{member_summary, room_summaries, space_child},
41
};
42
43
#[derive(Clone)]
44
pub struct ClientHandle {
45
    requests: mpsc::Sender<Request>,
46
    client: Client,
47
    connection: watch::Receiver<ConnectionState>,
48
    notification_client: Arc<OnceCell<NotificationClient>>,
49
    sync_service: Arc<SyncService>,
50
    media: Arc<MediaService>,
51
    spaces: Arc<OnceCell<SpaceService>>,
52
}
53
54
impl fmt::Debug for ClientHandle {
55
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
56
        formatter
57
            .debug_struct("ClientHandle")
58
            .finish_non_exhaustive()
59
    }
60
}
61
62
/// a command's answer. drop it if you don't care, the command still runs
63
#[derive(Debug)]
64
#[must_use = "dropping it is fine but then you'll never know"]
65
pub struct Pending(oneshot::Receiver<Result<CommandOutput, Error>>);
66
67
impl Future for Pending {
68
    type Output = Result<CommandOutput, Error>;
69
70
    fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
71
        Pin::new(&mut self.0)
72
            .poll(context)
73
            .map(|result| result.unwrap_or_else(|_| Err(Error::interrupted())))
74
    }
75
}
76
77
impl ClientHandle {
78
    /// current credentials. persist this atomically on `Event::SessionTokensChanged`, it holds secrets
79
    #[cfg(any(feature = "sqlite", feature = "indexeddb"))]
80
    pub fn session_record(&self) -> Result<crate::SessionRecord, crate::AccountError> {
81
        crate::account::session_record(&self.client)
82
    }
83
84
    /// queue a command, waiting for room in the queue if there isn't any
85
    pub async fn send(&self, command: Command) -> Result<Pending, Error> {
86
        let (request, pending) = Request::new(command);
87
        self.requests
88
            .send(request)
89
            .await
90
            .map_err(|_| Error::interrupted())?;
91
        Ok(pending)
92
    }
93
94
    /// queue a command or fail with `QueueFull` right now
95
    pub fn try_send(&self, command: Command) -> Result<Pending, Error> {
96
        let (request, pending) = Request::new(command);
97
        self.requests
98
            .try_send(request)
99
            .map_err(|error| match error {
100
                mpsc::error::TrySendError::Full(_) => {
101
                    Error::new(ErrorKind::QueueFull, "the command queue is full")
102
                }
103
                mpsc::error::TrySendError::Closed(_) => Error::interrupted(),
104
            })?;
105
        Ok(pending)
106
    }
107
108
    /// queue a command and wait for its answer. timeline sends answer once the
109
    /// sdk send queue has them, delivery shows up later as the item's send state
110
    pub async fn execute(&self, command: Command) -> Result<CommandOutput, Error> {
111
        self.send(command).await?.await
112
    }
113
114
    async fn run(&self, command: Command) -> Result<(), Error> {
115
        self.execute(command).await.map(drop)
116
    }
117
118
    async fn run_for_room(&self, command: RoomCommand) -> Result<OwnedRoomId, Error> {
119
        match self.execute(Command::Room(command)).await? {
120
            CommandOutput::Room(room_id) => Ok(room_id),
121
            output => Err(Error::new(
122
                ErrorKind::Other,
123
                format!("expected a room but got {output:?}"),
124
            )),
125
        }
126
    }
127
128
    pub async fn shutdown(&self) -> Result<(), Error> {
129
        self.run(Command::Lifecycle(LifecycleCommand::Shutdown))
130
            .await
131
    }
132
133
    pub async fn logout(&self) -> Result<(), Error> {
134
        self.run(Command::Account(AccountCommand::Logout)).await
135
    }
136
137
    pub async fn delete_devices(
138
        &self,
139
        device_ids: Vec<OwnedDeviceId>,
140
        auth: Option<InteractiveAuth>,
141
    ) -> Result<InteractiveAuthOutcome, Error> {
142
        match self
143
            .run(Command::Account(AccountCommand::DeleteDevices {
144
                device_ids,
145
                auth,
146
            }))
147
            .await
148
        {
149
            Ok(()) => Ok(InteractiveAuthOutcome::Completed),
150
            Err(Error {
151
                challenge: Some(challenge),
152
                ..
153
            }) => Ok(InteractiveAuthOutcome::Challenge(*challenge)),
154
            Err(error) => Err(error),
155
        }
156
    }
157
158
    pub async fn rename_device(
159
        &self,
160
        device_id: OwnedDeviceId,
161
        display_name: impl Into<String>,
162
    ) -> Result<(), Error> {
163
        self.run(Command::Account(AccountCommand::RenameDevice {
164
            device_id,
165
            display_name: display_name.into(),
166
        }))
167
        .await
168
    }
169
170
    pub async fn set_global_account_data(
171
        &self,
172
        event_type: GlobalAccountDataEventType,
173
        content: Raw<AnyGlobalAccountDataEventContent>,
174
    ) -> Result<(), Error> {
175
        self.run(Command::Account(AccountCommand::SetAccountData {
176
            event_type,
177
            content,
178
        }))
179
        .await
180
    }
181
182
    pub async fn set_room_account_data(
183
        &self,
184
        room_id: OwnedRoomId,
185
        event_type: RoomAccountDataEventType,
186
        content: Raw<AnyRoomAccountDataEventContent>,
187
    ) -> Result<(), Error> {
188
        self.run(Command::Room(RoomCommand::SetAccountData {
189
            room_id,
190
            event_type,
191
            content,
192
        }))
193
        .await
194
    }
195
196
    pub async fn set_room_notification_mode(
197
        &self,
198
        room_id: OwnedRoomId,
199
        mode: RoomNotificationMode,
200
    ) -> Result<(), Error> {
201
        self.run(Command::Notification(NotificationCommand::SetRoomMode {
202
            room_id,
203
            mode,
204
        }))
205
        .await
206
    }
207
208
    pub async fn reset_room_notification_mode(&self, room_id: OwnedRoomId) -> Result<(), Error> {
209
        self.run(Command::Notification(NotificationCommand::ResetRoomMode(
210
            room_id,
211
        )))
212
        .await
213
    }
214
215
    pub async fn set_default_notification_mode(
216
        &self,
217
        encrypted: bool,
218
        one_to_one: bool,
219
        mode: RoomNotificationMode,
220
    ) -> Result<(), Error> {
221
        self.run(Command::Notification(NotificationCommand::SetDefaultMode {
222
            encrypted,
223
            one_to_one,
224
            mode,
225
        }))
226
        .await
227
    }
228
229
    pub async fn add_notification_keyword(&self, keyword: impl Into<String>) -> Result<(), Error> {
230
        self.run(Command::Notification(NotificationCommand::AddKeyword(
231
            keyword.into(),
232
        )))
233
        .await
234
    }
235
236
    pub async fn remove_notification_keyword(
237
        &self,
238
        keyword: impl Into<String>,
239
    ) -> Result<(), Error> {
240
        self.run(Command::Notification(NotificationCommand::RemoveKeyword(
241
            keyword.into(),
242
        )))
243
        .await
244
    }
245
246
    pub async fn open_room(&self, room_id: OwnedRoomId) -> Result<(), Error> {
247
        self.run(Command::Room(RoomCommand::Open(room_id))).await
248
    }
249
250
    pub async fn close_room(&self, room_id: OwnedRoomId) -> Result<(), Error> {
251
        self.run(Command::Room(RoomCommand::Close(room_id))).await
252
    }
253
254
    pub async fn open_thread(
255
        &self,
256
        room_id: OwnedRoomId,
257
        root_event_id: OwnedEventId,
258
    ) -> Result<(), Error> {
259
        self.run(Command::Timeline(TimelineCommand::OpenThread {
260
            room_id,
261
            root_event_id,
262
        }))
263
        .await
264
    }
265
266
    pub async fn close_thread(
267
        &self,
268
        room_id: OwnedRoomId,
269
        root_event_id: OwnedEventId,
270
    ) -> Result<(), Error> {
271
        self.run(Command::Timeline(TimelineCommand::CloseThread {
272
            room_id,
273
            root_event_id,
274
        }))
275
        .await
276
    }
277
278
    pub async fn resync(&self, timeline_id: TimelineId) -> Result<(), Error> {
279
        self.run(Command::Timeline(TimelineCommand::Resync { timeline_id }))
280
            .await
281
    }
282
283
    pub async fn paginate(&self, timeline_id: TimelineId, limit: u16) -> Result<(), Error> {
284
        self.run(Command::Timeline(TimelineCommand::Paginate {
285
            timeline_id,
286
            limit,
287
        }))
288
        .await
289
    }
290
291
    pub async fn send_message(
292
        &self,
293
        timeline_id: TimelineId,
294
        content: MessageContent,
295
    ) -> Result<(), Error> {
296
        self.run(Command::Timeline(TimelineCommand::Send {
297
            timeline_id,
298
            content,
299
        }))
300
        .await
301
    }
302
303
    pub async fn edit_message(
304
        &self,
305
        timeline_id: TimelineId,
306
        target: impl Into<EventRef>,
307
        content: MessageEditContent,
308
    ) -> Result<(), Error> {
309
        self.run(Command::Timeline(TimelineCommand::Edit {
310
            timeline_id,
311
            target: target.into(),
312
            content,
313
        }))
314
        .await
315
    }
316
317
    pub async fn redact(
318
        &self,
319
        timeline_id: TimelineId,
320
        target: impl Into<EventRef>,
321
        reason: Option<String>,
322
    ) -> Result<(), Error> {
323
        self.run(Command::Timeline(TimelineCommand::Redact {
324
            timeline_id,
325
            target: target.into(),
326
            reason,
327
        }))
328
        .await
329
    }
330
331
    pub async fn toggle_reaction(
332
        &self,
333
        timeline_id: TimelineId,
334
        target: impl Into<EventRef>,
335
        key: impl Into<String>,
336
    ) -> Result<(), Error> {
337
        self.run(Command::Timeline(TimelineCommand::ToggleReaction {
338
            timeline_id,
339
            target: target.into(),
340
            key: key.into(),
341
        }))
342
        .await
343
    }
344
345
    pub async fn mark_read(
346
        &self,
347
        timeline_id: TimelineId,
348
        event_id: OwnedEventId,
349
    ) -> Result<(), Error> {
350
        self.run(Command::Timeline(TimelineCommand::MarkRead {
351
            timeline_id,
352
            event_id,
353
        }))
354
        .await
355
    }
356
357
    pub async fn set_typing(&self, room_id: OwnedRoomId, typing: bool) -> Result<(), Error> {
358
        self.run(Command::Room(RoomCommand::SetTyping { room_id, typing }))
359
            .await
360
    }
361
362
    pub async fn join_room(
363
        &self,
364
        room: OwnedRoomOrAliasId,
365
        via: Vec<OwnedServerName>,
366
    ) -> Result<OwnedRoomId, Error> {
367
        self.run_for_room(RoomCommand::Join { room, via }).await
368
    }
369
370
    pub async fn leave_room(&self, room_id: OwnedRoomId) -> Result<(), Error> {
371
        self.run(Command::Room(RoomCommand::Leave(room_id))).await
372
    }
373
374
    pub async fn forget_room(&self, room_id: OwnedRoomId) -> Result<(), Error> {
375
        self.run(Command::Room(RoomCommand::Forget(room_id))).await
376
    }
377
378
    pub async fn accept_invite(&self, room_id: OwnedRoomId) -> Result<(), Error> {
379
        self.run(Command::Room(RoomCommand::AcceptInvite(room_id)))
380
            .await
381
    }
382
383
    pub async fn reject_invite(&self, room_id: OwnedRoomId) -> Result<(), Error> {
384
        self.run(Command::Room(RoomCommand::RejectInvite(room_id)))
385
            .await
386
    }
387
388
    pub async fn invite_user(
389
        &self,
390
        room_id: OwnedRoomId,
391
        user_id: OwnedUserId,
392
    ) -> Result<(), Error> {
393
        self.run(Command::Room(RoomCommand::InviteUser { room_id, user_id }))
394
            .await
395
    }
396
397
    pub async fn kick_user(
398
        &self,
399
        room_id: OwnedRoomId,
400
        user_id: OwnedUserId,
401
        reason: Option<String>,
402
    ) -> Result<(), Error> {
403
        self.run(Command::Room(RoomCommand::KickUser {
404
            room_id,
405
            user_id,
406
            reason,
407
        }))
408
        .await
409
    }
410
411
    pub async fn ban_user(
412
        &self,
413
        room_id: OwnedRoomId,
414
        user_id: OwnedUserId,
415
        reason: Option<String>,
416
    ) -> Result<(), Error> {
417
        self.run(Command::Room(RoomCommand::BanUser {
418
            room_id,
419
            user_id,
420
            reason,
421
        }))
422
        .await
423
    }
424
425
    pub async fn unban_user(
426
        &self,
427
        room_id: OwnedRoomId,
428
        user_id: OwnedUserId,
429
        reason: Option<String>,
430
    ) -> Result<(), Error> {
431
        self.run(Command::Room(RoomCommand::UnbanUser {
432
            room_id,
433
            user_id,
434
            reason,
435
        }))
436
        .await
437
    }
438
439
    pub async fn set_power_levels(
440
        &self,
441
        room_id: OwnedRoomId,
442
        changes: PowerLevelChanges,
443
    ) -> Result<(), Error> {
444
        self.run(Command::Room(RoomCommand::SetPowerLevels {
445
            room_id,
446
            changes,
447
        }))
448
        .await
449
    }
450
451
    pub async fn set_user_power_level(
452
        &self,
453
        room_id: OwnedRoomId,
454
        user_id: OwnedUserId,
455
        level: i64,
456
    ) -> Result<(), Error> {
457
        self.run(Command::Room(RoomCommand::SetUserPowerLevel {
458
            room_id,
459
            user_id,
460
            level,
461
        }))
462
        .await
463
    }
464
465
    pub async fn create_room(&self, options: CreateRoomOptions) -> Result<OwnedRoomId, Error> {
466
        self.run_for_room(RoomCommand::Create(options)).await
467
    }
468
469
    /// hands back the existing dm with them if there is one
470
    pub async fn create_direct(&self, user_id: OwnedUserId) -> Result<OwnedRoomId, Error> {
471
        self.run_for_room(RoomCommand::CreateDirect(user_id)).await
472
    }
473
474
    pub async fn cancel_send(
475
        &self,
476
        room_id: OwnedRoomId,
477
        transaction_id: OwnedTransactionId,
478
    ) -> Result<(), Error> {
479
        self.run(Command::Room(RoomCommand::CancelSend {
480
            room_id,
481
            transaction_id,
482
        }))
483
        .await
484
    }
485
486
    pub async fn retry_send(
487
        &self,
488
        room_id: OwnedRoomId,
489
        transaction_id: OwnedTransactionId,
490
    ) -> Result<(), Error> {
491
        self.run(Command::Room(RoomCommand::RetrySend {
492
            room_id,
493
            transaction_id,
494
        }))
495
        .await
496
    }
497
498
    pub async fn upload(&self, upload: AttachmentUpload) -> Result<(), Error> {
499
        self.run(Command::Media(MediaCommand::Upload(upload))).await
500
    }
501
502
    pub async fn recover(&self, recovery_key: impl Into<String>) -> Result<(), Error> {
503
        self.run(Command::Encryption(EncryptionCommand::Recover(
504
            recovery_key.into(),
505
        )))
506
        .await
507
    }
508
509
    pub async fn create_key_backup(&self) -> Result<(), Error> {
510
        self.run(Command::Encryption(EncryptionCommand::CreateKeyBackup))
511
            .await
512
    }
513
514
    pub async fn enable_key_backup(&self) -> Result<(), Error> {
515
        self.run(Command::Encryption(EncryptionCommand::EnableKeyBackup))
516
            .await
517
    }
518
519
    pub async fn disable_key_backup(&self, delete_from_server: bool) -> Result<(), Error> {
520
        self.run(Command::Encryption(EncryptionCommand::DisableKeyBackup {
521
            delete_from_server,
522
        }))
523
        .await
524
    }
525
526
    pub async fn recover_key_backup(&self, recovery_key: impl Into<String>) -> Result<(), Error> {
527
        self.run(Command::Encryption(EncryptionCommand::RecoverKeyBackup(
528
            recovery_key.into(),
529
        )))
530
        .await
531
    }
532
533
    pub async fn start_verification(&self, target: VerificationTarget) -> Result<(), Error> {
534
        self.run(Command::Encryption(EncryptionCommand::StartVerification(
535
            target,
536
        )))
537
        .await
538
    }
539
540
    pub async fn verify(&self, action: VerificationAction) -> Result<(), Error> {
541
        self.run(Command::Encryption(EncryptionCommand::Verify(action)))
542
            .await
543
    }
544
545
    /// bytes for media a retained timeline showed you. concurrent asks share one download
546
    pub async fn media(&self, media_id: &MediaId, size: MediaSize) -> Result<Media, Error> {
547
        self.media.timeline_media(media_id, size).await
548
    }
549
550
    /// bytes for any plain mxc uri, avatars mostly. same byte cap as everything else
551
    pub async fn mxc_media(&self, uri: &MxcUri, size: MediaSize) -> Result<Media, Error> {
552
        self.media.mxc_media(uri, size).await
553
    }
554
555
    /// the space graph builds on first use and then keeps itself current
556
    async fn spaces(&self) -> &SpaceService {
557
        self.spaces
558
            .get_or_init(|| SpaceService::new(self.client.clone()))
559
            .await
560
    }
561
562
    pub async fn rooms(&self) -> Vec<RoomSummary> {
563
        room_summaries(&self.client, self.spaces().await).await
564
    }
565
566
    /// browse a space the way the server sees it, rooms you haven't joined included
567
    pub async fn space_hierarchy(&self, space_id: OwnedRoomId) -> SpaceHierarchy {
568
        SpaceHierarchy(SpaceRoomList::new(self.client.clone(), space_id).await)
569
    }
570
571
    pub async fn own_profile(&self) -> Result<Profile, Error> {
572
        let user_id = self
573
            .client
574
            .user_id()
575
            .ok_or_else(|| Error::new(ErrorKind::Unauthenticated, "the client is not logged in"))?
576
            .to_owned();
577
        let response = self
578
            .client
579
            .account()
580
            .fetch_user_profile()
581
            .await
582
            .map_err(|source| Error::from_sdk("fetching the profile failed", source))?;
583
        profile(user_id, &response)
584
    }
585
586
    pub async fn user_profile(&self, user_id: &UserId) -> Result<Profile, Error> {
587
        let response = self
588
            .client
589
            .account()
590
            .fetch_user_profile_of(user_id)
591
            .await
592
            .map_err(|source| Error::from_sdk("fetching the profile failed", source))?;
593
        profile(user_id.to_owned(), &response)
594
    }
595
596
    pub async fn set_display_name(&self, name: Option<String>) -> Result<(), Error> {
597
        self.run(Command::Account(AccountCommand::SetDisplayName(name)))
598
            .await
599
    }
600
601
    pub async fn set_avatar(&self, avatar: Option<OwnedMxcUri>) -> Result<(), Error> {
602
        self.run(Command::Account(AccountCommand::SetAvatar(avatar)))
603
            .await
604
    }
605
606
    pub async fn upload_avatar(
607
        &self,
608
        mimetype: impl Into<String>,
609
        bytes: Vec<u8>,
610
    ) -> Result<(), Error> {
611
        self.run(Command::Account(AccountCommand::UploadAvatar {
612
            mimetype: mimetype.into(),
613
            bytes,
614
        }))
615
        .await
616
    }
617
618
    pub async fn rooms_filtered(&self, filter: &RoomListFilter) -> Vec<RoomSummary> {
619
        let matcher = filter.matcher();
620
        self.rooms()
621
            .await
622
            .into_iter()
623
            .filter(|room| matcher.matches(room))
624
            .collect()
625
    }
626
627
    pub async fn devices(&self) -> Result<Vec<OwnDevice>, Error> {
628
        let current_device_id = self.client.device_id();
629
        Ok(self
630
            .client
631
            .devices()
632
            .await
633
            .map_err(|source| Error::from_http("listing devices failed", source))?
634
            .devices
635
            .into_iter()
636
            .map(|device| OwnDevice {
637
                current: current_device_id == Some(device.device_id.as_ref()),
638
                device_id: device.device_id,
639
                display_name: device.display_name,
640
                last_seen_ip: device.last_seen_ip,
641
                last_seen_timestamp_ms: device.last_seen_ts.map(|timestamp| timestamp.get().into()),
642
            })
643
            .collect())
644
    }
645
646
    fn room(&self, room_id: &RoomId) -> Result<matrix_sdk::Room, Error> {
647
        self.client
648
            .get_room(room_id)
649
            .ok_or_else(Error::room_unknown)
650
    }
651
652
    pub async fn room_notification_settings(
653
        &self,
654
        room_id: &RoomId,
655
    ) -> Result<Option<RoomNotificationSettings>, Error> {
656
        let room = self.room(room_id)?;
657
        let custom = room.user_defined_notification_mode().await;
658
        let mode = match custom {
659
            Some(mode) => mode,
660
            None => match room.notification_mode().await {
661
                Some(mode) => mode,
662
                None => return Ok(None),
663
            },
664
        };
665
        Ok(Some(RoomNotificationSettings {
666
            mode: crate::normalise::notification_mode(mode),
667
            custom: custom.is_some(),
668
        }))
669
    }
670
671
    pub async fn notification_keywords(&self) -> Vec<String> {
672
        self.client
673
            .notification_settings()
674
            .await
675
            .enabled_keywords()
676
            .await
677
            .into_iter()
678
            .collect()
679
    }
680
681
    pub async fn resolve_push_notification(
682
        &self,
683
        room_id: &RoomId,
684
        event_id: &EventId,
685
    ) -> Result<PushNotificationStatus, Error> {
686
        let notification_failed = |source| {
687
            Error::with_source(
688
                ErrorKind::Other,
689
                "resolving the push notification failed",
690
                source,
691
            )
692
        };
693
        let client = self
694
            .notification_client
695
            .get_or_try_init(|| async {
696
                NotificationClient::new(
697
                    self.client.clone(),
698
                    NotificationProcessSetup::SingleProcess {
699
                        sync_service: self.sync_service.clone(),
700
                    },
701
                )
702
                .await
703
            })
704
            .await
705
            .map_err(notification_failed)?;
706
        let status = client
707
            .get_notification(room_id, event_id)
708
            .await
709
            .map_err(notification_failed)?;
710
        Ok(normalise_push_notification(room_id, event_id, status))
711
    }
712
713
    pub async fn members(
714
        &self,
715
        room_id: &RoomId,
716
        filter: MemberFilter,
717
    ) -> Result<Vec<MemberSummary>, Error> {
718
        let memberships = match filter {
719
            MemberFilter::Active => RoomMemberships::ACTIVE,
720
            MemberFilter::Joined => RoomMemberships::JOIN,
721
            MemberFilter::Invited => RoomMemberships::INVITE,
722
            MemberFilter::Banned => RoomMemberships::BAN,
723
            MemberFilter::Left => RoomMemberships::LEAVE,
724
            // An empty mask is the SDK's unfiltered wildcard: it matches every
725
            // membership, including custom states that `all()` would exclude.
726
            MemberFilter::All => RoomMemberships::empty(),
727
        };
728
        Ok(self
729
            .room(room_id)?
730
            .members(memberships)
731
            .await
732
            .map_err(|source| Error::from_sdk("listing members failed", source))?
733
            .into_iter()
734
            .map(member_summary)
735
            .collect())
736
    }
737
738
    pub async fn room_power_levels(&self, room_id: &RoomId) -> Result<RoomPowerLevels, Error> {
739
        let levels = self
740
            .room(room_id)?
741
            .power_levels()
742
            .await
743
            .map_err(|source| Error::from_sdk("reading power levels failed", source.into()))?;
744
        Ok(RoomPowerLevels {
745
            ban: levels.ban.into(),
746
            invite: levels.invite.into(),
747
            kick: levels.kick.into(),
748
            redact: levels.redact.into(),
749
            events_default: levels.events_default.into(),
750
            state_default: levels.state_default.into(),
751
            users_default: levels.users_default.into(),
752
            room_notification: levels.notifications.room.into(),
753
            users: levels
754
                .users
755
                .into_iter()
756
                .map(|(user_id, level)| (user_id, level.into()))
757
                .collect(),
758
            events: levels
759
                .events
760
                .into_iter()
761
                .map(|(event_type, level)| (event_type.to_string(), level.into()))
762
                .collect(),
763
            creators: levels
764
                .rules
765
                .privileged_creators
766
                .into_iter()
767
                .flatten()
768
                .collect(),
769
        })
770
    }
771
772
    pub async fn pending_send_ids(
773
        &self,
774
        room_id: &RoomId,
775
    ) -> Result<Vec<OwnedTransactionId>, Error> {
776
        let (echoes, _) = self
777
            .room(room_id)?
778
            .send_queue()
779
            .subscribe()
780
            .await
781
            .map_err(|source| {
782
                Error::with_source(ErrorKind::Store, "reading the send queue failed", source)
783
            })?;
784
        Ok(echoes
785
            .into_iter()
786
            .filter(|echo| {
787
                matches!(
788
                    echo.content,
789
                    matrix_sdk::send_queue::LocalEchoContent::Event { .. }
790
                )
791
            })
792
            .map(|echo| echo.transaction_id)
793
            .collect())
794
    }
795
796
    pub async fn global_account_data(
797
        &self,
798
        event_type: GlobalAccountDataEventType,
799
    ) -> Result<Option<Raw<AnyGlobalAccountDataEventContent>>, Error> {
800
        self.client
801
            .account()
802
            .account_data_raw(event_type)
803
            .await
804
            .map_err(|source| Error::from_sdk("reading account data failed", source))
805
    }
806
807
    pub async fn room_account_data(
808
        &self,
809
        room_id: &RoomId,
810
        event_type: RoomAccountDataEventType,
811
    ) -> Result<Option<Raw<AnyRoomAccountDataEvent>>, Error> {
812
        self.room(room_id)?
813
            .account_data(event_type)
814
            .await
815
            .map_err(|source| Error::from_sdk("reading room account data failed", source))
816
    }
817
818
    #[must_use]
819
    pub fn connection_state(&self) -> watch::Receiver<ConnectionState> {
820
        self.connection.clone()
821
    }
822
823
    #[must_use]
824
    pub fn encryption_state(&self) -> Option<EncryptionState> {
825
        let device_id = self.client.device_id()?.to_owned();
826
        let encryption = self.client.encryption();
827
        Some(encryption_state(
828
            device_id,
829
            encryption.recovery().state(),
830
            encryption.verification_state().get(),
831
            encryption.backups().state(),
832
        ))
833
    }
834
}
835
836
/// one space's hierarchy, a page at a time. hold on to it while you browse, it remembers where it got to
837
pub struct SpaceHierarchy(SpaceRoomList);
838
839
impl fmt::Debug for SpaceHierarchy {
840
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
841
        formatter
842
            .debug_struct("SpaceHierarchy")
843
            .finish_non_exhaustive()
844
    }
845
}
846
847
impl SpaceHierarchy {
848
    /// fetches the next page. a no-op once the end is reached or while a page is in flight
849
    pub async fn paginate(&self) -> Result<(), Error> {
850
        self.0.paginate().await.map_err(|source| {
851
            Error::with_source(
852
                ErrorKind::Server,
853
                "fetching the space hierarchy failed",
854
                source,
855
            )
856
        })
857
    }
858
859
    /// everything fetched so far, the space itself excluded
860
    pub async fn rooms(&self) -> Vec<SpaceChild> {
861
        self.0.rooms().await.into_iter().map(space_child).collect()
862
    }
863
864
    #[must_use]
865
    pub fn end_reached(&self) -> bool {
866
        matches!(
867
            self.0.pagination_state(),
868
            SpaceRoomListPaginationState::Idle { end_reached: true }
869
        )
870
    }
871
}
872
873
fn profile(
874
    user_id: OwnedUserId,
875
    response: &ruma::api::client::profile::get_profile::v3::Response,
876
) -> Result<Profile, Error> {
877
    use ruma::api::client::profile::{AvatarUrl, DisplayName};
878
    let malformed = |source| {
879
        Error::with_source(
880
            ErrorKind::Server,
881
            "the server sent a malformed profile",
882
            source,
883
        )
884
    };
885
    Ok(Profile {
886
        user_id,
887
        display_name: response.get_static::<DisplayName>().map_err(malformed)?,
888
        avatar: response.get_static::<AvatarUrl>().map_err(malformed)?,
889
    })
890
}
891
892
#[derive(Debug)]
893
pub(crate) struct Request {
894
    pub(crate) command: Command,
895
    pub(crate) reply: Responder,
896
}
897
898
impl Request {
899
    fn new(command: Command) -> (Self, Pending) {
900
        let (reply, pending) = oneshot::channel();
901
        (
902
            Self {
903
                command,
904
                reply: Responder(reply),
905
            },
906
            Pending(pending),
907
        )
908
    }
909
}
910
911
/// whoever holds this owes the caller an answer. dropping it answers `Interrupted`
912
#[derive(Debug)]
913
pub(crate) struct Responder(oneshot::Sender<Result<CommandOutput, Error>>);
914
915
impl Responder {
916
    pub(crate) fn send(self, result: Result<CommandOutput, Error>) {
917
        if let Err(error) = &result {
918
            tracing::debug!(%error, kind = ?error.kind, "command failed");
919
        }
920
        let _ = self.0.send(result);
921
    }
922
923
    pub(crate) fn done(self, result: Result<(), Error>) {
924
        self.send(result.map(|()| CommandOutput::Done));
925
    }
926
}
927
928
#[derive(Debug)]
929
pub struct EventStream {
930
    events: mpsc::Receiver<Event>,
931
}
932
933
impl EventStream {
934
    pub async fn recv(&mut self) -> Option<Event> {
935
        self.events.recv().await
936
    }
937
938
    pub fn try_recv(&mut self) -> Result<Event, mpsc::error::TryRecvError> {
939
        self.events.try_recv()
940
    }
941
}
942
943
impl futures_util::Stream for EventStream {
944
    type Item = Event;
945
946
    fn poll_next(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<Self::Item>> {
947
        self.events.poll_recv(context)
948
    }
949
}
950
951
#[derive(Debug)]
952
pub(crate) struct DriverPort {
953
    requests: mpsc::Receiver<Request>,
954
    events: mpsc::Sender<Event>,
955
    connection: watch::Sender<ConnectionState>,
956
}
957
958
#[derive(Clone, Debug)]
959
pub(crate) struct EventSender(mpsc::Sender<Event>);
960
961
impl EventSender {
962
    pub(crate) async fn send(&self, event: Event) -> Result<(), SendEventError> {
963
        self.0.send(event).await.map_err(|_| SendEventError)
964
    }
965
966
    /// resolves once every `EventStream` is gone
967
    pub(crate) async fn closed(&self) {
968
        self.0.closed().await;
969
    }
970
}
971
972
impl DriverPort {
973
    pub(crate) fn event_sender(&self) -> EventSender {
974
        EventSender(self.events.clone())
975
    }
976
977
    pub(crate) fn connection_sender(&self) -> watch::Sender<ConnectionState> {
978
        self.connection.clone()
979
    }
980
981
    pub(crate) async fn recv(&mut self) -> Option<Request> {
982
        self.requests.recv().await
983
    }
984
985
    pub(crate) fn try_recv(&mut self) -> Option<Request> {
986
        self.requests.try_recv().ok()
987
    }
988
989
    pub(crate) fn close(&mut self) {
990
        self.requests.close();
991
    }
992
}
993
994
#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
995
#[error("the event receiver has closed")]
996
pub(crate) struct SendEventError;
997
998
#[must_use]
999
pub(crate) fn transport_pair(
1000
    client: Client,
1001
    sync_service: Arc<SyncService>,
1002
    media: Arc<MediaService>,
1003
    command_capacity: usize,
1004
    event_capacity: usize,
1005
) -> (ClientHandle, DriverPort, EventStream) {
1006
    let (request_tx, request_rx) = mpsc::channel(command_capacity);
1007
    let (event_tx, event_rx) = mpsc::channel(event_capacity);
1008
    let (connection_tx, connection_rx) = watch::channel(ConnectionState::Idle);
1009
    (
1010
        ClientHandle {
1011
            requests: request_tx,
1012
            client,
1013
            connection: connection_rx,
1014
            notification_client: Arc::new(OnceCell::new()),
1015
            sync_service,
1016
            media,
1017
            spaces: Arc::new(OnceCell::new()),
1018
        },
1019
        DriverPort {
1020
            requests: request_rx,
1021
            events: event_tx,
1022
            connection: connection_tx,
1023
        },
1024
        EventStream { events: event_rx },
1025
    )
1026
}
1027
1028
fn normalise_push_notification(
1029
    room_id: &RoomId,
1030
    event_id: &EventId,
1031
    status: NotificationStatus,
1032
) -> PushNotificationStatus {
1033
    match status {
1034
        NotificationStatus::Event(item) => {
1035
            let event = match &item.raw_event {
1036
                RawNotificationEvent::Timeline(event) => {
1037
                    PushNotificationEvent::Timeline(event.clone())
1038
                }
1039
                RawNotificationEvent::Invite(event) => PushNotificationEvent::Invite(event.clone()),
1040
            };
1041
            PushNotificationStatus::Event(Box::new(PushNotification {
1042
                event_id: event_id.to_owned(),
1043
                event,
1044
                sender: PushNotificationSender {
1045
                    user_id: item.event.sender().to_owned(),
1046
                    display_name: item.sender_display_name,
1047
                    avatar_url: item.sender_avatar_url.map(Into::into),
1048
                    name_ambiguous: item.is_sender_name_ambiguous,
1049
                },
1050
                room: PushNotificationRoom {
1051
                    room_id: room_id.to_owned(),
1052
                    name: item.room_computed_display_name,
1053
                    avatar_url: item.room_avatar_url.map(Into::into),
1054
                    canonical_alias: item
1055
                        .room_canonical_alias
1056
                        .as_deref()
1057
                        .and_then(|alias| alias.parse().ok()),
1058
                    topic: item.room_topic,
1059
                    encrypted: item.is_room_encrypted,
1060
                    direct: item.room_is_dm,
1061
                    joined_members_count: item.joined_members_count,
1062
                    service_members: item
1063
                        .service_members
1064
                        .into_iter()
1065
                        .filter_map(|user_id| user_id.parse().ok())
1066
                        .collect(),
1067
                    active_service_members_count: item.active_service_members_count,
1068
                    space: item.is_space,
1069
                },
1070
                noisy: item.is_noisy,
1071
                has_mention: item.has_mention,
1072
                thread_id: item.thread_id,
1073
            }))
1074
        }
1075
        NotificationStatus::EventNotFound => PushNotificationStatus::NotFound,
1076
        NotificationStatus::EventFilteredOut => PushNotificationStatus::FilteredOut,
1077
        NotificationStatus::EventRedacted => PushNotificationStatus::Redacted,
1078
    }
1079
}