atagen

macltok

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