[fractal] login: Handle matrix client creation in Login



commit ba430b50ccb9916815435c14d6fbf857cb4d13f1
Author: Kévin Commaille <zecakeh tedomum fr>
Date:   Thu Oct 6 12:56:36 2022 +0200

    login: Handle matrix client creation in Login

 src/login/mod.rs   | 530 ++++++++++++++++++++++++++++++++++++-----------------
 src/session/mod.rs | 292 +++--------------------------
 src/window.rs      |  35 ++--
 3 files changed, 405 insertions(+), 452 deletions(-)
---
diff --git a/src/login/mod.rs b/src/login/mod.rs
index 8b3714223..df64452cc 100644
--- a/src/login/mod.rs
+++ b/src/login/mod.rs
@@ -1,10 +1,18 @@
+use std::{fs, path::PathBuf};
+
 use adw::{prelude::*, subclass::prelude::BinImpl};
 use gettextrs::gettext;
-use gtk::{self, gio, glib, glib::clone, subclass::prelude::*, CompositeTemplate};
-use log::{debug, warn};
+use gtk::{self, gdk, gio, glib, glib::clone, subclass::prelude::*, CompositeTemplate};
+use log::{error, warn};
 use matrix_sdk::{
-    config::RequestConfig, ruma::api::client::session::get_login_types::v3::LoginType, Client,
+    config::{RequestConfig, StoreConfig},
+    ruma::api::client::session::get_login_types::v3::LoginType,
+    store::{MigrationConflictStrategy, OpenStoreError, SledStateStore},
+    Client, ClientBuildError, StoreError,
 };
+use rand::{distributions::Alphanumeric, thread_rng, Rng};
+use ruma::OwnedServerName;
+use thiserror::Error;
 use url::Url;
 
 mod advanced_dialog;
