Browse Source

session-list: Refactor and clean up

pipelines/767384
Kévin Commaille 1 year ago
parent
commit
bf631bc323
No known key found for this signature in database
GPG Key ID: C971D9DBC9D678D
  1. 4
      src/session_list/failed_session.rs
  2. 444
      src/session_list/mod.rs
  3. 2
      src/session_list/new_session.rs
  4. 20
      src/session_list/session_info.rs
  5. 12
      src/session_list/session_list_settings.rs

4
src/session_list/failed_session.rs

@ -29,9 +29,9 @@ mod imp {
pub struct FailedSession {
/// The error encountered when initializing the session.
#[property(get, construct_only)]
pub error: OnceCell<BoxedClientSetupError>,
error: OnceCell<BoxedClientSetupError>,
/// The data for the avatar representation for this session.
pub avatar_data: OnceCell<AvatarData>,
avatar_data: OnceCell<AvatarData>,
}
#[glib::object_subclass]

444
src/session_list/mod.rs

@ -30,19 +30,20 @@ mod imp {
#[derive(Debug, Default, glib::Properties)]
#[properties(wrapper_type = super::SessionList)]
pub struct SessionList {
/// The map of session ID to session.
pub(super) list: RefCell<IndexMap<String, SessionInfo>>,
/// The loading state of the list.
#[property(get, builder(LoadingState::default()))]
pub state: Cell<LoadingState>,
state: Cell<LoadingState>,
/// The error message, if state is set to `LoadingState::Error`.
#[property(get, nullable)]
pub error: RefCell<Option<String>>,
/// A map of session ID to session.
pub list: RefCell<IndexMap<String, SessionInfo>>,
error: RefCell<Option<String>>,
/// The settings of the sessions.
pub settings: SessionListSettings,
#[property(get)]
settings: SessionListSettings,
/// Whether this list is empty.
#[property(get = Self::is_empty)]
pub is_empty: PhantomData<bool>,
is_empty: PhantomData<bool>,
}
#[glib::object_subclass]
@ -68,8 +69,7 @@ mod imp {
self.list
.borrow()
.get_index(position as usize)
.map(|(_, v)| v.upcast_ref::<glib::Object>())
.cloned()
.map(|(_, v)| v.clone().upcast())
}
}
@ -78,6 +78,211 @@ mod imp {
fn is_empty(&self) -> bool {
self.list.borrow().is_empty()
}
/// Set the loading state of this list.
fn set_state(&self, state: LoadingState) {
if self.state.get() == state {
return;
}
self.state.set(state);
self.obj().notify_state();
}
/// Set the error message.
fn set_error(&self, message: String) {
self.error.replace(Some(message));
self.obj().notify_error();
}
/// Insert the given session into the list.
///
/// If a session with the same ID already exists, it is replaced.
///
/// Returns the index of the session.
pub(super) fn insert(&self, session: impl IsA<SessionInfo>) -> usize {
let session = session.upcast();
if let Some(session) = session.downcast_ref::<Session>() {
session.connect_logged_out(clone!(
#[weak(rename_to = imp)]
self,
move |session| imp.remove(session.session_id())
));
}
let was_empty = self.is_empty();
let (index, replaced) = self
.list
.borrow_mut()
.insert_full(session.session_id(), session);
let removed = replaced.is_some().into();
let obj = self.obj();
obj.items_changed(index as u32, removed, 1);
if was_empty {
obj.notify_is_empty();
}
index
}
/// Remove the session with the given ID from the list.
fn remove(&self, session_id: &str) {
let removed = self.list.borrow_mut().shift_remove_full(session_id);
if let Some((position, ..)) = removed {
let obj = self.obj();
obj.items_changed(position as u32, 1, 0);
if self.is_empty() {
obj.notify_is_empty();
}
}
}
/// Restore the logged-in sessions.
pub(super) async fn restore_sessions(&self) {
if self.state.get() >= LoadingState::Loading {
return;
}
self.set_state(LoadingState::Loading);
let handle = spawn_tokio!(secret::restore_sessions());
let mut sessions = match handle.await.expect("task was not aborted") {
Ok(sessions) => sessions,
Err(error) => {
let message = format!(
"{}\n\n{}",
gettext("Could not restore previous sessions"),
error.to_user_facing(),
);
self.set_error(message);
self.set_state(LoadingState::Error);
return;
}
};
self.settings.load();
let session_ids = self.settings.session_ids();
// Keep the order from the settings.
sessions.sort_by(|a, b| {
let pos_a = session_ids.get_index_of(&a.id);
let pos_b = session_ids.get_index_of(&b.id);
match (pos_a, pos_b) {
(Some(pos_a), Some(pos_b)) => pos_a.cmp(&pos_b),
// Keep unknown sessions at the end.
(Some(_), None) => Ordering::Greater,
(None, Some(_)) => Ordering::Less,
_ => Ordering::Equal,
}
});
// Get the directories present in the data path to only restore sessions with
// data on the system. This is necessary for users sharing their secrets between
// devices.
let mut directories = match self.data_directories(sessions.len()).await {
Ok(directories) => directories,
Err(error) => {
error!("Could not access data directory: {error}");
let message = format!(
"{}\n\n{}",
gettext("Could not restore previous sessions"),
gettext("An unexpected error happened while accessing the data directory"),
);
self.set_error(message);
self.set_state(LoadingState::Error);
return;
}
};
for stored_session in sessions {
if let Some(pos) = directories
.iter()
.position(|dir_name| dir_name == stored_session.id.as_str())
{
directories.swap_remove(pos);
info!(
"Restoring previous session {} for user {}",
stored_session.id, stored_session.user_id,
);
self.insert(NewSession::new(&stored_session));
spawn!(
glib::Priority::DEFAULT_IDLE,
clone!(
#[weak(rename_to = obj)]
self,
async move {
obj.restore_stored_session(&stored_session).await;
}
)
);
} else {
info!(
"Ignoring session {} for user {}: no data directory",
stored_session.id, stored_session.user_id,
);
}
}
self.set_state(LoadingState::Ready);
}
/// The list of directories in the data directory.
async fn data_directories(&self, capacity: usize) -> std::io::Result<Vec<OsString>> {
let data_path = data_dir_path(DataType::Persistent);
if !data_path.try_exists()? {
return Ok(Vec::new());
}
spawn_tokio!(async move {
let mut read_dir = tokio::fs::read_dir(data_path).await?;
let mut directories = Vec::with_capacity(capacity);
loop {
let Some(entry) = read_dir.next_entry().await? else {
// We are at the end of the list.
break;
};
if !entry.file_type().await?.is_dir() {
// We are only interested in directories.
continue;
}
directories.push(entry.file_name());
}
std::io::Result::Ok(directories)
})
.await
.expect("task was not aborted")
}
/// Restore a stored session.
async fn restore_stored_session(&self, session_info: &StoredSession) {
let settings = self.settings.get_or_create(&session_info.id);
match Session::restore(session_info.clone(), settings).await {
Ok(session) => {
session.prepare().await;
self.insert(session);
}
Err(error) => {
error!("Could not restore previous session: {error}");
self.insert(FailedSession::new(session_info, error));
}
}
}
}
}
@ -93,38 +298,8 @@ impl SessionList {
glib::Object::new()
}
/// Set the loading state of this list.
fn set_state(&self, state: LoadingState) {
if self.state() == state {
return;
}
self.imp().state.set(state);
self.notify_state();
}
/// Set the error message.
fn set_error(&self, message: String) {
self.imp().error.replace(Some(message));
self.notify_error();
}
/// The settings of the sessions.
pub fn settings(&self) -> &SessionListSettings {
&self.imp().settings
}
/// Whether any of the sessions are new.
pub fn has_new_sessions(&self) -> bool {
self.imp()
.list
.borrow()
.values()
.any(ObjectExt::is::<NewSession>)
}
/// Whether at least one session is ready.
pub fn has_session_ready(&self) -> bool {
pub(crate) fn has_session_ready(&self) -> bool {
self.imp()
.list
.borrow()
@ -134,207 +309,32 @@ impl SessionList {
}
/// The session with the given ID, if any.
pub fn get(&self, session_id: &str) -> Option<SessionInfo> {
pub(crate) fn get(&self, session_id: &str) -> Option<SessionInfo> {
self.imp().list.borrow().get(session_id).cloned()
}
/// The index of the session with the given ID, if any.
pub fn index(&self, session_id: &str) -> Option<usize> {
pub(crate) fn index(&self, session_id: &str) -> Option<usize> {
self.imp().list.borrow().get_index_of(session_id)
}
/// The first session in the list, if any.
pub fn first(&self) -> Option<SessionInfo> {
pub(crate) fn first(&self) -> Option<SessionInfo> {
self.imp().list.borrow().first().map(|(_, v)| v.clone())
}
/// Insert the given session to the list.
/// Insert the given session into the list.
///
/// If a session with the same ID already exists, it is replaced.
///
/// Returns the index of the session.
pub fn insert(&self, session: impl IsA<SessionInfo>) -> usize {
let session = session.upcast();
if let Some(session) = session.downcast_ref::<Session>() {
session.connect_logged_out(clone!(
#[weak(rename_to = obj)]
self,
move |session| obj.remove(session.session_id())
));
}
let was_empty = self.is_empty();
let (index, replaced) = self
.imp()
.list
.borrow_mut()
.insert_full(session.session_id(), session);
let removed = replaced.is_some().into();
self.items_changed(index as u32, removed, 1);
if was_empty {
self.notify_is_empty();
}
index
}
/// Remove the session with the given ID from the list.
pub fn remove(&self, session_id: &str) {
let removed = self.imp().list.borrow_mut().shift_remove_full(session_id);
if let Some((position, ..)) = removed {
self.items_changed(position as u32, 1, 0);
if self.is_empty() {
self.notify_is_empty();
}
}
pub(crate) fn insert(&self, session: impl IsA<SessionInfo>) -> usize {
self.imp().insert(session)
}
/// Restore the logged-in sessions.
pub async fn restore_sessions(&self) {
if self.state() >= LoadingState::Loading {
return;
}
self.set_state(LoadingState::Loading);
let handle = spawn_tokio!(secret::restore_sessions());
let mut sessions = match handle.await.unwrap() {
Ok(sessions) => sessions,
Err(error) => {
let message = format!(
"{}\n\n{}",
gettext("Could not restore previous sessions"),
error.to_user_facing(),
);
self.set_error(message);
self.set_state(LoadingState::Error);
return;
}
};
let settings = self.settings();
settings.load();
let session_ids = settings.session_ids();
// Keep the order from the settings.
sessions.sort_by(|a, b| {
let pos_a = session_ids.get_index_of(&a.id);
let pos_b = session_ids.get_index_of(&b.id);
match (pos_a, pos_b) {
(Some(pos_a), Some(pos_b)) => pos_a.cmp(&pos_b),
// Keep unknown sessions at the end.
(Some(_), None) => Ordering::Greater,
(None, Some(_)) => Ordering::Less,
_ => Ordering::Equal,
}
});
// Get the directories present in the data path to only restore sessions with
// data on the system. This is necessary for users sharing their secrets between
// devices.
let mut directories = match self.data_directories(sessions.len()).await {
Ok(directories) => directories,
Err(error) => {
error!("Could not access data directory: {error}");
let message = format!(
"{}\n\n{}",
gettext("Could not restore previous sessions"),
gettext("An unexpected error happened while accessing the data directory"),
);
self.set_error(message);
self.set_state(LoadingState::Error);
return;
}
};
for stored_session in sessions {
if let Some(pos) = directories
.iter()
.position(|dir_name| dir_name == stored_session.id.as_str())
{
directories.swap_remove(pos);
info!(
"Restoring previous session {} for user {}",
stored_session.id, stored_session.user_id,
);
self.insert(NewSession::new(&stored_session));
spawn!(
glib::Priority::DEFAULT_IDLE,
clone!(
#[weak(rename_to = obj)]
self,
async move {
obj.restore_stored_session(&stored_session).await;
}
)
);
} else {
info!(
"Ignoring session {} for user {}: no data directory",
stored_session.id, stored_session.user_id,
);
}
}
self.set_state(LoadingState::Ready);
}
/// The list of directories in the data directory.
async fn data_directories(&self, capacity: usize) -> std::io::Result<Vec<OsString>> {
let data_path = data_dir_path(DataType::Persistent);
if !data_path.try_exists()? {
return Ok(Vec::new());
}
spawn_tokio!(async move {
let mut read_dir = tokio::fs::read_dir(data_path).await?;
let mut directories = Vec::with_capacity(capacity);
loop {
let Some(entry) = read_dir.next_entry().await? else {
// We are at the end of the list.
break;
};
if !entry.file_type().await?.is_dir() {
// We are only interested in directories.
continue;
}
directories.push(entry.file_name());
}
std::io::Result::Ok(directories)
})
.await
.expect("task was not aborted")
}
/// Restore a stored session.
async fn restore_stored_session(&self, session_info: &StoredSession) {
let settings = self.settings().get_or_create(&session_info.id);
match Session::restore(session_info.clone(), settings).await {
Ok(session) => {
session.prepare().await;
self.insert(session);
}
Err(error) => {
error!("Could not restore previous session: {error}");
self.insert(FailedSession::new(session_info, error));
}
}
pub(crate) async fn restore_sessions(&self) {
self.imp().restore_sessions().await;
}
}

2
src/session_list/new_session.rs

@ -11,7 +11,7 @@ mod imp {
#[derive(Debug, Default)]
pub struct NewSession {
/// The data for the avatar representation for this session.
pub avatar_data: OnceCell<AvatarData>,
avatar_data: OnceCell<AvatarData>,
}
#[glib::object_subclass]

20
src/session_list/session_info.rs

@ -11,8 +11,8 @@ mod imp {
#[repr(C)]
pub struct SessionInfoClass {
pub parent_class: glib::object::ObjectClass,
pub avatar_data: fn(&super::SessionInfo) -> AvatarData,
parent_class: glib::object::ObjectClass,
pub(super) avatar_data: fn(&super::SessionInfo) -> AvatarData,
}
unsafe impl ClassStruct for SessionInfoClass {
@ -29,22 +29,22 @@ mod imp {
pub struct SessionInfo {
/// The Matrix session's info.
#[property(get, construct_only)]
pub info: OnceCell<StoredSession>,
info: OnceCell<StoredSession>,
/// The Matrix session's user ID, as a string.
#[property(get = Self::user_id_string)]
pub user_id_string: PhantomData<String>,
user_id_string: PhantomData<String>,
/// The Matrix session's homeserver, as a string.
#[property(get = Self::homeserver_string)]
pub homeserver_string: PhantomData<String>,
homeserver_string: PhantomData<String>,
/// The Matrix session's device ID, as a string.
#[property(get = Self::device_id_string)]
pub device_id_string: PhantomData<String>,
device_id_string: PhantomData<String>,
/// The local session's ID.
#[property(get = Self::session_id)]
pub session_id: PhantomData<String>,
session_id: PhantomData<String>,
/// The avatar data to represent this session.
#[property(get = Self::avatar_data)]
pub avatar_data: PhantomData<AvatarData>,
avatar_data: PhantomData<AvatarData>,
}
#[glib::object_subclass]
@ -60,8 +60,8 @@ mod imp {
impl SessionInfo {
/// The Matrix session's info.
pub fn info(&self) -> &StoredSession {
self.info.get().unwrap()
pub(super) fn info(&self) -> &StoredSession {
self.info.get().expect("info is initialized")
}
/// The Matrix session's user ID, as a string.

12
src/session_list/session_list_settings.rs

@ -16,7 +16,7 @@ mod imp {
#[derive(Debug, Default)]
pub struct SessionListSettings {
/// The settings of the sessions.
pub sessions: RefCell<IndexMap<String, SessionSettings>>,
pub(super) sessions: RefCell<IndexMap<String, SessionSettings>>,
}
#[glib::object_subclass]
@ -40,7 +40,7 @@ impl SessionListSettings {
}
/// Load these settings from the application settings.
pub fn load(&self) {
pub(crate) fn load(&self) {
let serialized = Application::default().settings().string("sessions");
let stored_sessions =
@ -79,7 +79,7 @@ impl SessionListSettings {
}
/// Save these settings in the application settings.
pub fn save(&self) {
pub(crate) fn save(&self) {
let stored_sessions = self
.imp()
.sessions
@ -97,7 +97,7 @@ impl SessionListSettings {
}
/// Get or create the settings for the session with the given ID.
pub fn get_or_create(&self, session_id: &str) -> SessionSettings {
pub(crate) fn get_or_create(&self, session_id: &str) -> SessionSettings {
let sessions = &self.imp().sessions;
if let Some(session) = sessions.borrow().get(session_id) {
@ -114,13 +114,13 @@ impl SessionListSettings {
}
/// Remove the settings of the session with the given ID.
pub fn remove(&self, session_id: &str) {
pub(crate) fn remove(&self, session_id: &str) {
self.imp().sessions.borrow_mut().shift_remove(session_id);
self.save();
}
/// Get the list of session IDs stored in these settings.
pub fn session_ids(&self) -> IndexSet<String> {
pub(crate) fn session_ids(&self) -> IndexSet<String> {
self.imp().sessions.borrow().keys().cloned().collect()
}
}

Loading…
Cancel
Save