fractal/session/
global_account_data.rs1use futures_util::StreamExt;
2use gtk::{
3 glib,
4 glib::{clone, closure_local},
5 prelude::*,
6 subclass::prelude::*,
7};
8use ruma::events::media_preview_config::{
9 InviteAvatars, MediaPreviewConfigEventContent, MediaPreviews,
10};
11use tokio::task::AbortHandle;
12use tracing::error;
13
14use super::{Room, Session};
15use crate::{session::JoinRuleValue, spawn, spawn_tokio};
16
17const DEFAULT_MEDIA_PREVIEWS: MediaPreviews = MediaPreviews::Private;
19const DEFAULT_INVITE_AVATARS_ENABLED: bool = true;
21
22mod imp {
23 use std::{
24 cell::{Cell, OnceCell, RefCell},
25 sync::LazyLock,
26 };
27
28 use glib::subclass::Signal;
29
30 use super::*;
31
32 #[derive(Debug, glib::Properties)]
33 #[properties(wrapper_type = super::GlobalAccountData)]
34 pub struct GlobalAccountData {
35 #[property(get, construct_only)]
37 session: OnceCell<Session>,
38 pub(super) media_previews_enabled: RefCell<MediaPreviews>,
40 #[property(get, default = DEFAULT_INVITE_AVATARS_ENABLED)]
42 invite_avatars_enabled: Cell<bool>,
43 abort_handle: RefCell<Option<AbortHandle>>,
44 }
45
46 impl Default for GlobalAccountData {
47 fn default() -> Self {
48 Self {
49 session: Default::default(),
50 media_previews_enabled: RefCell::new(DEFAULT_MEDIA_PREVIEWS),
51 invite_avatars_enabled: Cell::new(DEFAULT_INVITE_AVATARS_ENABLED),
52 abort_handle: Default::default(),
53 }
54 }
55 }
56
57 #[glib::object_subclass]
58 impl ObjectSubclass for GlobalAccountData {
59 const NAME: &'static str = "GlobalAccountData";
60 type Type = super::GlobalAccountData;
61 }
62
63 #[glib::derived_properties]
64 impl ObjectImpl for GlobalAccountData {
65 fn signals() -> &'static [Signal] {
66 static SIGNALS: LazyLock<Vec<Signal>> =
67 LazyLock::new(|| vec![Signal::builder("media-previews-enabled-changed").build()]);
68 SIGNALS.as_ref()
69 }
70
71 fn constructed(&self) {
72 self.parent_constructed();
73
74 spawn!(clone!(
75 #[weak(rename_to = imp)]
76 self,
77 async move {
78 imp.init_media_previews_settings().await;
79 imp.apply_migrations().await;
80 }
81 ));
82 }
83
84 fn dispose(&self) {
85 if let Some(handle) = self.abort_handle.take() {
86 handle.abort();
87 }
88 }
89 }
90
91 impl GlobalAccountData {
92 fn session(&self) -> &Session {
94 self.session.get().expect("session should be initialized")
95 }
96
97 pub(super) async fn init_media_previews_settings(&self) {
100 let client = self.session().client();
101 let handle =
102 spawn_tokio!(async move { client.account().observe_media_preview_config().await });
103
104 let (account_data, stream) = match handle.await.expect("task was not aborted") {
105 Ok((account_data, stream)) => (account_data, stream),
106 Err(error) => {
107 error!("Could not initialize media preview settings: {error}");
108 return;
109 }
110 };
111
112 self.update_media_previews_settings(account_data.unwrap_or_default());
113
114 let obj_weak = glib::SendWeakRef::from(self.obj().downgrade());
115 let fut = stream.for_each(move |account_data| {
116 let obj_weak = obj_weak.clone();
117 async move {
118 let ctx = glib::MainContext::default();
119 ctx.spawn(async move {
120 spawn!(async move {
121 if let Some(obj) = obj_weak.upgrade() {
122 obj.imp().update_media_previews_settings(account_data);
123 }
124 });
125 });
126 }
127 });
128
129 let abort_handle = spawn_tokio!(fut).abort_handle();
130 self.abort_handle.replace(Some(abort_handle));
131 }
132
133 fn update_media_previews_settings(&self, account_data: MediaPreviewConfigEventContent) {
135 let media_previews = account_data
136 .media_previews
137 .unwrap_or(DEFAULT_MEDIA_PREVIEWS);
138 let media_previews_enabled_changed =
139 *self.media_previews_enabled.borrow() != media_previews;
140 if media_previews_enabled_changed {
141 *self.media_previews_enabled.borrow_mut() = media_previews;
142 self.obj()
143 .emit_by_name::<()>("media-previews-enabled-changed", &[]);
144 }
145
146 let invite_avatars_enabled = account_data
147 .invite_avatars
148 .map_or(DEFAULT_INVITE_AVATARS_ENABLED, |invite_avatars| {
149 invite_avatars == InviteAvatars::On
150 });
151 let invite_avatars_enabled_changed =
152 self.invite_avatars_enabled.get() != invite_avatars_enabled;
153 if invite_avatars_enabled_changed {
154 self.invite_avatars_enabled.set(invite_avatars_enabled);
155 self.obj().notify_invite_avatars_enabled();
156 }
157 }
158
159 pub(super) async fn apply_migrations(&self) {
161 let session_settings = self.session().settings();
162 let mut stored_settings = session_settings.stored_settings();
163
164 if stored_settings.version != 0 {
165 return;
167 }
168
169 let stored_media_previews_enabled = stored_settings
171 .media_previews_enabled
172 .take()
173 .map_or(DEFAULT_MEDIA_PREVIEWS, |setting| setting.global.into());
174 let _ = self
175 .set_media_previews_enabled(stored_media_previews_enabled)
176 .await;
177
178 let stored_invite_avatars_enabled = stored_settings
179 .invite_avatars_enabled
180 .take()
181 .unwrap_or(DEFAULT_INVITE_AVATARS_ENABLED);
182 let _ = self
183 .set_invite_avatars_enabled(stored_invite_avatars_enabled)
184 .await;
185
186 session_settings.apply_version_1_migration();
187 }
188
189 pub(super) async fn set_media_previews_enabled(
191 &self,
192 setting: MediaPreviews,
193 ) -> Result<(), ()> {
194 if *self.media_previews_enabled.borrow() == setting {
195 return Ok(());
196 }
197
198 let client = self.session().client();
199 let setting_clone = setting.clone();
200 let handle = spawn_tokio!(async move {
201 client
202 .account()
203 .set_media_previews_display_policy(setting_clone)
204 .await
205 });
206
207 if let Err(error) = handle.await.expect("task was not aborted") {
208 error!("Could not change media previews enabled setting: {error}");
209 return Err(());
210 }
211
212 self.media_previews_enabled.replace(setting);
213
214 self.obj()
215 .emit_by_name::<()>("media-previews-enabled-changed", &[]);
216
217 Ok(())
218 }
219
220 pub(super) async fn set_invite_avatars_enabled(&self, enabled: bool) -> Result<(), ()> {
222 if self.invite_avatars_enabled.get() == enabled {
223 return Ok(());
224 }
225
226 let client = self.session().client();
227 let setting = if enabled {
228 InviteAvatars::On
229 } else {
230 InviteAvatars::Off
231 };
232 let handle = spawn_tokio!(async move {
233 client
234 .account()
235 .set_invite_avatars_display_policy(setting)
236 .await
237 });
238
239 if let Err(error) = handle.await.expect("task was not aborted") {
240 error!("Could not change invite avatars enabled setting: {error}");
241 return Err(());
242 }
243
244 self.invite_avatars_enabled.set(enabled);
245 self.obj().notify_invite_avatars_enabled();
246
247 Ok(())
248 }
249 }
250}
251
252glib::wrapper! {
253 pub struct GlobalAccountData(ObjectSubclass<imp::GlobalAccountData>);
255}
256
257impl GlobalAccountData {
258 pub(crate) fn new(session: &Session) -> Self {
260 glib::Object::builder::<Self>()
261 .property("session", session)
262 .build()
263 }
264
265 pub(crate) fn media_previews_enabled(&self) -> MediaPreviews {
267 self.imp().media_previews_enabled.borrow().clone()
268 }
269
270 pub(crate) fn should_room_show_media_previews(&self, room: &Room) -> bool {
272 match &*self.imp().media_previews_enabled.borrow() {
273 MediaPreviews::Off => false,
274 MediaPreviews::Private => matches!(
275 room.join_rule().value(),
276 JoinRuleValue::Invite | JoinRuleValue::RoomMembership
277 ),
278 _ => true,
279 }
280 }
281
282 pub(crate) async fn set_media_previews_enabled(
284 &self,
285 setting: MediaPreviews,
286 ) -> Result<(), ()> {
287 self.imp().set_media_previews_enabled(setting).await
288 }
289
290 pub(crate) async fn set_invite_avatars_enabled(&self, enabled: bool) -> Result<(), ()> {
292 self.imp().set_invite_avatars_enabled(enabled).await
293 }
294
295 pub fn connect_media_previews_enabled_changed<F: Fn(&Self) + 'static>(
297 &self,
298 f: F,
299 ) -> glib::SignalHandlerId {
300 self.connect_closure(
301 "media-previews-enabled-changed",
302 true,
303 closure_local!(move |obj: Self| {
304 f(&obj);
305 }),
306 )
307 }
308}