Skip to main content

fractal/session/remote/
user.rs

1use std::time::{Duration, Instant};
2
3use gtk::{glib, glib::clone, prelude::*, subclass::prelude::*};
4use matrix_sdk::ruma::OwnedUserId;
5
6use crate::{
7    components::PillSource,
8    prelude::*,
9    session::{Session, User},
10    spawn,
11    utils::LoadingState,
12};
13
14/// The time after which the profile of a user is assumed to be stale.
15///
16/// This matches 1 hour.
17const PROFILE_VALIDITY_DURATION: Duration = Duration::from_secs(60 * 60);
18
19mod imp {
20    use std::cell::Cell;
21
22    use super::*;
23
24    #[derive(Debug, Default, glib::Properties)]
25    #[properties(wrapper_type = super::RemoteUser)]
26    pub struct RemoteUser {
27        // The loading state of the profile.
28        #[property(get, builder(LoadingState::default()))]
29        loading_state: Cell<LoadingState>,
30        // The time of the last request.
31        last_request_time: Cell<Option<Instant>>,
32    }
33
34    #[glib::object_subclass]
35    impl ObjectSubclass for RemoteUser {
36        const NAME: &'static str = "RemoteUser";
37        type Type = super::RemoteUser;
38        type ParentType = User;
39    }
40
41    #[glib::derived_properties]
42    impl ObjectImpl for RemoteUser {}
43
44    impl PillSourceImpl for RemoteUser {
45        fn identifier(&self) -> String {
46            self.obj().upcast_ref::<User>().user_id_string()
47        }
48    }
49
50    impl RemoteUser {
51        /// Set the loading state.
52        pub(super) fn set_loading_state(&self, loading_state: LoadingState) {
53            if self.loading_state.get() == loading_state {
54                return;
55            }
56
57            self.loading_state.set(loading_state);
58
59            if loading_state == LoadingState::Error {
60                // Reset the request time so we try it again the next time.
61                self.last_request_time.take();
62            }
63
64            self.obj().notify_loading_state();
65        }
66
67        /// Whether the profile of the user is considered to be stale.
68        pub(super) fn is_profile_stale(&self) -> bool {
69            self.last_request_time
70                .get()
71                .is_none_or(|last_time| last_time.elapsed() > PROFILE_VALIDITY_DURATION)
72        }
73
74        /// Update the last request time to now.
75        pub(super) fn update_last_request_time(&self) {
76            self.last_request_time.set(Some(Instant::now()));
77        }
78    }
79}
80
81glib::wrapper! {
82    /// A User that can only be updated by making remote calls, i.e. it won't be updated via sync.
83    pub struct RemoteUser(ObjectSubclass<imp::RemoteUser>) @extends PillSource, User;
84}
85
86impl RemoteUser {
87    pub(super) fn new(session: &Session, user_id: OwnedUserId) -> Self {
88        let obj = glib::Object::builder::<Self>()
89            .property("session", session)
90            .build();
91
92        obj.upcast_ref::<User>().imp().set_user_id(user_id);
93        obj.load_profile_if_stale();
94
95        obj
96    }
97
98    /// Request this user's profile from the homeserver if it is considered to
99    /// be stale.
100    pub(super) fn load_profile_if_stale(&self) {
101        let imp = self.imp();
102
103        if !imp.is_profile_stale() {
104            // The data is still valid, nothing to do.
105            return;
106        }
107
108        // Set the request time right away, to prevent several requests at the same
109        // time.
110        imp.update_last_request_time();
111
112        spawn!(clone!(
113            #[weak(rename_to = obj)]
114            self,
115            async move {
116                let imp = obj.imp();
117                imp.set_loading_state(LoadingState::Loading);
118
119                let loading_state = match obj.load_profile().await {
120                    Ok(()) => LoadingState::Ready,
121                    Err(()) => LoadingState::Error,
122                };
123                imp.set_loading_state(loading_state);
124            }
125        ));
126    }
127}