@@ -18,8 +26,11 @@ use self::{
     method_page::LoginMethodPage, sso_page::LoginSsoPage,
 };
 use crate::{
-    components::SpinnerButton, spawn, spawn_tokio, toast, user_facing_error::UserFacingError,
-    Session,
+    components::SpinnerButton,
+    secret::{self, Secret, StoredSession},
+    spawn, spawn_tokio, toast,
+    user_facing_error::UserFacingError,
+    Session, Window,
 };
 
 mod imp {
@@ -36,7 +47,8 @@ mod imp {
     #[derive(Debug, Default, CompositeTemplate)]
     #[template(resource = "/org/gnome/Fractal/login.ui")]
     pub struct Login {
-        pub current_session: RefCell<Option<Session>>,
+        /// The current created Matrix client and its configuration.
+        pub created_client: RefCell<Option<CreatedClient>>,
         #[template_child]
         pub back_button: TemplateChild<gtk::Button>,
         #[template_child]
@@ -81,7 +93,9 @@ mod imp {
             klass.install_action("login.prev", None, move |widget, _, _| widget.go_previous());
             klass.install_action("login.sso", Some("ms"), move |widget, _, variant| {
                 let idp_id = variant.and_then(|v| v.get::<Option<String>>()).flatten();
-                widget.login_with_sso(idp_id);
+                spawn!(clone!(@weak widget => async move {
+                    widget.login_with_sso(idp_id).await;
+                }));
             });
             klass.install_action("login.open-advanced", None, move |widget, _, _| {
                 spawn!(clone!(@weak widget => async move {
@@ -172,6 +186,10 @@ mod imp {
 
             obj.update_network_state();
         }
+
+        fn dispose(&self, obj: &Self::Type) {
+            obj.prune_created_client(true);
+        }
     }
 
     impl WidgetImpl for Login {}
@@ -190,6 +208,45 @@ impl Login {
         glib::Object::new(&[]).expect("Failed to create Login")
     }
 
+    fn parent_window(&self) -> Window {
+        self.root()
+            .and_then(|root| root.downcast().ok())
+            .expect("Login needs to have a parent window")
+    }
+
+    pub fn created_client(&self) -> Option<CreatedClient> {
+        self.imp().created_client.borrow().clone()
+    }
+
+    pub fn client(&self) -> Option<Client> {
+        self.imp()
+            .created_client
+            .borrow()
+            .as_ref()
+            .map(|c| c.client.clone())
+    }
+
+    pub fn prune_created_client(&self, clean: bool) {
+        if let Some(created_client) = self.imp().created_client.take() {
+            if clean {
+                if let Err(error) = fs::remove_dir_all(created_client.path) {
+                    error!("Failed to remove newly-created database: {}", error);
+                }
+            }
+        }
+    }
+
+    async fn set_created_client(&self, created_client: Option<CreatedClient>) {
+        let homeserver = if let Some(c) = &created_client {
+            Some(c.client.homeserver().await)
+        } else {
+            None
+        };
+
+        self.set_homeserver(homeserver);
+        self.imp().created_client.replace(created_client);
+    }
+
     pub fn homeserver(&self) -> Option<Url> {
         self.imp().homeserver.borrow().clone()
     }
@@ -203,7 +260,7 @@ impl Login {
     }
 
     pub fn set_homeserver(&self, homeserver: Option<Url>) {
-        let priv_ = imp::Login::from_instance(self);
+        let priv_ = self.imp();
 
         if self.homeserver() == homeserver {
             return;
@@ -228,17 +285,20 @@ impl Login {
     }
 
     fn visible_child(&self) -> String {
-        let priv_ = imp::Login::from_instance(self);
-        priv_.main_stack.visible_child_name().unwrap().into()
+        self.imp().main_stack.visible_child_name().unwrap().into()
     }
 
     fn set_visible_child(&self, visible_child: &str) {
-        let priv_ = imp::Login::from_instance(self);
-        priv_.main_stack.set_visible_child_name(visible_child);
+        // Clean up the created client when we come back to the homeserver selection.
+        if visible_child == "homeserver" {
+            self.prune_created_client(true);
+        }
+
+        self.imp().main_stack.set_visible_child_name(visible_child);
     }
 
     fn update_next_state(&self) {
-        let priv_ = imp::Login::from_instance(self);
+        let priv_ = self.imp();
         match self.visible_child().as_ref() {
             "homeserver" => {
                 self.enable_next_action(priv_.homeserver_page.can_go_next());
@@ -262,22 +322,28 @@ impl Login {
     }
 
     fn go_next(&self) {
-        match self.visible_child().as_ref() {
-            "homeserver" => {
-                if self.autodiscovery() {
-                    self.try_autodiscovery();
-                } else {
-                    self.check_homeserver();
+        self.freeze();
+
+        spawn!(
+            glib::PRIORITY_DEFAULT_IDLE,
+            clone!(@weak self as obj => async move {
+                match obj.visible_child().as_ref() {
+                    "homeserver" => obj.get_homeserver().await,
+                    "method" => obj.login_with_password().await,
+                    _ => {}
                 }
-            }
-            "method" => self.login_with_password(),
-            _ => {}
-        }
+
+                obj.unfreeze();
+            })
+        );
     }
 
     fn go_previous(&self) {
         match self.visible_child().as_ref() {
-            "method" => self.set_visible_child("homeserver"),
+            "method" => {
+                self.set_visible_child("homeserver");
+                self.imp().method_page.clean();
+            }
             "sso" => {
                 self.set_visible_child(if self.imp().supports_password.get() {
                     "method"
@@ -292,74 +358,48 @@ impl Login {
     }
 
     async fn open_advanced_dialog(&self) {
-        let dialog =
-            LoginAdvancedDialog::new(self.root().unwrap().downcast_ref::<gtk::Window>().unwrap());
+        let dialog = LoginAdvancedDialog::new(self.parent_window().upcast_ref());
         self.bind_property("autodiscovery", &dialog, "autodiscovery")
             .flags(glib::BindingFlags::SYNC_CREATE | glib::BindingFlags::BIDIRECTIONAL)
             .build();
         dialog.run_future().await;
     }
 
-    fn try_autodiscovery(&self) {
-        let server = self.imp().homeserver_page.server_name().unwrap();
-
-        self.freeze();
-
-        let handle =
-            spawn_tokio!(async move { Client::builder().server_name(&server).build().await });
-
-        spawn!(
-            glib::PRIORITY_DEFAULT_IDLE,
-            clone!(@weak self as obj => async move {
-                match handle.await.unwrap() {
-                    Ok(client) => {
-                        let homeserver = client.homeserver().await;
-                        obj.set_homeserver(Some(homeserver));
-                        obj.check_login_types(client).await;
-                    }
-                    Err(error) => {
-                        warn!("Failed to discover homeserver: {}", error);
-                        toast!(obj, error.to_user_facing());
-                    }
-                };
-                obj.unfreeze();
-            })
-        );
-    }
-
-    fn check_homeserver(&self) {
-        let homeserver = self.imp().homeserver_page.homeserver_url().unwrap();
-        let homeserver_clone = homeserver.clone();
+    async fn get_homeserver(&self) {
+        let autodiscovery = self.autodiscovery();
+        let homeserver_page = &self.imp().homeserver_page;
 
-        self.freeze();
+        let homeserver = if autodiscovery {
+            HomeserverOrServerName::ServerName(homeserver_page.server_name().unwrap())
+        } else {
+            HomeserverOrServerName::Homeserver(homeserver_page.homeserver_url().unwrap())
+        };
 
         let handle = spawn_tokio!(async move {
-            Client::builder()
-                .homeserver_url(homeserver_clone)
-                .request_config(RequestConfig::new().disable_retry())
-                .build()
-                .await
+            CreatedClient::new(&homeserver, autodiscovery, None, None).await
         });
 
-        spawn!(
-            glib::PRIORITY_DEFAULT_IDLE,
-            clone!(@weak self as obj => async move {
-                match handle.await.unwrap() {
-                    Ok(client) => {
-                        obj.set_homeserver(Some(homeserver));
-                        obj.check_login_types(client).await;
-                    }
-                    Err(error) => {
-                        warn!("Failed to check homeserver: {}", error);
-                        toast!(obj, error.to_user_facing());
-                    }
-                };
-                obj.unfreeze();
-            })
-        );
+        match handle.await.unwrap() {
+            Ok(created_client) => {
+                self.set_created_client(Some(created_client)).await;
+                self.check_login_types().await;
+            }
+            Err(error) => {
+                if autodiscovery {
+                    warn!("Failed to discover homeserver: {}", error);
+                } else {
+                    warn!("Failed to check homeserver: {}", error);
+                }
+                toast!(self, error.to_user_facing());
+
+                // Clean up the created client because it's bound to the homeserver.
+                self.prune_created_client(true);
+            }
+        };
     }
 
-    async fn check_login_types(&self, client: Client) {
+    async fn check_login_types(&self) {
+        let client = self.client().unwrap();
         let handle = spawn_tokio!(async move { client.get_login_types().await });
 
         let login_types = match handle.await.unwrap() {
@@ -384,13 +424,14 @@ impl Login {
             .iter()
             .any(|flow| matches!(flow, LoginType::Password(_)));
 
-        self.imp().supports_password.replace(has_password);
+        let priv_ = self.imp();
+        priv_.supports_password.replace(has_password);
 
         if has_password {
-            self.imp().method_page.update_sso(sso);
+            priv_.method_page.update_sso(sso);
             self.show_login_methods();
         } else {
-            self.login_with_sso(None);
+            self.login_with_sso(None).await;
         }
     }
 
@@ -407,53 +448,163 @@ impl Login {
         self.set_visible_child("method");
     }
 
-    fn login_with_password(&self) {
+    async fn login_with_password(&self) {
         let priv_ = self.imp();
-        let homeserver = self.homeserver().unwrap();
         let username = priv_.method_page.username();
         let password = priv_.method_page.password();
-        let autodiscovery = self.autodiscovery();
-
-        self.freeze();
+        let CreatedClient {
+            client,
+            path,
+            passphrase,
+        } = self.created_client().unwrap();
 
-        let session = Session::new();
-        self.set_handler_for_prepared_session(&session);
+        let handle = spawn_tokio!(async move {
+            client
+                .login_username(&username, &password)
+                .initial_device_display_name("Fractal")
+                .send()
+                .await
+        });
 
-        spawn!(
-            glib::PRIORITY_DEFAULT_IDLE,
-            clone!(@weak session => async move {
-                session.login_with_password(homeserver, username, password, autodiscovery).await;
-            })
-        );
-        priv_.current_session.replace(Some(session));
+        match handle.await.unwrap() {
+            Ok(response) => {
+                let session_info = StoredSession {
+                    homeserver: self.homeserver().unwrap(),
+                    user_id: response.user_id,
+                    device_id: response.device_id,
+                    path,
+                    secret: Secret {
+                        access_token: response.access_token,
+                        passphrase,
+                    },
+                };
+                self.create_session(session_info, true).await;
+            }
+            Err(error) => {
+                warn!("Failed to log in: {error}");
+                toast!(self, error.to_user_facing());
+            }
+        }
     }
 
-    fn login_with_sso(&self, idp_id: Option<String>) {
-        let priv_ = imp::Login::from_instance(self);
-        let homeserver = self.homeserver().unwrap();
+    async fn login_with_sso(&self, idp_id: Option<String>) {
+        let CreatedClient {
+            client,
+            path,
+            passphrase,
+        } = self.created_client().unwrap();
+
         self.set_visible_child("sso");
 
+        let handle = spawn_tokio!(async move {
+            let mut login = client
+                .login_sso(|sso_url| async move {
+                    let ctx = glib::MainContext::default();
+                    ctx.spawn(async move {
+                        gtk::show_uri(gtk::Window::NONE, &sso_url, gdk::CURRENT_TIME);
+                    });
+                    Ok(())
+                })
+                .initial_device_display_name("Fractal");
+
+            if let Some(idp_id) = idp_id.as_deref() {
+                login = login.identity_provider_id(idp_id);
+            }
+
+            login.send().await
+        });
+
+        match handle.await.unwrap() {
+            Ok(response) => {
+                let session_info = StoredSession {
+                    homeserver: self.homeserver().unwrap(),
+                    user_id: response.user_id,
+                    device_id: response.device_id,
+                    path,
+                    secret: Secret {
+                        access_token: response.access_token,
+                        passphrase,
+                    },
+                };
+                self.create_session(session_info, true).await;
+            }
+            Err(error) => {
+                warn!("Failed to log in: {error}");
+                toast!(self, error.to_user_facing());
+                self.go_previous();
+            }
+        }
+    }
+
+    pub async fn restore_previous_session(&self, session: StoredSession) {
+        let handle = spawn_tokio!(async move {
+            let created_client = CreatedClient::new(
+                &HomeserverOrServerName::Homeserver(session.homeserver.clone()),
+                false,
+                Some(session.path.clone()),
+                Some(session.secret.passphrase.clone()),
+            )
+            .await?;
+
+            created_client
+                .client
+                .restore_login(matrix_sdk::Session {
+                    user_id: session.user_id.clone(),
+                    device_id: session.device_id.clone(),
+                    access_token: session.secret.access_token.clone(),
+                    refresh_token: None,
+                })
+                .await
+                .map(|_| (created_client, session))
+                .map_err(ClientSetupError::from)
+        });
+
+        match handle.await.unwrap() {
+            Ok((created_client, session_info)) => {
+                self.set_created_client(Some(created_client)).await;
+                self.create_session(session_info, false).await;
+            }
+            Err(error) => {
+                warn!("Failed to restore previous login: {error}");
+                toast!(self, error.to_user_facing());
+            }
+        }
+    }
+
+    pub async fn create_session(&self, session_info: StoredSession, is_new: bool) {
+        let client = self.client().unwrap();
         let session = Session::new();
-        self.set_handler_for_prepared_session(&session);
-        spawn!(
-            glib::PRIORITY_DEFAULT_IDLE,
-            clone!(@weak session, @weak self as s => async move {
-                session.login_with_sso(homeserver, idp_id).await;
-                s.set_visible_child("homeserver");
-            })
-        );
-        priv_.current_session.replace(Some(session));
+
+        if is_new {
+            if let Err(error) = secret::store_session(&session_info).await {
+                error!("Couldn't store session: {:?}", error);
+
+                self.parent_window().switch_to_error_page(
+                    &format!("{}\n\n{}", gettext("Unable to store session"), error),
+                    error,
+                );
+                return;
+            }
+        };
+
+        session.prepare(client, session_info).await;
+        self.emit_by_name::<()>("new-session", &[&session]);
     }
 
     pub fn clean(&self) {
         let priv_ = self.imp();
+
+        // Clean pages.
         priv_.homeserver_page.clean();
         priv_.method_page.clean();
+
+        // Clean data.
+        self.prune_created_client(false);
         self.set_autodiscovery(true);
-        priv_.homeserver.take();
+
+        // Reinitialize UI.
         priv_.main_stack.set_visible_child_name("homeserver");
         self.unfreeze();
-        self.drop_session_reference();
     }
 
     fn freeze(&self) {
@@ -486,22 +637,6 @@ impl Login {
         })
     }
 
-    fn drop_session_reference(&self) {
-        let priv_ = self.imp();
-
-        if let Some(session) = priv_.current_session.take() {
-            if let Some(id) = priv_.prepared_source_id.take() {
-                session.disconnect(id);
-            }
-            if let Some(id) = priv_.logged_out_source_id.take() {
-                session.disconnect(id);
-            }
-            if let Some(id) = priv_.ready_source_id.take() {
-                session.disconnect(id);
-            }
-        }
-    }
-
     pub fn default_widget(&self) -> gtk::Widget {
         self.imp().next_button.get().upcast()
     }
@@ -520,48 +655,6 @@ impl Login {
         }
     }
 
-    fn set_handler_for_prepared_session(&self, session: &Session) {
-        let priv_ = self.imp();
-        priv_
-            .prepared_source_id
-            .replace(Some(session.connect_prepared(
-                clone!(@weak self as login => move |session, error| {
-                    match error {
-                        Some(e) => {
-                            toast!(login, e);
-                            login.unfreeze();
-                        },
-                        None => {
-                            debug!("A new session was prepared");
-                            login.emit_by_name::<()>("new-session", &[&session]);
-                        }
-                    }
-                }),
-            )));
-
-        priv_.ready_source_id.replace(Some(session.connect_ready(
-            clone!(@weak self as login => move |_| {
-                login.clean();
-            }),
-        )));
-
-        priv_
-            .logged_out_source_id
-            .replace(Some(session.connect_logged_out(
-                clone!(@weak self as login => move |_| {
-                    login.parent_window().switch_to_login_page();
-                    login.drop_session_reference();
-                    login.unfreeze();
-                }),
-            )));
-    }
-
-    fn parent_window(&self) -> crate::Window {
-        self.root()
-            .and_then(|root| root.downcast().ok())
-            .expect("Login needs to have a parent window")
-    }
-
     fn update_network_state(&self) {
         let priv_ = self.imp();
         let monitor = gio::NetworkMonitor::default();
@@ -592,3 +685,104 @@ impl Default for Login {
         Self::new()
     }
 }
+
+/// A homeserver URL or a server name.
+pub enum HomeserverOrServerName {
+    /// A homeserver URL.
+    Homeserver(Url),
+
+    /// A server name.
+    ServerName(OwnedServerName),
+}
+
+/// All errors that can occur when setting up the Matrix client.
+#[derive(Error, Debug)]
+pub enum ClientSetupError {
+    #[error(transparent)]
+    Store(#[from] OpenStoreError),
+    #[error(transparent)]
+    Client(#[from] ClientBuildError),
+    #[error(transparent)]
+    Sdk(#[from] matrix_sdk::Error),
+}
+
+impl UserFacingError for ClientSetupError {
+    fn to_user_facing(self) -> String {
+        match self {
+            ClientSetupError::Store(err) => err.to_user_facing(),
+            ClientSetupError::Client(err) => err.to_user_facing(),
+            ClientSetupError::Sdk(err) => err.to_user_facing(),
+        }
+    }
+}
+
+/// A newly created Matrix client and its configuration.
+#[derive(Debug, Clone)]
+pub struct CreatedClient {
+    /// The Matrix client.
+    pub client: Client,
+
+    /// The path where the store is located.
+    pub path: PathBuf,
+
+    /// The passphrase to decrypt the store.
+    pub passphrase: String,
+}
+
+impl CreatedClient {
+    /// Create a Matrix `Client` for the given homeserver.
+    async fn new(
+        homeserver: &HomeserverOrServerName,
+        use_discovery: bool,
+        path: Option<PathBuf>,
+        passphrase: Option<String>,
+    ) -> Result<CreatedClient, ClientSetupError> {
+        let path = path.unwrap_or_else(|| {
+            let mut path = glib::user_data_dir();
+            path.push(glib::uuid_string_random().as_str());
+            path
+        });
+
+        let passphrase = passphrase.unwrap_or_else(|| {
+            thread_rng()
+                .sample_iter(Alphanumeric)
+                .take(30)
+                .map(char::from)
+                .collect()
+        });
+
+        let state_store = SledStateStore::builder()
+            .path(path.clone())
+            .passphrase(passphrase.clone())
+            .migration_conflict_strategy(MigrationConflictStrategy::Drop)
+            .build()
+            .map_err(|err| OpenStoreError::from(StoreError::backend(err)))?;
+        let crypto_store = state_store.open_crypto_store()?;
+        let store_config = StoreConfig::new()
+            .state_store(state_store)
+            .crypto_store(crypto_store);
+
+        let builder = match homeserver {
+            HomeserverOrServerName::Homeserver(url) => Client::builder().homeserver_url(url),
+            HomeserverOrServerName::ServerName(server_name) => {
+                Client::builder().server_name(server_name)
+            }
+        };
+
+        let client = builder
+            .store_config(store_config)
+            // force_auth option to solve an issue with some servers configuration to require
+            // auth for profiles:
+            // https://gitlab.gnome.org/GNOME/fractal/-/issues/934
+            .request_config(RequestConfig::new().retry_limit(2).force_auth())
+            .respect_login_well_known(use_discovery)
+            .build()
+            .await?;
+
+        Ok(Self {
+            client,
+            path,
+            passphrase,
+        })
+    }
+}
diff --git a/src/session/mod.rs b/src/session/mod.rs
index 780469688..b37be46e8 100644
--- a/src/session/mod.rs
+++ b/src/session/mod.rs
@@ -11,7 +11,7 @@ mod sidebar;
 mod user;
 pub mod verification;
 
-use std::{collections::HashSet, fs, path::PathBuf, time::Duration};
+use std::{collections::HashSet, fs, time::Duration};
 
 use adw::{prelude::*, subclass::prelude::*};
 use futures::StreamExt;
@@ -23,7 +23,7 @@ use gtk::{
 };
 use log::{debug, error, warn};
 use matrix_sdk::{
-    config::{RequestConfig, StoreConfig, SyncSettings},
+    config::SyncSettings,
     deserialized_responses::SyncResponse,
     room::Room as MatrixRoom,
     ruma::{
@@ -43,13 +43,9 @@ use matrix_sdk::{
         matrix_uri::MatrixId,
         MatrixUri, OwnedRoomOrAliasId, OwnedServerName, RoomId, RoomOrAliasId,
     },
-    store::{MigrationConflictStrategy, OpenStoreError, SledStateStore},
-    Client, ClientBuildError, Error, HttpError, RumaApiError, StoreError,
+    Client, HttpError, RumaApiError,
 };
-use rand::{distributions::Alphanumeric, thread_rng, Rng};
-use thiserror::Error;
 use tokio::task::JoinHandle;
-use url::Url;
 
 use self::{
     account_settings::AccountSettings,
@@ -67,34 +63,13 @@ pub use self::{
     user::{User, UserActions, UserExt},
 };
 use crate::{
-    secret,
-    secret::{Secret, StoredSession},
+    secret::{self, StoredSession},
     session::sidebar::ItemList,
     spawn, spawn_tokio, toast,
     utils::{check_if_reachable, parse_matrix_to_uri},
-    UserFacingError, Window,
+    Window,
 };
 
-#[derive(Error, Debug)]
-pub enum ClientSetupError {
-    #[error(transparent)]
-    Store(#[from] OpenStoreError),
-    #[error(transparent)]
-    Client(#[from] ClientBuildError),
-    #[error(transparent)]
-    Sdk(#[from] Error),
-}
-
-impl UserFacingError for ClientSetupError {
-    fn to_user_facing(self) -> String {
-        match self {
-            ClientSetupError::Store(err) => err.to_user_facing(),
-            ClientSetupError::Client(err) => err.to_user_facing(),
-            ClientSetupError::Sdk(err) => err.to_user_facing(),
-        }
-    }
-}
-
 mod imp {
     use std::cell::{Cell, RefCell};
 
@@ -116,7 +91,7 @@ mod imp {
         pub content: TemplateChild<Content>,
         #[template_child]
         pub media_viewer: TemplateChild<MediaViewer>,
-        pub client: RefCell<Option<Client>>,
+        pub client: OnceCell<Client>,
         pub item_list: OnceCell<ItemList>,
         pub user: OnceCell<User>,
         pub is_ready: Cell<bool>,
@@ -252,12 +227,6 @@ mod imp {
         fn signals() -> &'static [Signal] {
             static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
                 vec![
-                    Signal::builder(
-                        "prepared",
-                        &[Option::<String>::static_type().into()],
-                        <()>::static_type().into(),
-                    )
-                    .build(),
                     Signal::builder("ready", &[], <()>::static_type().into()).build(),
                     Signal::builder("logged-out", &[], <()>::static_type().into()).build(),
                 ]
@@ -347,198 +316,33 @@ impl Session {
         room_search.set_search_mode(!room_search.is_search_mode());
     }
 
-    pub async fn login_with_password(
-        &self,
-        homeserver: Url,
-        username: String,
-        password: String,
-        use_discovery: bool,
-    ) {
-        self.imp().logout_on_dispose.set(true);
-
-        let mut path = glib::user_data_dir();
-        path.push(glib::uuid_string_random().as_str());
-
-        let passphrase: String = {
-            let mut rng = thread_rng();
-            (&mut rng)
-                .sample_iter(Alphanumeric)
-                .take(30)
-                .map(char::from)
-                .collect()
-        };
-
-        let handle = spawn_tokio!(async move {
-            let client =
-                create_client(&homeserver, path.clone(), passphrase.clone(), use_discovery).await?;
-
-            let response = client
-                .login_username(&username, &password)
-                .initial_device_display_name("Fractal")
-                .send()
-                .await;
-            match response {
-                Ok(response) => Ok((
-                    client,
-                    StoredSession {
-                        homeserver,
-                        path,
-                        user_id: response.user_id,
-                        device_id: response.device_id,
-                        secret: Secret {
-                            passphrase,
-                            access_token: response.access_token,
-                        },
-                    },
-                )),
-                Err(error) => {
-                    // Remove the store created by Client::new()
-                    fs::remove_dir_all(path).unwrap();
-                    Err(error.into())
-                }
-            }
-        });
-
-        self.handle_login_result(handle.await.unwrap(), true).await;
-    }
-
-    pub async fn login_with_sso(&self, homeserver: Url, idp_id: Option<String>) {
-        self.imp().logout_on_dispose.set(true);
-        let mut path = glib::user_data_dir();
-        path.push(glib::uuid_string_random().as_str());
-        let passphrase: String = {
-            let mut rng = thread_rng();
-            (&mut rng)
-                .sample_iter(Alphanumeric)
-                .take(30)
-                .map(char::from)
-                .collect()
-        };
-        let handle = spawn_tokio!(async move {
-            let client = create_client(&homeserver, path.clone(), passphrase.clone(), true).await?;
-
-            let mut login = client
-                .login_sso(|sso_url| async move {
-                    let ctx = glib::MainContext::default();
-                    ctx.spawn(async move {
-                        gtk::show_uri(gtk::Window::NONE, &sso_url, gdk::CURRENT_TIME);
-                    });
-                    Ok(())
-                })
-                .initial_device_display_name("Fractal");
-
-            if let Some(idp_id) = idp_id.as_deref() {
-                login = login.identity_provider_id(idp_id);
-            }
-
-            let response = login.send().await;
-            match response {
-                Ok(response) => Ok((
-                    client,
-                    StoredSession {
-                        homeserver,
-                        path,
-                        user_id: response.user_id,
-                        device_id: response.device_id,
-                        secret: Secret {
-                            passphrase,
-                            access_token: response.access_token,
-                        },
-                    },
-                )),
-                Err(error) => {
-                    // Remove the store created by Client::new()
-                    fs::remove_dir_all(path).unwrap();
-                    Err(error.into())
-                }
-            }
-        });
-
-        self.handle_login_result(handle.await.unwrap(), true).await;
-    }
-
-    pub async fn login_with_previous_session(&self, session: StoredSession) {
-        let handle = spawn_tokio!(async move {
-            let client = create_client(
-                &session.homeserver,
-                session.path.clone(),
-                session.secret.passphrase.clone(),
-                false,
-            )
-            .await?;
-
-            client
-                .restore_login(matrix_sdk::Session {
-                    user_id: session.user_id.clone(),
-                    device_id: session.device_id.clone(),
-                    access_token: session.secret.access_token.clone(),
-                    refresh_token: None,
-                })
-                .await
-                .map(|_| (client, session))
-                .map_err(Into::into)
-        });
-
-        self.handle_login_result(handle.await.unwrap(), false).await;
-    }
-
-    async fn handle_login_result(
-        &self,
-        result: Result<(Client, StoredSession), ClientSetupError>,
-        store_session: bool,
-    ) {
+    pub async fn prepare(&self, client: Client, session: StoredSession) {
         let priv_ = self.imp();
-        let error = match result {
-            Ok((client, session)) => {
-                priv_.client.replace(Some(client));
-                let user = User::new(self, &session.user_id);
-                priv_.user.set(user).unwrap();
-                self.notify("user");
-
-                self.update_user_profile();
-
-                if store_session {
-                    if let Err(error) = secret::store_session(&session).await {
-                        warn!("Couldn't store session: {:?}", error);
-                        if let Some(window) = self.parent_window() {
-                            window.switch_to_error_page(
-                                &format!("{}\n\n{}", gettext("Unable to store session"), error),
-                                error,
-                            );
-                        }
-                        self.logout(false).await;
-                        fs::remove_dir_all(session.path).unwrap();
-                        return;
-                    }
-                };
 
-                priv_
-                    .settings
-                    .set(SessionSettings::new(session.id()))
-                    .unwrap();
+        priv_.client.set(client).unwrap();
 
-                priv_.info.set(session).unwrap();
-                self.update_offline().await;
+        let user = User::new(self, &session.user_id);
+        priv_.user.set(user).unwrap();
+        self.notify("user");
 
-                self.room_list().load();
-                self.setup_direct_room_handler();
-                self.setup_room_encrypted_changes();
+        self.update_user_profile();
 
-                self.set_is_prepared(true);
-                self.sync();
+        priv_
+            .settings
+            .set(SessionSettings::new(session.id()))
+            .unwrap();
 
-                None
-            }
-            Err(error) => {
-                error!("Failed to prepare the session: {:?}", error);
+        priv_.info.set(session).unwrap();
+        self.update_offline().await;
 
-                priv_.logout_on_dispose.set(false);
+        self.room_list().load();
+        self.setup_direct_room_handler();
+        self.setup_room_encrypted_changes();
 
-                Some(error.to_user_facing())
-            }
-        };
+        self.set_is_prepared(true);
+        self.sync();
 
-        self.emit_by_name::<()>("prepared", &[&error]);
+        debug!("A new session was prepared");
     }
 
     fn sync(&self) {
@@ -703,9 +507,9 @@ impl Session {
     pub fn client(&self) -> Client {
         self.imp()
             .client
-            .borrow()
+            .get()
+            .expect("The session wasn't prepared")
             .clone()
-            .expect("The session isn't ready")
     }
 
     pub fn is_offline(&self) -> bool {
@@ -748,21 +552,6 @@ impl Session {
         self.notify("offline");
     }
 
-    /// Connects the prepared signals to the function f given in input
-    pub fn connect_prepared<F: Fn(&Self, Option<String>) + 'static>(
-        &self,
-        f: F,
-    ) -> glib::SignalHandlerId {
-        self.connect_local("prepared", true, move |values| {
-            let obj = values[0].get::<Self>().unwrap();
-            let err = values[1].get::<Option<String>>().unwrap();
-
-            f(&obj, err);
-
-            None
-        })
-    }
-
     pub fn connect_logged_out<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
         self.connect_local("logged-out", true, move |values| {
             let obj = values[0].get::<Self>().unwrap();
@@ -1036,35 +825,6 @@ impl Default for Session {
     }
 }
 
-async fn create_client(
-    homeserver: &Url,
-    path: PathBuf,
-    passphrase: String,
-    use_discovery: bool,
-) -> Result<Client, ClientSetupError> {
-    let state_store = SledStateStore::builder()
-        .path(path)
-        .passphrase(passphrase)
-        .migration_conflict_strategy(MigrationConflictStrategy::Drop)
-        .build()
-        .map_err(|err| OpenStoreError::from(StoreError::backend(err)))?;
-    let crypto_store = state_store.open_crypto_store()?;
-    let store_config = StoreConfig::new()
-        .state_store(state_store)
-        .crypto_store(crypto_store);
-    Client::builder()
-        .homeserver_url(homeserver)
-        .store_config(store_config)
-        // force_auth option to solve an issue with some servers configuration to require
-        // auth for profiles:
-        // https://gitlab.gnome.org/GNOME/fractal/-/issues/934
-        .request_config(RequestConfig::new().retry_limit(2).force_auth())
-        .respect_login_well_known(use_discovery)
-        .build()
-        .await
-        .map_err(Into::into)
-}
-
 fn parse_room(room: &str) -> Option<(OwnedRoomOrAliasId, Vec<OwnedServerName>)> {
     MatrixUri::parse(room)
         .ok()
diff --git a/src/window.rs b/src/window.rs
index 0388aac44..62485ae34 100644
--- a/src/window.rs
+++ b/src/window.rs
@@ -126,6 +126,18 @@ mod imp {
             }));
             obj.add_action(&fullscreen);
 
+            self.login
+                .connect_new_session(clone!(@weak obj => move |_login, session| {
+                    obj.add_session(&session);
+                    obj.switch_to_loading_page();
+                }));
+
+            self.main_stack.connect_visible_child_notify(
+                clone!(@weak obj => move |_| obj.set_default_by_child()),
+            );
+
+            obj.set_default_by_child();
+
             spawn!(clone!(@weak obj => async move {
                 obj.restore_sessions().await;
             }));
@@ -176,6 +188,9 @@ impl Window {
         // We need to grab the focus so that keyboard shortcuts work
         session.grab_focus();
 
+        session.connect_ready(clone!(@weak self as obj => move |_| {
+            obj.imp().login.clean();
+        }));
         session.connect_logged_out(clone!(@weak self as obj => move |session| {
             obj.remove_session(session)
         }));
@@ -213,30 +228,14 @@ impl Window {
                             info!("Database path: {path}");
                         }
 
-                        let session = Session::new();
                         spawn!(
                             glib::PRIORITY_DEFAULT_IDLE,
-                            clone!(@weak session => async move {
-                                session.login_with_previous_session(stored_session).await;
+                            clone!(@weak self as obj => async move {
+                                obj.imp().login.restore_previous_session(stored_session).await;
                             })
                         );
-                        self.add_session(&session);
                     }
                 }
-
-                let priv_ = self.imp();
-                priv_.login.connect_new_session(
-                    clone!(@weak self as obj => move |_login, session| {
-                        obj.add_session(&session);
-                        obj.switch_to_loading_page();
-                    }),
-                );
-
-                priv_.main_stack.connect_visible_child_notify(
-                    clone!(@weak self as obj => move |_| obj.set_default_by_child()),
-                );
-
-                self.set_default_by_child();
             }
             Err(error) => {
                 warn!("Failed to restore previous sessions: {:?}", error);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]