1use std::{cell::RefCell, collections::HashSet};
2
3use futures_util::StreamExt;
4use gettextrs::gettext;
5use gtk::{
6 glib,
7 glib::{clone, closure_local},
8 prelude::*,
9 subclass::prelude::*,
10};
11use matrix_sdk::{
12 Result as MatrixResult, RoomDisplayName, RoomInfo, RoomMemberships, RoomState,
13 deserialized_responses::{AmbiguityChange, RawSyncOrStrippedState},
14 event_handler::EventHandlerDropGuard,
15 room::Room as MatrixRoom,
16 send_queue::RoomSendQueueUpdate,
17};
18use ruma::{
19 EventId, MatrixToUri, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId,
20 api::client::{
21 error::{ErrorKind, LimitExceededErrorData, RetryAfter},
22 receipt::create_receipt::v3::ReceiptType as ApiReceiptType,
23 },
24 events::room::{
25 guest_access::GuestAccess,
26 history_visibility::HistoryVisibility,
27 member::{MembershipState, RoomMemberEventContent, SyncRoomMemberEvent},
28 },
29 room_version_rules::RoomVersionRules,
30};
31use serde::Deserialize;
32use tokio_stream::wrappers::BroadcastStream;
33use tracing::{debug, error, warn};
34
35mod aliases;
36mod category;
37mod highlight_flags;
38mod join_rule;
39mod member;
40mod member_list;
41mod permissions;
42mod timeline;
43mod typing_list;
44
45pub(crate) use self::{
46 aliases::{AddAltAliasError, RegisterLocalAliasError, RoomAliases},
47 category::{RoomCategory, TargetRoomCategory},
48 highlight_flags::HighlightFlags,
49 join_rule::{JoinRule, JoinRuleValue},
50 member::{Member, Membership},
51 member_list::*,
52 permissions::*,
53 timeline::*,
54 typing_list::TypingList,
55};
56use super::{
57 IdentityVerification, Session, User, notifications::NotificationsRoomSetting,
58 room_list::RoomMetainfo,
59};
60use crate::{
61 components::{AtRoom, AvatarImage, AvatarUriSource, PillSource},
62 gettext_f,
63 prelude::*,
64 spawn, spawn_tokio,
65 utils::{BoundObjectWeakRef, string::linkify},
66};
67
68const DEFAULT_RETRY_AFTER: u32 = 30;
71
72mod imp {
73 use std::{
74 cell::{Cell, OnceCell},
75 marker::PhantomData,
76 sync::LazyLock,
77 time::SystemTime,
78 };
79
80 use glib::subclass::Signal;
81
82 use super::*;
83
84 #[derive(Default, glib::Properties)]
85 #[properties(wrapper_type = super::Room)]
86 pub struct Room {
87 matrix_room: OnceCell<MatrixRoom>,
89 #[property(get, set = Self::set_session, construct_only)]
91 session: glib::WeakRef<Session>,
92 #[property(get = Self::room_id_string)]
94 room_id_string: PhantomData<String>,
95 #[property(get)]
97 aliases: RoomAliases,
98 #[property(get)]
103 name: RefCell<Option<String>>,
104 #[property(get)]
109 has_avatar: Cell<bool>,
110 #[property(get)]
112 topic: RefCell<Option<String>>,
113 #[property(get)]
118 topic_linkified: RefCell<Option<String>>,
119 #[property(get, builder(RoomCategory::default()))]
121 category: Cell<RoomCategory>,
122 #[property(get)]
124 is_direct: Cell<bool>,
125 #[property(get)]
127 is_tombstoned: Cell<bool>,
128 pub(super) predecessor_id: OnceCell<OwnedRoomId>,
130 #[property(get = Self::predecessor_id_string)]
133 predecessor_id_string: PhantomData<Option<String>>,
134 pub(super) successor_id: OnceCell<OwnedRoomId>,
136 #[property(get = Self::successor_id_string)]
139 successor_id_string: PhantomData<Option<String>>,
140 #[property(get)]
143 successor: glib::WeakRef<super::Room>,
144 #[property(get)]
146 pub(super) members: glib::WeakRef<MemberList>,
147 members_drop_guard: OnceCell<EventHandlerDropGuard>,
148 #[property(get)]
151 joined_members_count: Cell<u64>,
152 #[property(get)]
154 own_member: OnceCell<Member>,
155 #[property(get)]
158 is_invite: Cell<bool>,
159 #[property(get)]
163 inviter: RefCell<Option<Member>>,
164 #[property(get)]
167 direct_member: RefCell<Option<Member>>,
168 #[property(get)]
170 live_timeline: OnceCell<Timeline>,
171 #[property(get)]
178 latest_activity: Cell<u64>,
179 #[property(get)]
181 is_marked_unread: Cell<bool>,
182 #[property(get)]
184 is_read: Cell<bool>,
185 #[property(get)]
187 notification_count: Cell<u64>,
188 #[property(get)]
190 has_notifications: Cell<bool>,
191 #[property(get)]
193 highlight: Cell<HighlightFlags>,
194 #[property(get)]
196 is_encrypted: Cell<bool>,
197 #[property(get)]
199 join_rule: JoinRule,
200 #[property(get)]
202 guests_allowed: Cell<bool>,
203 #[property(get, builder(HistoryVisibilityValue::default()))]
205 history_visibility: Cell<HistoryVisibilityValue>,
206 #[property(get = Self::version)]
208 version: PhantomData<String>,
209 #[property(get = Self::federated)]
211 federated: PhantomData<bool>,
212 #[property(get)]
214 typing_list: TypingList,
215 typing_drop_guard: OnceCell<EventHandlerDropGuard>,
216 #[property(get, set = Self::set_notifications_setting, explicit_notify, builder(NotificationsRoomSetting::default()))]
218 notifications_setting: Cell<NotificationsRoomSetting>,
219 #[property(get)]
221 permissions: Permissions,
222 #[property(get, set = Self::set_verification, nullable, explicit_notify)]
224 verification: BoundObjectWeakRef<IdentityVerification>,
225 #[property(get)]
229 is_room_info_initialized: Cell<bool>,
230 #[property(get)]
232 attempted_auto_join: Cell<bool>,
233 #[property(get = Self::is_call)]
235 is_call: PhantomData<bool>,
236 }
237
238 #[glib::object_subclass]
239 impl ObjectSubclass for Room {
240 const NAME: &'static str = "Room";
241 type Type = super::Room;
242 type ParentType = PillSource;
243 }
244
245 #[glib::derived_properties]
246 impl ObjectImpl for Room {
247 fn signals() -> &'static [Signal] {
248 static SIGNALS: LazyLock<Vec<Signal>> =
249 LazyLock::new(|| vec![Signal::builder("room-forgotten").build()]);
250 SIGNALS.as_ref()
251 }
252 }
253
254 impl PillSourceImpl for Room {
255 fn identifier(&self) -> String {
256 self.aliases
257 .alias_string()
258 .unwrap_or_else(|| self.room_id_string())
259 }
260 }
261
262 impl Room {
263 pub(super) fn init(&self, matrix_room: MatrixRoom, metainfo: Option<RoomMetainfo>) {
265 let obj = self.obj();
266
267 self.matrix_room
268 .set(matrix_room)
269 .expect("matrix room is uninitialized");
270
271 self.init_live_timeline();
272 self.aliases.init(&obj);
273 self.load_predecessor();
274 self.watch_members();
275 self.join_rule.init(&obj);
276 self.set_up_typing();
277 self.watch_send_queue();
278
279 spawn!(
280 glib::Priority::DEFAULT_IDLE,
281 clone!(
282 #[weak(rename_to = imp)]
283 self,
284 async move {
285 imp.update_with_room_info(imp.matrix_room().clone_info())
286 .await;
287 imp.watch_room_info();
288
289 imp.is_room_info_initialized.set(true);
290 imp.obj().notify_is_room_info_initialized();
291
292 let preload = matches!(
298 imp.category.get(),
299 RoomCategory::Favorite
300 | RoomCategory::Normal
301 | RoomCategory::LowPriority
302 );
303 imp.live_timeline().set_preload(preload);
304
305 imp.permissions.init(&imp.obj()).await;
306 }
307 )
308 );
309
310 spawn!(
311 glib::Priority::DEFAULT_IDLE,
312 clone!(
313 #[weak(rename_to = imp)]
314 self,
315 async move {
316 imp.load_own_member().await;
317 }
318 )
319 );
320
321 if let Some(RoomMetainfo {
322 latest_activity,
323 is_read,
324 }) = metainfo
325 {
326 self.set_latest_activity(latest_activity);
327 self.set_is_read(is_read);
328
329 self.update_highlight();
330 }
331 }
332
333 pub(super) fn matrix_room(&self) -> &MatrixRoom {
335 self.matrix_room.get().expect("matrix room was initialized")
336 }
337
338 fn set_session(&self, session: &Session) {
340 self.session.set(Some(session));
341
342 let own_member = Member::new(&self.obj(), session.user_id().clone());
343 self.own_member
344 .set(own_member)
345 .expect("own member was uninitialized");
346 }
347
348 pub(super) fn room_id(&self) -> &RoomId {
350 self.matrix_room().room_id()
351 }
352
353 fn room_id_string(&self) -> String {
355 self.matrix_room().room_id().to_string()
356 }
357
358 fn update_name(&self) {
360 let name = self.matrix_room().name().into_clean_string();
361
362 if *self.name.borrow() == name {
363 return;
364 }
365
366 self.name.replace(name);
367 self.obj().notify_name();
368 }
369
370 async fn update_display_name(&self) {
372 let matrix_room = self.matrix_room().clone();
373 let handle = spawn_tokio!(async move { matrix_room.display_name().await });
374
375 let sdk_display_name = handle
376 .await
377 .expect("task was not aborted")
378 .inspect_err(|error| {
379 error!("Could not compute display name: {error}");
380 })
381 .ok();
382
383 let mut display_name = if let Some(sdk_display_name) = sdk_display_name {
384 match sdk_display_name {
385 RoomDisplayName::Named(s)
386 | RoomDisplayName::Calculated(s)
387 | RoomDisplayName::Aliased(s) => s,
388 RoomDisplayName::EmptyWas(s) => {
389 gettext_f("Empty Room (was {user})", &[("user", &s)])
393 }
394 RoomDisplayName::Empty => gettext("Empty Room"),
396 }
397 } else {
398 Default::default()
399 };
400
401 display_name.clean_string();
402
403 if display_name.is_empty() {
404 display_name = gettext("Unknown");
406 }
407
408 self.obj().set_display_name(display_name);
409 }
410
411 fn set_has_avatar(&self, has_avatar: bool) {
413 if self.has_avatar.get() == has_avatar {
414 return;
415 }
416
417 self.has_avatar.set(has_avatar);
418 self.obj().notify_has_avatar();
419 }
420
421 fn update_avatar(&self) {
423 let Some(session) = self.session.upgrade() else {
424 return;
425 };
426
427 let obj = self.obj();
428 let avatar_data = obj.avatar_data();
429 let matrix_room = self.matrix_room();
430
431 let prev_avatar_url = avatar_data.image().and_then(|i| i.uri());
432 let room_avatar_url = matrix_room.avatar_url();
433
434 if prev_avatar_url.is_some() && prev_avatar_url == room_avatar_url {
435 return;
437 }
438
439 if let Some(avatar_url) = room_avatar_url {
440 let avatar_info = matrix_room.avatar_info();
442
443 if let Some(avatar_image) = avatar_data
444 .image()
445 .filter(|i| i.uri_source() == AvatarUriSource::Room)
446 {
447 avatar_image.set_uri_and_info(Some(avatar_url), avatar_info);
448 } else {
449 let avatar_image = AvatarImage::new(
450 &session,
451 AvatarUriSource::Room,
452 Some(avatar_url),
453 avatar_info,
454 );
455
456 avatar_data.set_image(Some(avatar_image.clone()));
457 }
458
459 self.set_has_avatar(true);
460 return;
461 }
462
463 self.set_has_avatar(false);
464
465 if let Some(direct_member) = self.direct_member.borrow().as_ref() {
467 avatar_data.set_image(direct_member.avatar_data().image());
468 }
469
470 let avatar_image = avatar_data.image();
471
472 if let Some(avatar_image) = avatar_image
473 .as_ref()
474 .filter(|i| i.uri_source() == AvatarUriSource::Room)
475 {
476 avatar_image.set_uri_and_info(None, None);
478 } else if avatar_image.is_none() {
479 avatar_data.set_image(Some(AvatarImage::new(
481 &session,
482 AvatarUriSource::Room,
483 None,
484 None,
485 )));
486 }
487 }
488
489 fn update_topic(&self) {
491 let topic = self
492 .matrix_room()
493 .topic()
494 .map(|mut s| {
495 s.strip_nul();
496 s.truncate_end_whitespaces();
497 s
498 })
499 .filter(|topic| !topic.is_empty());
500
501 if *self.topic.borrow() == topic {
502 return;
503 }
504
505 let topic_linkified = topic.as_ref().map(|t| {
506 let mut s = linkify(t);
508 s.truncate_end_whitespaces();
510 s
511 });
512
513 self.topic.replace(topic);
514 self.topic_linkified.replace(topic_linkified);
515
516 let obj = self.obj();
517 obj.notify_topic();
518 obj.notify_topic_linkified();
519 }
520
521 fn set_category(&self, category: RoomCategory) {
523 let old_category = self.category.get();
524
525 if old_category == RoomCategory::Outdated || old_category == category {
526 return;
527 }
528
529 self.category.set(category);
530 self.obj().notify_category();
531
532 let room_state = self.matrix_room().state();
534 if !old_category.is_state(room_state) {
535 if self.is_room_info_initialized.get() {
536 debug!(room_id = %self.room_id(), ?room_state, "The state of the room changed");
537 }
538
539 match room_state {
540 RoomState::Joined => {
541 if let Some(members) = self.members.upgrade() {
542 members.reload();
545 }
546
547 self.set_up_typing();
548 }
549 RoomState::Left
550 | RoomState::Knocked
551 | RoomState::Banned
552 | RoomState::Invited => {}
553 }
554 }
555 }
556
557 pub(super) async fn update_category(&self) {
559 if self.category.get() == RoomCategory::Outdated {
561 return;
562 }
563
564 self.update_is_invite().await;
565 self.update_inviter().await;
566
567 let matrix_room = self.matrix_room();
568 let state = matrix_room.state();
569
570 if state != RoomState::Invited {
572 self.attempted_auto_join.take();
573 }
574
575 let category = match state {
576 RoomState::Joined => {
577 if matrix_room.is_space() {
578 RoomCategory::Space
579 } else if matrix_room.is_favourite() {
580 RoomCategory::Favorite
581 } else if matrix_room.is_low_priority() {
582 RoomCategory::LowPriority
583 } else {
584 RoomCategory::Normal
585 }
586 }
587 RoomState::Invited => {
588 if !self.attempted_auto_join.get()
590 && self.was_membership(&MembershipState::Knock).await
591 {
592 self.attempted_auto_join.set(true);
593
594 if self
595 .change_category(TargetRoomCategory::Normal)
596 .await
597 .is_ok()
598 {
599 return;
602 }
603 }
604
605 if self
606 .inviter
607 .borrow()
608 .as_ref()
609 .is_some_and(Member::is_ignored)
610 {
611 RoomCategory::Ignored
612 } else {
613 RoomCategory::Invited
614 }
615 }
616 RoomState::Knocked => RoomCategory::Knocked,
617 RoomState::Left | RoomState::Banned => RoomCategory::Left,
618 };
619
620 self.set_category(category);
621 }
622
623 async fn set_is_direct(&self, is_direct: bool) {
625 if self.is_direct.get() == is_direct {
626 return;
627 }
628
629 self.is_direct.set(is_direct);
630 self.obj().notify_is_direct();
631
632 self.update_direct_member().await;
633 }
634
635 pub(super) async fn update_is_direct(&self) {
637 let matrix_room = self.matrix_room().clone();
638 let handle = spawn_tokio!(async move { matrix_room.is_direct().await });
639
640 match handle.await.expect("task was not aborted") {
641 Ok(is_direct) => self.set_is_direct(is_direct).await,
642 Err(error) => {
643 error!(room_id = %self.room_id(), "Could not load whether room is direct: {error}");
644 }
645 }
646 }
647
648 fn update_tombstone(&self) {
650 let matrix_room = self.matrix_room();
651
652 if !matrix_room.is_tombstoned() || self.successor_id.get().is_some() {
653 return;
654 }
655 let obj = self.obj();
656
657 if let Some(successor_id) = matrix_room
658 .tombstone_content()
659 .and_then(|room_tombstone| room_tombstone.replacement_room)
660 {
661 self.successor_id
662 .set(successor_id)
663 .expect("successor ID should be uninitialized");
664 obj.notify_successor_id_string();
665 }
666
667 self.update_successor();
669
670 if self.successor.upgrade().is_none()
672 && let Some(session) = self.session.upgrade()
673 {
674 session
675 .room_list()
676 .add_tombstoned_room(self.room_id().to_owned());
677 }
678
679 if !self.is_tombstoned.get() {
680 self.is_tombstoned.set(true);
681 obj.notify_is_tombstoned();
682 }
683 }
684
685 pub(super) fn update_successor(&self) {
687 if self.category.get() == RoomCategory::Outdated {
688 return;
689 }
690
691 let Some(session) = self.session.upgrade() else {
692 return;
693 };
694 let room_list = session.room_list();
695
696 if let Some(successor) = self
697 .successor_id
698 .get()
699 .and_then(|successor_id| room_list.get(successor_id))
700 {
701 if successor
705 .predecessor_id()
706 .is_some_and(|predecessor_id| predecessor_id == self.room_id())
707 {
708 self.set_successor(&successor);
709 return;
710 }
711 }
712
713 for room in room_list.iter::<super::Room>() {
716 let Ok(room) = room else {
717 break;
718 };
719
720 if room
721 .predecessor_id()
722 .is_some_and(|predecessor_id| predecessor_id == self.room_id())
723 {
724 self.set_successor(&room);
725 return;
726 }
727 }
728 }
729
730 fn predecessor_id_string(&self) -> Option<String> {
733 self.predecessor_id.get().map(ToString::to_string)
734 }
735
736 fn load_predecessor(&self) {
738 let Some(event) = self.matrix_room().create_content() else {
739 return;
740 };
741 let Some(predecessor) = event.predecessor else {
742 return;
743 };
744
745 self.predecessor_id
746 .set(predecessor.room_id)
747 .expect("predecessor ID is uninitialized");
748 self.obj().notify_predecessor_id_string();
749 }
750
751 fn successor_id_string(&self) -> Option<String> {
753 self.successor_id.get().map(ToString::to_string)
754 }
755
756 fn set_successor(&self, successor: &super::Room) {
758 self.successor.set(Some(successor));
759 self.obj().notify_successor();
760
761 self.set_category(RoomCategory::Outdated);
762 }
763
764 fn watch_members(&self) {
766 let matrix_room = self.matrix_room();
767
768 let obj_weak = glib::SendWeakRef::from(self.obj().downgrade());
769 let handle = matrix_room.add_event_handler(move |event: SyncRoomMemberEvent| {
770 let obj_weak = obj_weak.clone();
771 async move {
772 let ctx = glib::MainContext::default();
773 ctx.spawn(async move {
774 spawn!(async move {
775 if let Some(obj) = obj_weak.upgrade() {
776 obj.imp().handle_member_event(&event);
777 }
778 });
779 });
780 }
781 });
782
783 let drop_guard = matrix_room.client().event_handler_drop_guard(handle);
784 self.members_drop_guard.set(drop_guard).unwrap();
785 }
786
787 fn handle_member_event(&self, event: &SyncRoomMemberEvent) {
789 let user_id = event.state_key();
790
791 if let Some(members) = self.members.upgrade() {
792 members.update_member(user_id.clone());
793 } else if user_id == self.own_member().user_id() {
794 self.own_member().update();
795 } else if let Some(member) = self
796 .direct_member
797 .borrow()
798 .as_ref()
799 .filter(|member| member.user_id() == user_id)
800 {
801 member.update();
802 }
803
804 spawn!(clone!(
806 #[weak(rename_to = imp)]
807 self,
808 async move {
809 imp.update_direct_member().await;
810 }
811 ));
812 }
813
814 fn set_joined_members_count(&self, count: u64) {
817 if self.joined_members_count.get() == count {
818 return;
819 }
820
821 self.joined_members_count.set(count);
822 self.obj().notify_joined_members_count();
823 }
824
825 pub(super) fn own_member(&self) -> &Member {
827 self.own_member.get().expect("Own member was initialized")
828 }
829
830 async fn load_own_member(&self) {
832 let own_member = self.own_member();
833 let user_id = own_member.user_id().clone();
834 let matrix_room = self.matrix_room().clone();
835
836 let handle =
837 spawn_tokio!(async move { matrix_room.get_member_no_sync(&user_id).await });
838
839 match handle.await.expect("task was not aborted") {
840 Ok(Some(matrix_member)) => own_member.update_from_room_member(&matrix_member),
841 Ok(None) => {}
842 Err(error) => error!(
843 "Could not load own member for room {}: {error}",
844 self.room_id()
845 ),
846 }
847 }
848
849 async fn update_is_invite(&self) {
852 let matrix_room = self.matrix_room();
853
854 let is_invite = match matrix_room.state() {
855 RoomState::Invited => true,
856 RoomState::Left | RoomState::Banned => {
857 self.was_membership(&MembershipState::Invite).await
858 }
859 _ => false,
860 };
861
862 if self.is_invite.get() == is_invite {
863 return;
864 }
865
866 self.is_invite.set(is_invite);
867 self.obj().notify_is_invite();
868 }
869
870 async fn was_membership(&self, membership: &MembershipState) -> bool {
873 let matrix_room = self.matrix_room();
874
875 let matrix_room_clone = matrix_room.clone();
879 let handle = spawn_tokio!(async move {
880 matrix_room_clone
881 .get_state_event_static_for_key::<RoomMemberEventContent, _>(
882 matrix_room_clone.own_user_id(),
883 )
884 .await
885 });
886
887 let raw_member_event = match handle.await.expect("task was not aborted") {
888 Ok(Some(raw_member_event)) => raw_member_event,
889 Ok(None) => {
890 return false;
891 }
892 Err(error) => {
893 error!("Could not get own member event: {error}");
894 return false;
895 }
896 };
897
898 let member_event = match raw_member_event {
899 RawSyncOrStrippedState::Sync(raw) => {
900 raw.deserialize_as_unchecked::<RoomMemberMembershipEvent>()
901 }
902 RawSyncOrStrippedState::Stripped(raw) => raw.deserialize_as_unchecked(),
903 };
904
905 let member_event = match member_event {
906 Ok(member_event) => member_event,
907 Err(error) => {
908 warn!("Could not deserialize room member event: {error}");
909 return false;
910 }
911 };
912
913 if member_event.content.membership == *membership {
916 return true;
917 }
918
919 if let Some(prev_content) = member_event
922 .unsigned
923 .as_ref()
924 .and_then(|unsigned| unsigned.prev_content.as_ref())
925 {
926 return prev_content.membership == *membership;
927 }
928
929 let Some(replaces_state) = member_event
932 .unsigned
933 .and_then(|unsigned| unsigned.replaces_state)
934 else {
935 return false;
936 };
937
938 let matrix_room = matrix_room.clone();
939 let handle = spawn_tokio!(async move {
940 matrix_room.load_or_fetch_event(&replaces_state, None).await
941 });
942
943 let raw_prev_member_event = match handle.await.expect("task was not aborted") {
944 Ok(event) => event,
945 Err(error) => {
946 warn!("Could not fetch previous member event: {error}");
947 return false;
948 }
949 };
950
951 match raw_prev_member_event
952 .kind
953 .raw()
954 .deserialize_as_unchecked::<RoomMemberMembershipEvent>()
955 {
956 Ok(prev_member_event) => prev_member_event.content.membership == *membership,
957 Err(error) => {
958 warn!("Could not deserialize previous member event: {error}");
959 false
960 }
961 }
962 }
963
964 async fn update_inviter(&self) {
966 let matrix_room = self.matrix_room();
967
968 if matrix_room.state() != RoomState::Invited {
970 if self.inviter.take().is_some() {
971 self.obj().notify_inviter();
972 }
973
974 return;
975 }
976
977 let matrix_room = matrix_room.clone();
978 let handle = spawn_tokio!(async move { matrix_room.invite_details().await });
979
980 let invite = match handle.await.expect("task was not aborted") {
981 Ok(invite) => invite,
982 Err(error) => {
983 error!("Could not get invite: {error}");
984 return;
985 }
986 };
987
988 let Some(inviter_member) = invite.inviter else {
989 if self.inviter.take().is_some() {
990 self.obj().notify_inviter();
991 }
992 return;
993 };
994
995 if let Some(inviter) = self
996 .inviter
997 .borrow()
998 .as_ref()
999 .filter(|inviter| inviter.user_id() == inviter_member.user_id())
1000 {
1001 inviter.update_from_room_member(&inviter_member);
1003
1004 return;
1005 }
1006
1007 let inviter = Member::new(&self.obj(), inviter_member.user_id().to_owned());
1008 inviter.update_from_room_member(&inviter_member);
1009
1010 inviter
1011 .upcast_ref::<User>()
1012 .connect_is_ignored_notify(clone!(
1013 #[weak(rename_to = imp)]
1014 self,
1015 move |_| {
1016 spawn!(async move {
1017 imp.update_category().await;
1019 });
1020 }
1021 ));
1022
1023 self.inviter.replace(Some(inviter));
1024
1025 self.obj().notify_inviter();
1026 }
1027
1028 fn set_direct_member(&self, member: Option<Member>) {
1031 if *self.direct_member.borrow() == member {
1032 return;
1033 }
1034
1035 self.direct_member.replace(member);
1036 self.obj().notify_direct_member();
1037 self.update_avatar();
1038 }
1039
1040 async fn direct_user_id(&self) -> Option<OwnedUserId> {
1043 let matrix_room = self.matrix_room();
1044
1045 let mut direct_targets = matrix_room
1047 .direct_targets()
1048 .into_iter()
1049 .filter_map(|id| OwnedUserId::try_from(id).ok());
1050
1051 let Some(direct_target_user_id) = direct_targets.next() else {
1052 return None;
1054 };
1055
1056 if direct_targets.next().is_some() {
1057 return None;
1059 }
1060
1061 let members_count = matrix_room.active_members_count();
1063
1064 if members_count > 2 {
1065 return None;
1068 }
1069
1070 let matrix_room_clone = matrix_room.clone();
1073 let handle =
1074 spawn_tokio!(
1075 async move { matrix_room_clone.members(RoomMemberships::ACTIVE).await }
1076 );
1077
1078 let members = match handle.await.expect("task was not aborted") {
1079 Ok(m) => m,
1080 Err(error) => {
1081 error!("Could not load room members: {error}");
1082 vec![]
1083 }
1084 };
1085
1086 let members_count = members_count.max(members.len() as u64);
1087 if members_count > 2 {
1088 return None;
1090 }
1091
1092 let own_user_id = matrix_room.own_user_id();
1093 for member in members {
1095 let user_id = member.user_id();
1096
1097 if user_id != direct_target_user_id && user_id != own_user_id {
1098 return None;
1100 }
1101 }
1102
1103 Some(direct_target_user_id)
1104 }
1105
1106 async fn update_direct_member(&self) {
1109 let Some(direct_user_id) = self.direct_user_id().await else {
1110 self.set_direct_member(None);
1111 return;
1112 };
1113
1114 if self
1115 .direct_member
1116 .borrow()
1117 .as_ref()
1118 .is_some_and(|m| *m.user_id() == direct_user_id)
1119 {
1120 return;
1122 }
1123
1124 let direct_member = if let Some(members) = self.members.upgrade() {
1125 members.get_or_create(direct_user_id.clone())
1126 } else {
1127 Member::new(&self.obj(), direct_user_id.clone())
1128 };
1129
1130 let matrix_room = self.matrix_room().clone();
1131 let handle =
1132 spawn_tokio!(async move { matrix_room.get_member_no_sync(&direct_user_id).await });
1133
1134 match handle.await.expect("task was not aborted") {
1135 Ok(Some(matrix_member)) => {
1136 direct_member.update_from_room_member(&matrix_member);
1137 }
1138 Ok(None) => {}
1139 Err(error) => {
1140 error!("Could not get direct member: {error}");
1141 }
1142 }
1143
1144 self.set_direct_member(Some(direct_member));
1145 }
1146
1147 fn init_live_timeline(&self) {
1149 let timeline = self
1150 .live_timeline
1151 .get_or_init(|| Timeline::new(&self.obj()));
1152
1153 timeline.connect_read_change_trigger(clone!(
1154 #[weak(rename_to = imp)]
1155 self,
1156 move |_| {
1157 spawn!(glib::Priority::DEFAULT_IDLE, async move {
1158 imp.handle_read_change_trigger().await;
1159 });
1160 }
1161 ));
1162 }
1163
1164 fn live_timeline(&self) -> &Timeline {
1166 self.live_timeline
1167 .get()
1168 .expect("live timeline is initialized")
1169 }
1170
1171 pub(super) fn set_latest_activity(&self, latest_activity: u64) {
1173 if self.latest_activity.get() == latest_activity {
1174 return;
1175 }
1176
1177 self.latest_activity.set(latest_activity);
1178 self.obj().notify_latest_activity();
1179 }
1180
1181 async fn update_is_marked_unread(&self) {
1183 let is_marked_unread = self.matrix_room().is_marked_unread();
1184
1185 if self.is_marked_unread.get() == is_marked_unread {
1186 return;
1187 }
1188
1189 self.is_marked_unread.set(is_marked_unread);
1190 self.handle_read_change_trigger().await;
1191 self.obj().notify_is_marked_unread();
1192 }
1193
1194 fn set_is_read(&self, is_read: bool) {
1196 if self.is_read.get() == is_read {
1197 return;
1198 }
1199
1200 self.is_read.set(is_read);
1201 self.obj().notify_is_read();
1202 }
1203
1204 async fn handle_read_change_trigger(&self) {
1206 let timeline = self.live_timeline();
1207
1208 if self.is_marked_unread.get() {
1209 self.set_is_read(false);
1210 } else if let Some(has_unread) = timeline.has_unread_messages().await {
1211 self.set_is_read(!has_unread);
1212 }
1213
1214 self.update_highlight();
1215 }
1216
1217 fn set_highlight(&self, highlight: HighlightFlags) {
1219 if self.highlight.get() == highlight {
1220 return;
1221 }
1222
1223 self.highlight.set(highlight);
1224 self.obj().notify_highlight();
1225 }
1226
1227 fn update_highlight(&self) {
1229 let mut highlight = HighlightFlags::empty();
1230
1231 if matches!(self.category.get(), RoomCategory::Left) {
1232 self.set_highlight(highlight);
1234 self.set_notification_count(0);
1235 return;
1236 }
1237
1238 if self.is_read.get() {
1239 self.set_notification_count(0);
1240 } else {
1241 let counts = self.matrix_room().unread_notification_counts();
1242
1243 if counts.highlight_count > 0 {
1244 highlight = HighlightFlags::all();
1245 } else {
1246 highlight = HighlightFlags::BOLD;
1247 }
1248 self.set_notification_count(counts.notification_count);
1249 }
1250
1251 self.set_highlight(highlight);
1252 }
1253
1254 fn set_notification_count(&self, count: u64) {
1256 if self.notification_count.get() == count {
1257 return;
1258 }
1259
1260 self.notification_count.set(count);
1261 self.set_has_notifications(count > 0);
1262 self.obj().notify_notification_count();
1263 }
1264
1265 fn set_has_notifications(&self, has_notifications: bool) {
1267 if self.has_notifications.get() == has_notifications {
1268 return;
1269 }
1270
1271 self.has_notifications.set(has_notifications);
1272 self.obj().notify_has_notifications();
1273 }
1274
1275 async fn update_is_encrypted(&self) {
1277 let matrix_room = self.matrix_room();
1278 let matrix_room_clone = matrix_room.clone();
1279 let handle =
1280 spawn_tokio!(async move { matrix_room_clone.latest_encryption_state().await });
1281
1282 match handle.await.expect("task was not aborted") {
1283 Ok(state) => {
1284 if state.is_encrypted() {
1285 self.is_encrypted.set(true);
1286 self.obj().notify_is_encrypted();
1287 }
1288 }
1289 Err(error) => {
1290 if matches!(matrix_room.state(), RoomState::Invited | RoomState::Knocked)
1293 && error
1294 .as_client_api_error()
1295 .is_some_and(|e| e.status_code.is_client_error())
1296 {
1297 debug!("Could not load room encryption state: {error}");
1298 } else {
1299 error!("Could not load room encryption state: {error}");
1300 }
1301 }
1302 }
1303 }
1304
1305 fn update_guests_allowed(&self) {
1307 let matrix_room = self.matrix_room();
1308 let guests_allowed = matrix_room.guest_access() == GuestAccess::CanJoin;
1309
1310 if self.guests_allowed.get() == guests_allowed {
1311 return;
1312 }
1313
1314 self.guests_allowed.set(guests_allowed);
1315 self.obj().notify_guests_allowed();
1316 }
1317
1318 fn update_history_visibility(&self) {
1320 let matrix_room = self.matrix_room();
1321 let visibility = matrix_room.history_visibility_or_default().into();
1322
1323 if self.history_visibility.get() == visibility {
1324 return;
1325 }
1326
1327 self.history_visibility.set(visibility);
1328 self.obj().notify_history_visibility();
1329 }
1330
1331 fn version(&self) -> String {
1333 self.matrix_room()
1334 .create_content()
1335 .map(|c| c.room_version.to_string())
1336 .unwrap_or_default()
1337 }
1338
1339 fn is_call(&self) -> bool {
1343 self.matrix_room().is_call()
1344 }
1345
1346 pub(super) fn rules(&self) -> RoomVersionRules {
1348 self.matrix_room()
1349 .clone_info()
1350 .room_version_rules_or_default()
1351 }
1352
1353 fn federated(&self) -> bool {
1355 self.matrix_room()
1356 .create_content()
1357 .is_some_and(|c| c.federate)
1358 }
1359
1360 fn set_up_typing(&self) {
1362 if self.typing_drop_guard.get().is_some() {
1363 return;
1365 }
1366
1367 let matrix_room = self.matrix_room();
1368 if matrix_room.state() != RoomState::Joined {
1369 return;
1370 }
1371
1372 let (typing_drop_guard, receiver) = matrix_room.subscribe_to_typing_notifications();
1373 let stream = BroadcastStream::new(receiver);
1374
1375 let obj_weak = glib::SendWeakRef::from(self.obj().downgrade());
1376 let fut = stream.for_each(move |typing_user_ids| {
1377 let obj_weak = obj_weak.clone();
1378 async move {
1379 let Ok(typing_user_ids) = typing_user_ids else {
1380 return;
1381 };
1382
1383 let ctx = glib::MainContext::default();
1384 ctx.spawn(async move {
1385 spawn!(async move {
1386 if let Some(obj) = obj_weak.upgrade() {
1387 obj.imp().update_typing_list(typing_user_ids);
1388 }
1389 });
1390 });
1391 }
1392 });
1393 spawn_tokio!(fut);
1394
1395 self.typing_drop_guard
1396 .set(typing_drop_guard)
1397 .expect("typing drop guard is uninitialized");
1398 }
1399
1400 fn update_typing_list(&self, typing_user_ids: Vec<OwnedUserId>) {
1402 let Some(session) = self.session.upgrade() else {
1403 return;
1404 };
1405
1406 let Some(members) = self.members.upgrade() else {
1407 self.typing_list.update(vec![]);
1410 return;
1411 };
1412
1413 let own_user_id = session.user_id();
1414
1415 let members = typing_user_ids
1416 .into_iter()
1417 .filter(|user_id| user_id != own_user_id)
1418 .map(|user_id| members.get_or_create(user_id))
1419 .collect();
1420
1421 self.typing_list.update(members);
1422 }
1423
1424 fn set_notifications_setting(&self, setting: NotificationsRoomSetting) {
1426 if self.notifications_setting.get() == setting {
1427 return;
1428 }
1429
1430 self.notifications_setting.set(setting);
1431 self.obj().notify_notifications_setting();
1432 }
1433
1434 fn set_verification(&self, verification: Option<IdentityVerification>) {
1436 if self.verification.obj().is_some() && verification.is_some() {
1437 return;
1440 }
1441
1442 self.verification.disconnect_signals();
1443
1444 let verification = verification.or_else(|| {
1445 let room_id = self.matrix_room().room_id();
1447 self.session
1448 .upgrade()
1449 .map(|s| s.verification_list())
1450 .and_then(|list| list.ongoing_room_verification(room_id))
1451 });
1452
1453 if let Some(verification) = &verification {
1454 let state_handler = verification.connect_is_finished_notify(clone!(
1455 #[weak(rename_to = imp)]
1456 self,
1457 move |_| {
1458 imp.set_verification(None);
1459 }
1460 ));
1461
1462 let dismiss_handler = verification.connect_dismiss(clone!(
1463 #[weak(rename_to = imp)]
1464 self,
1465 move |_| {
1466 imp.set_verification(None);
1467 }
1468 ));
1469
1470 self.verification
1471 .set(verification, vec![state_handler, dismiss_handler]);
1472 }
1473
1474 self.obj().notify_verification();
1475 }
1476
1477 fn watch_room_info(&self) {
1479 let matrix_room = self.matrix_room();
1480 let subscriber = matrix_room.subscribe_info();
1481
1482 let obj_weak = glib::SendWeakRef::from(self.obj().downgrade());
1483 let fut = subscriber.for_each(move |room_info| {
1484 let obj_weak = obj_weak.clone();
1485 async move {
1486 let ctx = glib::MainContext::default();
1487 ctx.spawn(async move {
1488 spawn!(async move {
1489 if let Some(obj) = obj_weak.upgrade() {
1490 obj.imp().update_with_room_info(room_info).await;
1491 }
1492 });
1493 });
1494 }
1495 });
1496 spawn_tokio!(fut);
1497 }
1498
1499 async fn update_with_room_info(&self, room_info: RoomInfo) {
1501 self.aliases.update();
1502 self.update_name();
1503 self.update_display_name().await;
1504 self.update_avatar();
1505 self.update_topic();
1506 self.update_category().await;
1507 self.update_is_direct().await;
1508 self.update_is_marked_unread().await;
1509 self.update_tombstone();
1510 self.set_joined_members_count(room_info.joined_members_count());
1511 self.update_is_encrypted().await;
1512 self.join_rule.update(room_info.join_rule());
1513 self.update_guests_allowed();
1514 self.update_history_visibility();
1515 }
1516
1517 pub(super) fn handle_ambiguity_changes<'a>(
1519 &self,
1520 changes: impl Iterator<Item = &'a AmbiguityChange>,
1521 ) {
1522 let user_ids = changes
1524 .flat_map(AmbiguityChange::user_ids)
1525 .collect::<HashSet<_>>();
1526
1527 if let Some(members) = self.members.upgrade() {
1528 for user_id in user_ids {
1529 members.update_member(user_id.to_owned());
1530 }
1531 } else {
1532 let own_member = self.own_member();
1533 let own_user_id = own_member.user_id();
1534
1535 if user_ids.contains(&**own_user_id) {
1536 own_member.update();
1537 }
1538 }
1539 }
1540
1541 fn watch_send_queue(&self) {
1543 let matrix_room = self.matrix_room().clone();
1544
1545 let room_weak = glib::SendWeakRef::from(self.obj().downgrade());
1546 spawn_tokio!(async move {
1547 let send_queue = matrix_room.send_queue();
1548 let subscriber = match send_queue.subscribe().await {
1549 Ok((_, subscriber)) => BroadcastStream::new(subscriber),
1550 Err(error) => {
1551 warn!("Failed to listen to room send queue: {error}");
1552 return;
1553 }
1554 };
1555
1556 subscriber
1557 .for_each(move |update| {
1558 let room_weak = room_weak.clone();
1559 async move {
1560 let Ok(RoomSendQueueUpdate::SendError {
1561 error,
1562 is_recoverable: true,
1563 ..
1564 }) = update
1565 else {
1566 return;
1567 };
1568
1569 let ctx = glib::MainContext::default();
1570 ctx.spawn(async move {
1571 spawn!(async move {
1572 let Some(obj) = room_weak.upgrade() else {
1573 return;
1574 };
1575 let Some(session) = obj.session() else {
1576 return;
1577 };
1578
1579 if session.is_offline() {
1580 return;
1583 }
1584
1585 let duration = match error.client_api_error_kind() {
1586 Some(ErrorKind::LimitExceeded(
1587 LimitExceededErrorData {
1588 retry_after: Some(retry_after),
1589 ..
1590 },
1591 )) => match retry_after {
1592 RetryAfter::Delay(duration) => Some(*duration),
1593 RetryAfter::DateTime(time) => {
1594 time.duration_since(SystemTime::now()).ok()
1595 }
1596 },
1597 _ => None,
1598 };
1599 let retry_after = duration
1600 .and_then(|d| d.as_secs().try_into().ok())
1601 .unwrap_or(DEFAULT_RETRY_AFTER);
1602
1603 glib::timeout_add_seconds_local_once(retry_after, move || {
1604 let matrix_room = obj.matrix_room().clone();
1605 spawn_tokio!(async move {
1607 matrix_room.send_queue().set_enabled(true);
1608 });
1609 });
1610 });
1611 });
1612 }
1613 })
1614 .await;
1615 });
1616 }
1617
1618 pub(super) async fn change_category(
1628 &self,
1629 category: TargetRoomCategory,
1630 ) -> MatrixResult<()> {
1631 let previous_category = self.category.get();
1632
1633 if previous_category == category {
1634 return Ok(());
1635 }
1636
1637 if previous_category == RoomCategory::Outdated {
1638 warn!("Cannot change the category of an upgraded room");
1639 return Ok(());
1640 }
1641
1642 self.set_category(category.into());
1643
1644 let matrix_room = self.matrix_room().clone();
1645 let handle = spawn_tokio!(async move {
1646 let room_state = matrix_room.state();
1647
1648 match category {
1649 TargetRoomCategory::Favorite => {
1650 if !matrix_room.is_favourite() {
1651 matrix_room.set_is_favourite(true, None).await?;
1653 } else if matrix_room.is_low_priority() {
1654 matrix_room.set_is_low_priority(false, None).await?;
1655 }
1656
1657 if matches!(room_state, RoomState::Invited | RoomState::Left) {
1658 matrix_room.join().await?;
1659 }
1660 }
1661 TargetRoomCategory::Normal => {
1662 if matrix_room.is_favourite() {
1663 matrix_room.set_is_favourite(false, None).await?;
1664 }
1665 if matrix_room.is_low_priority() {
1666 matrix_room.set_is_low_priority(false, None).await?;
1667 }
1668
1669 if matches!(room_state, RoomState::Invited | RoomState::Left) {
1670 matrix_room.join().await?;
1671 }
1672 }
1673 TargetRoomCategory::LowPriority => {
1674 if !matrix_room.is_low_priority() {
1675 matrix_room.set_is_low_priority(true, None).await?;
1677 } else if matrix_room.is_favourite() {
1678 matrix_room.set_is_favourite(false, None).await?;
1679 }
1680
1681 if matches!(room_state, RoomState::Invited | RoomState::Left) {
1682 matrix_room.join().await?;
1683 }
1684 }
1685 TargetRoomCategory::Left => {
1686 if matches!(
1687 room_state,
1688 RoomState::Knocked | RoomState::Invited | RoomState::Joined
1689 ) {
1690 matrix_room.leave().await?;
1691 }
1692 }
1693 }
1694
1695 Result::<_, matrix_sdk::Error>::Ok(())
1696 });
1697
1698 match handle.await.expect("task was not aborted") {
1699 Ok(()) => Ok(()),
1700 Err(error) => {
1701 error!("Could not set the room category: {error}");
1702
1703 Box::pin(self.update_category()).await;
1705
1706 Err(error)
1707 }
1708 }
1709 }
1710 }
1711}
1712
1713glib::wrapper! {
1714 pub struct Room(ObjectSubclass<imp::Room>) @extends PillSource;
1718}
1719
1720impl Room {
1721 pub fn new(session: &Session, matrix_room: MatrixRoom, metainfo: Option<RoomMetainfo>) -> Self {
1723 let this = glib::Object::builder::<Self>()
1724 .property("session", session)
1725 .build();
1726
1727 this.imp().init(matrix_room, metainfo);
1728 this
1729 }
1730
1731 pub(crate) fn matrix_room(&self) -> &MatrixRoom {
1733 self.imp().matrix_room()
1734 }
1735
1736 pub(crate) fn room_id(&self) -> &RoomId {
1738 self.imp().room_id()
1739 }
1740
1741 pub fn human_readable_id(&self) -> String {
1746 format!("{} ({})", self.display_name(), self.room_id())
1747 }
1748
1749 pub(crate) fn rules(&self) -> RoomVersionRules {
1751 self.imp().rules()
1752 }
1753
1754 pub(crate) fn is_joined(&self) -> bool {
1756 self.own_member().membership() == Membership::Join
1757 }
1758
1759 pub(crate) fn predecessor_id(&self) -> Option<&OwnedRoomId> {
1762 self.imp().predecessor_id.get()
1763 }
1764
1765 pub(crate) fn successor_id(&self) -> Option<&OwnedRoomId> {
1767 self.imp().successor_id.get()
1768 }
1769
1770 pub(crate) async fn matrix_to_uri(&self) -> MatrixToUri {
1772 let matrix_room = self.matrix_room().clone();
1773
1774 let handle = spawn_tokio!(async move { matrix_room.matrix_to_permalink().await });
1775 match handle.await.expect("task was not aborted") {
1776 Ok(permalink) => {
1777 return permalink;
1778 }
1779 Err(error) => {
1780 error!("Could not get room event permalink: {error}");
1781 }
1782 }
1783
1784 self.room_id().matrix_to_uri()
1786 }
1787
1788 pub(crate) async fn matrix_to_event_uri(&self, event_id: OwnedEventId) -> MatrixToUri {
1790 let matrix_room = self.matrix_room().clone();
1791
1792 let event_id_clone = event_id.clone();
1793 let handle =
1794 spawn_tokio!(
1795 async move { matrix_room.matrix_to_event_permalink(event_id_clone).await }
1796 );
1797 match handle.await.expect("task was not aborted") {
1798 Ok(permalink) => {
1799 return permalink;
1800 }
1801 Err(error) => {
1802 error!("Could not get room event permalink: {error}");
1803 }
1804 }
1805
1806 self.room_id().matrix_to_event_uri(event_id)
1808 }
1809
1810 pub(crate) fn at_room(&self) -> AtRoom {
1812 AtRoom::new(self)
1813 }
1814
1815 pub(crate) fn get_or_create_members(&self) -> MemberList {
1819 let members = &self.imp().members;
1820 if let Some(list) = members.upgrade() {
1821 list
1822 } else {
1823 let list = MemberList::new(self);
1824 members.set(Some(&list));
1825 self.notify_members();
1826 list
1827 }
1828 }
1829
1830 pub(crate) async fn change_category(&self, category: TargetRoomCategory) -> MatrixResult<()> {
1839 self.imp().change_category(category).await
1840 }
1841
1842 pub(crate) async fn toggle_reaction(&self, key: String, event: &Event) -> Result<(), ()> {
1844 let matrix_timeline = self.live_timeline().matrix_timeline();
1845 let identifier = event.identifier();
1846
1847 let handle =
1848 spawn_tokio!(async move { matrix_timeline.toggle_reaction(&identifier, &key).await });
1849
1850 if let Err(error) = handle.await.expect("task was not aborted") {
1851 error!("Could not toggle reaction: {error}");
1852 return Err(());
1853 }
1854
1855 Ok(())
1856 }
1857
1858 pub(crate) async fn send_receipt(
1862 &self,
1863 receipt_type: ApiReceiptType,
1864 position: ReceiptPosition,
1865 ) {
1866 let Some(session) = self.session() else {
1867 return;
1868 };
1869 let send_public_receipt = session.settings().public_read_receipts_enabled();
1870
1871 let receipt_type = match receipt_type {
1872 ApiReceiptType::Read if !send_public_receipt => ApiReceiptType::ReadPrivate,
1873 t => t,
1874 };
1875
1876 let matrix_timeline = self.live_timeline().matrix_timeline();
1877 let handle = spawn_tokio!(async move {
1878 match position {
1879 ReceiptPosition::End => matrix_timeline.mark_as_read(receipt_type).await,
1880 ReceiptPosition::Event(event_id) => {
1881 matrix_timeline
1882 .send_single_receipt(receipt_type, event_id)
1883 .await
1884 }
1885 }
1886 });
1887
1888 if let Err(error) = handle.await.expect("task was not aborted") {
1889 error!("Could not send read receipt: {error}");
1890 }
1891 }
1892
1893 pub(crate) async fn mark_as_unread(&self) {
1895 let matrix_room = self.matrix_room().clone();
1896 let handle = spawn_tokio!(async move { matrix_room.set_unread_flag(true).await });
1897
1898 if let Err(error) = handle.await.expect("task was not aborted") {
1899 error!("Could not mark room as unread: {error}");
1900 }
1901 }
1902
1903 pub(crate) fn send_typing_notification(&self, is_typing: bool) {
1905 let matrix_room = self.matrix_room();
1906 if matrix_room.state() != RoomState::Joined {
1907 return;
1908 }
1909
1910 let matrix_room = matrix_room.clone();
1911 let handle = spawn_tokio!(async move { matrix_room.typing_notice(is_typing).await });
1912
1913 spawn!(glib::Priority::DEFAULT_IDLE, async move {
1914 match handle.await.expect("task was not aborted") {
1915 Ok(()) => {}
1916 Err(error) => error!("Could not send typing notification: {error}"),
1917 }
1918 });
1919 }
1920
1921 pub(crate) async fn redact<'a>(
1926 &self,
1927 events: &'a [OwnedEventId],
1928 reason: Option<String>,
1929 ) -> Result<(), Vec<&'a EventId>> {
1930 let matrix_room = self.matrix_room();
1931 if matrix_room.state() != RoomState::Joined {
1932 return Ok(());
1933 }
1934
1935 let events_clone = events.to_owned();
1936 let matrix_room = matrix_room.clone();
1937 let handle = spawn_tokio!(async move {
1938 let mut failed_redactions = Vec::new();
1939
1940 for (i, event_id) in events_clone.iter().enumerate() {
1941 match matrix_room.redact(event_id, reason.as_deref(), None).await {
1942 Ok(_) => {}
1943 Err(error) => {
1944 error!("Could not redact event with ID {event_id}: {error}");
1945 failed_redactions.push(i);
1946 }
1947 }
1948 }
1949
1950 failed_redactions
1951 });
1952
1953 let failed_redactions = handle.await.expect("task was not aborted");
1954 let failed_redactions = failed_redactions
1955 .into_iter()
1956 .map(|i| &*events[i])
1957 .collect::<Vec<_>>();
1958
1959 if failed_redactions.is_empty() {
1960 Ok(())
1961 } else {
1962 Err(failed_redactions)
1963 }
1964 }
1965
1966 pub(crate) async fn report_events<'a>(
1973 &self,
1974 events: &'a [(OwnedEventId, Option<String>)],
1975 ) -> Result<(), Vec<&'a EventId>> {
1976 let events_clone = events.to_owned();
1977 let matrix_room = self.matrix_room().clone();
1978 let handle = spawn_tokio!(async move {
1979 let futures = events_clone
1980 .into_iter()
1981 .map(|(event_id, reason)| matrix_room.report_content(event_id, reason));
1982 futures_util::future::join_all(futures).await
1983 });
1984
1985 let mut failed = Vec::new();
1986 for (index, result) in handle
1987 .await
1988 .expect("task was not aborted")
1989 .iter()
1990 .enumerate()
1991 {
1992 match result {
1993 Ok(_) => {}
1994 Err(error) => {
1995 error!(
1996 "Could not report content with event ID {}: {error}",
1997 events[index].0,
1998 );
1999 failed.push(&*events[index].0);
2000 }
2001 }
2002 }
2003
2004 if failed.is_empty() {
2005 Ok(())
2006 } else {
2007 Err(failed)
2008 }
2009 }
2010
2011 pub(crate) async fn invite<'a>(
2016 &self,
2017 user_ids: &'a [OwnedUserId],
2018 ) -> Result<(), Vec<&'a UserId>> {
2019 let matrix_room = self.matrix_room();
2020 if matrix_room.state() != RoomState::Joined {
2021 error!("Can’t invite users, because this room isn’t a joined room");
2022 return Ok(());
2023 }
2024
2025 let user_ids_clone = user_ids.to_owned();
2026 let matrix_room = matrix_room.clone();
2027 let handle = spawn_tokio!(async move {
2028 let invitations = user_ids_clone
2029 .iter()
2030 .map(|user_id| matrix_room.invite_user_by_id(user_id));
2031 futures_util::future::join_all(invitations).await
2032 });
2033
2034 let mut failed_invites = Vec::new();
2035 for (index, result) in handle
2036 .await
2037 .expect("task was not aborted")
2038 .iter()
2039 .enumerate()
2040 {
2041 match result {
2042 Ok(()) => {}
2043 Err(error) => {
2044 error!("Could not invite user with ID {}: {error}", user_ids[index],);
2045 failed_invites.push(&*user_ids[index]);
2046 }
2047 }
2048 }
2049
2050 if failed_invites.is_empty() {
2051 Ok(())
2052 } else {
2053 Err(failed_invites)
2054 }
2055 }
2056
2057 pub(crate) async fn kick<'a>(
2064 &self,
2065 users: &'a [(OwnedUserId, Option<String>)],
2066 ) -> Result<(), Vec<&'a UserId>> {
2067 let users_clone = users.to_owned();
2068 let matrix_room = self.matrix_room().clone();
2069 let handle = spawn_tokio!(async move {
2070 let futures = users_clone
2071 .iter()
2072 .map(|(user_id, reason)| matrix_room.kick_user(user_id, reason.as_deref()));
2073 futures_util::future::join_all(futures).await
2074 });
2075
2076 let mut failed_kicks = Vec::new();
2077 for (index, result) in handle
2078 .await
2079 .expect("task was not aborted")
2080 .iter()
2081 .enumerate()
2082 {
2083 match result {
2084 Ok(()) => {}
2085 Err(error) => {
2086 error!("Could not kick user with ID {}: {error}", users[index].0);
2087 failed_kicks.push(&*users[index].0);
2088 }
2089 }
2090 }
2091
2092 if failed_kicks.is_empty() {
2093 Ok(())
2094 } else {
2095 Err(failed_kicks)
2096 }
2097 }
2098
2099 pub(crate) async fn ban<'a>(
2106 &self,
2107 users: &'a [(OwnedUserId, Option<String>)],
2108 ) -> Result<(), Vec<&'a UserId>> {
2109 let users_clone = users.to_owned();
2110 let matrix_room = self.matrix_room().clone();
2111 let handle = spawn_tokio!(async move {
2112 let futures = users_clone
2113 .iter()
2114 .map(|(user_id, reason)| matrix_room.ban_user(user_id, reason.as_deref()));
2115 futures_util::future::join_all(futures).await
2116 });
2117
2118 let mut failed_bans = Vec::new();
2119 for (index, result) in handle
2120 .await
2121 .expect("task was not aborted")
2122 .iter()
2123 .enumerate()
2124 {
2125 match result {
2126 Ok(()) => {}
2127 Err(error) => {
2128 error!("Could not ban user with ID {}: {error}", users[index].0);
2129 failed_bans.push(&*users[index].0);
2130 }
2131 }
2132 }
2133
2134 if failed_bans.is_empty() {
2135 Ok(())
2136 } else {
2137 Err(failed_bans)
2138 }
2139 }
2140
2141 pub(crate) async fn unban<'a>(
2148 &self,
2149 users: &'a [(OwnedUserId, Option<String>)],
2150 ) -> Result<(), Vec<&'a UserId>> {
2151 let users_clone = users.to_owned();
2152 let matrix_room = self.matrix_room().clone();
2153 let handle = spawn_tokio!(async move {
2154 let futures = users_clone
2155 .iter()
2156 .map(|(user_id, reason)| matrix_room.unban_user(user_id, reason.as_deref()));
2157 futures_util::future::join_all(futures).await
2158 });
2159
2160 let mut failed_unbans = Vec::new();
2161 for (index, result) in handle
2162 .await
2163 .expect("task was not aborted")
2164 .iter()
2165 .enumerate()
2166 {
2167 match result {
2168 Ok(()) => {}
2169 Err(error) => {
2170 error!("Could not unban user with ID {}: {error}", users[index].0);
2171 failed_unbans.push(&*users[index].0);
2172 }
2173 }
2174 }
2175
2176 if failed_unbans.is_empty() {
2177 Ok(())
2178 } else {
2179 Err(failed_unbans)
2180 }
2181 }
2182
2183 pub(crate) async fn enable_encryption(&self) -> Result<(), ()> {
2185 if self.is_encrypted() {
2186 return Ok(());
2188 }
2189
2190 let matrix_room = self.matrix_room().clone();
2191 let handle = spawn_tokio!(async move { matrix_room.enable_encryption().await });
2192
2193 match handle.await.expect("task was not aborted") {
2194 Ok(()) => Ok(()),
2195 Err(error) => {
2196 error!("Could not enable room encryption: {error}");
2197 Err(())
2198 }
2199 }
2200 }
2201
2202 pub(crate) async fn forget(&self) -> MatrixResult<()> {
2204 if self.category() != RoomCategory::Left {
2205 warn!("Cannot forget a room that is not left");
2206 return Ok(());
2207 }
2208
2209 let matrix_room = self.matrix_room().clone();
2210 let handle = spawn_tokio!(async move { matrix_room.forget().await });
2211
2212 match handle.await.expect("task was not aborted") {
2213 Ok(()) => {
2214 self.emit_by_name::<()>("room-forgotten", &[]);
2215 Ok(())
2216 }
2217 Err(error) => {
2218 error!("Could not forget the room: {error}");
2219 Err(error)
2220 }
2221 }
2222 }
2223
2224 pub(crate) fn handle_ambiguity_changes<'a>(
2226 &self,
2227 changes: impl Iterator<Item = &'a AmbiguityChange>,
2228 ) {
2229 self.imp().handle_ambiguity_changes(changes);
2230 }
2231
2232 fn update_latest_activity<'a>(&self, events: impl Iterator<Item = &'a Event>) {
2236 let own_user_id = self.imp().own_member().user_id();
2237 let mut latest_activity = self.latest_activity();
2238
2239 for event in events {
2240 if event.counts_as_activity(own_user_id) {
2241 latest_activity = latest_activity.max(event.origin_server_ts().get().into());
2242 break;
2243 }
2244 }
2245
2246 self.imp().set_latest_activity(latest_activity);
2247 }
2248
2249 pub(crate) fn update_successor(&self) {
2251 self.imp().update_successor();
2252 }
2253
2254 pub(crate) fn connect_room_forgotten<F: Fn(&Self) + 'static>(
2256 &self,
2257 f: F,
2258 ) -> glib::SignalHandlerId {
2259 self.connect_closure(
2260 "room-forgotten",
2261 true,
2262 closure_local!(move |obj: Self| {
2263 f(&obj);
2264 }),
2265 )
2266 }
2267}
2268
2269#[derive(Debug, Default, Hash, Eq, PartialEq, Clone, Copy, glib::Enum)]
2271#[enum_type(name = "HistoryVisibilityValue")]
2272pub enum HistoryVisibilityValue {
2273 WorldReadable,
2275 #[default]
2277 Shared,
2278 Invited,
2280 Joined,
2282 Unsupported,
2284}
2285
2286impl From<HistoryVisibility> for HistoryVisibilityValue {
2287 fn from(value: HistoryVisibility) -> Self {
2288 match value {
2289 HistoryVisibility::Invited => Self::Invited,
2290 HistoryVisibility::Joined => Self::Joined,
2291 HistoryVisibility::Shared => Self::Shared,
2292 HistoryVisibility::WorldReadable => Self::WorldReadable,
2293 _ => Self::Unsupported,
2294 }
2295 }
2296}
2297
2298impl From<HistoryVisibilityValue> for HistoryVisibility {
2299 fn from(value: HistoryVisibilityValue) -> Self {
2300 match value {
2301 HistoryVisibilityValue::Invited => Self::Invited,
2302 HistoryVisibilityValue::Joined => Self::Joined,
2303 HistoryVisibilityValue::Shared => Self::Shared,
2304 HistoryVisibilityValue::WorldReadable => Self::WorldReadable,
2305 HistoryVisibilityValue::Unsupported => unimplemented!(),
2306 }
2307 }
2308}
2309
2310#[derive(Debug, Clone)]
2312pub(crate) enum ReceiptPosition {
2313 End,
2315 Event(OwnedEventId),
2317}
2318
2319#[derive(Deserialize)]
2322struct RoomMemberMembershipEvent {
2323 content: RoomMemberMembershipContent,
2324 unsigned: Option<RoomMemberMembershipUnsigned>,
2325}
2326
2327#[derive(Deserialize)]
2330struct RoomMemberMembershipUnsigned {
2331 replaces_state: Option<OwnedEventId>,
2332 prev_content: Option<RoomMemberMembershipContent>,
2333}
2334
2335#[derive(Deserialize)]
2338struct RoomMemberMembershipContent {
2339 membership: MembershipState,
2340}