fractal/session/
ignored_users.rs1use futures_util::StreamExt;
2use gtk::{
3 gio,
4 glib::{self, clone},
5 prelude::*,
6 subclass::prelude::*,
7};
8use indexmap::IndexSet;
9use ruma::{OwnedUserId, events::ignored_user_list::IgnoredUserListEventContent};
10use tracing::{debug, error, warn};
11
12use super::Session;
13use crate::{spawn, spawn_tokio};
14
15mod imp {
16 use std::cell::RefCell;
17
18 use super::*;
19
20 #[derive(Debug, Default, glib::Properties)]
21 #[properties(wrapper_type = super::IgnoredUsers)]
22 pub struct IgnoredUsers {
23 #[property(get, set = Self::set_session, explicit_notify, nullable)]
25 pub session: glib::WeakRef<Session>,
26 pub list: RefCell<IndexSet<OwnedUserId>>,
28 abort_handle: RefCell<Option<tokio::task::AbortHandle>>,
29 }
30
31 #[glib::object_subclass]
32 impl ObjectSubclass for IgnoredUsers {
33 const NAME: &'static str = "IgnoredUsers";
34 type Type = super::IgnoredUsers;
35 type Interfaces = (gio::ListModel,);
36 }
37
38 #[glib::derived_properties]
39 impl ObjectImpl for IgnoredUsers {
40 fn dispose(&self) {
41 if let Some(abort_handle) = self.abort_handle.take() {
42 abort_handle.abort();
43 }
44 }
45 }
46
47 impl ListModelImpl for IgnoredUsers {
48 fn item_type(&self) -> glib::Type {
49 gtk::StringObject::static_type()
50 }
51
52 fn n_items(&self) -> u32 {
53 self.list.borrow().len() as u32
54 }
55
56 fn item(&self, position: u32) -> Option<glib::Object> {
57 self.list
58 .borrow()
59 .get_index(position as usize)
60 .map(|user_id| gtk::StringObject::new(user_id.as_str()).upcast())
61 }
62 }
63
64 impl IgnoredUsers {
65 fn set_session(&self, session: Option<&Session>) {
67 if self.session.upgrade().as_ref() == session {
68 return;
69 }
70
71 self.session.set(session);
72
73 self.init();
74 self.obj().notify_session();
75 }
76
77 fn init(&self) {
79 if let Some(abort_handle) = self.abort_handle.take() {
80 abort_handle.abort();
81 }
82
83 let Some(session) = self.session.upgrade() else {
84 return;
85 };
86 let obj = self.obj();
87
88 let obj_weak = glib::SendWeakRef::from(obj.downgrade());
89 let subscriber = session.client().subscribe_to_ignore_user_list_changes();
90 let fut = subscriber.for_each(move |_| {
91 let obj_weak = obj_weak.clone();
92 async move {
93 let ctx = glib::MainContext::default();
94 ctx.spawn(async move {
95 spawn!(async move {
96 if let Some(obj) = obj_weak.upgrade() {
97 obj.imp().load_list().await;
98 }
99 });
100 });
101 }
102 });
103
104 let abort_handle = spawn_tokio!(fut).abort_handle();
105 self.abort_handle.replace(Some(abort_handle));
106
107 spawn!(clone!(
108 #[weak(rename_to = imp)]
109 self,
110 async move {
111 imp.load_list().await;
112 }
113 ));
114 }
115
116 async fn load_list(&self) {
118 let Some(session) = self.session.upgrade() else {
119 return;
120 };
121
122 let client = session.client();
123 let handle = spawn_tokio!(async move {
124 client
125 .account()
126 .account_data::<IgnoredUserListEventContent>()
127 .await
128 });
129
130 let raw = match handle.await.unwrap() {
131 Ok(Some(raw)) => raw,
132 Ok(None) => {
133 debug!("Got no ignored users list");
134 self.update_list(IndexSet::new());
135 return;
136 }
137 Err(error) => {
138 error!("Could not get ignored users list: {error}");
139 return;
140 }
141 };
142
143 match raw.deserialize() {
144 Ok(content) => self.update_list(content.ignored_users.into_keys().collect()),
145 Err(error) => {
146 error!("Could not deserialize ignored users list: {error}");
147 }
148 }
149 }
150
151 fn update_list(&self, new_list: IndexSet<OwnedUserId>) {
153 if *self.list.borrow() == new_list {
154 return;
155 }
156
157 let old_len = self.n_items();
158 let new_len = new_list.len() as u32;
159
160 let mut pos = 0;
161 {
162 let old_list = self.list.borrow();
163
164 for old_item in old_list.iter() {
165 let Some(new_item) = new_list.get_index(pos as usize) else {
166 break;
167 };
168
169 if old_item != new_item {
170 break;
171 }
172
173 pos += 1;
174 }
175 }
176
177 if old_len == new_len && pos == new_len {
178 return;
180 }
181
182 self.list.replace(new_list);
183
184 self.obj().items_changed(
185 pos,
186 old_len.saturating_sub(pos),
187 new_len.saturating_sub(pos),
188 );
189 }
190 }
191}
192
193glib::wrapper! {
194 pub struct IgnoredUsers(ObjectSubclass<imp::IgnoredUsers>)
196 @implements gio::ListModel;
197}
198
199impl IgnoredUsers {
200 pub fn new() -> Self {
201 glib::Object::new()
202 }
203
204 pub fn contains(&self, user_id: &OwnedUserId) -> bool {
206 self.imp().list.borrow().contains(user_id)
207 }
208
209 pub async fn add(&self, user_id: &OwnedUserId) -> Result<(), ()> {
211 let Some(session) = self.session() else {
212 return Err(());
213 };
214
215 if self.contains(user_id) {
216 warn!(
217 "Trying to add `{user_id}` to the ignored users but they are already in the list, ignoring"
218 );
219 return Ok(());
220 }
221
222 let client = session.client();
223 let user_id_clone = user_id.clone();
224 let handle =
225 spawn_tokio!(async move { client.account().ignore_user(&user_id_clone).await });
226
227 match handle.await.unwrap() {
228 Ok(()) => {
229 let (pos, added) = self.imp().list.borrow_mut().insert_full(user_id.clone());
230
231 if added {
232 self.items_changed(pos as u32, 0, 1);
233 }
234 Ok(())
235 }
236 Err(error) => {
237 error!("Could not add `{user_id}` to the ignored users: {error}");
238 Err(())
239 }
240 }
241 }
242
243 pub async fn remove(&self, user_id: &OwnedUserId) -> Result<(), ()> {
245 let Some(session) = self.session() else {
246 return Err(());
247 };
248
249 if !self.contains(user_id) {
250 warn!(
251 "Trying to remove `{user_id}` from the ignored users but they are not in the list, ignoring"
252 );
253 return Ok(());
254 }
255
256 let client = session.client();
257 let user_id_clone = user_id.clone();
258 let handle =
259 spawn_tokio!(async move { client.account().unignore_user(&user_id_clone).await });
260
261 match handle.await.unwrap() {
262 Ok(()) => {
263 let removed = self.imp().list.borrow_mut().shift_remove_full(user_id);
264
265 if let Some((pos, _)) = removed {
266 self.items_changed(pos as u32, 1, 0);
267 }
268 Ok(())
269 }
270 Err(error) => {
271 error!("Could not remove `{user_id}` from the ignored users: {error}");
272 Err(())
273 }
274 }
275 }
276}
277
278impl Default for IgnoredUsers {
279 fn default() -> Self {
280 Self::new()
281 }
282}