fractal/session/model/
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::{events::ignored_user_list::IgnoredUserListEventContent, OwnedUserId};
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!("Trying to add `{user_id}` to the ignored users but they are already in the list, ignoring");
217 return Ok(());
218 }
219
220 let client = session.client();
221 let user_id_clone = user_id.clone();
222 let handle =
223 spawn_tokio!(async move { client.account().ignore_user(&user_id_clone).await });
224
225 match handle.await.unwrap() {
226 Ok(()) => {
227 let (pos, added) = self.imp().list.borrow_mut().insert_full(user_id.clone());
228
229 if added {
230 self.items_changed(pos as u32, 0, 1);
231 }
232 Ok(())
233 }
234 Err(error) => {
235 error!("Could not add `{user_id}` to the ignored users: {error}");
236 Err(())
237 }
238 }
239 }
240
241 pub async fn remove(&self, user_id: &OwnedUserId) -> Result<(), ()> {
243 let Some(session) = self.session() else {
244 return Err(());
245 };
246
247 if !self.contains(user_id) {
248 warn!("Trying to remove `{user_id}` from the ignored users but they are not in the list, ignoring");
249 return Ok(());
250 }
251
252 let client = session.client();
253 let user_id_clone = user_id.clone();
254 let handle =
255 spawn_tokio!(async move { client.account().unignore_user(&user_id_clone).await });
256
257 match handle.await.unwrap() {
258 Ok(()) => {
259 let removed = self.imp().list.borrow_mut().shift_remove_full(user_id);
260
261 if let Some((pos, _)) = removed {
262 self.items_changed(pos as u32, 1, 0);
263 }
264 Ok(())
265 }
266 Err(error) => {
267 error!("Could not remove `{user_id}` from the ignored users: {error}");
268 Err(())
269 }
270 }
271 }
272}
273
274impl Default for IgnoredUsers {
275 fn default() -> Self {
276 Self::new()
277 }
278}