use futures_channel::mpsc; use gio::prelude::*; use log::warn; use std::cell::RefCell; use std::path::PathBuf; use std::rc::Rc; pub use musicus_client::*; pub use musicus_database::*; pub mod error; pub use error::*; // Override the identically named types from the other crates. pub use error::{Error, Result}; pub mod library; pub use library::*; pub mod player; pub use player::*; mod secure; /// General states the application can be in. pub enum BackendState { /// The backend is not set up yet. This means that no backend methods except for setting the /// music library path should be called. The user interface should adapt and only present this /// option. NoMusicLibrary, /// The backend is loading the music library. No methods should be called. The user interface /// should represent that state by prohibiting all interaction. Loading, /// The backend is ready and all methods may be called. Ready, } /// A collection of all backend state and functionality. pub struct Backend { pub state_stream: RefCell>, state_sender: RefCell>, settings: gio::Settings, music_library_path: RefCell>, database: RefCell>>, player: RefCell>>, client: Client, } impl Backend { /// Create a new backend initerface. The user interface should subscribe to the state stream /// and call init() afterwards. pub fn new() -> Self { let (state_sender, state_stream) = mpsc::channel(1024); Backend { state_stream: RefCell::new(state_stream), state_sender: RefCell::new(state_sender), settings: gio::Settings::new("de.johrpan.musicus"), music_library_path: RefCell::new(None), database: RefCell::new(None), player: RefCell::new(None), client: Client::new(), } } /// Initialize the backend updating the state accordingly. pub async fn init(self: Rc) -> Result<()> { self.init_library().await?; if let Some(url) = self.settings.get_string("server-url") { if !url.is_empty() { self.client.set_server_url(&url); } } match Self::load_login_data().await { Ok(Some(data)) => self.client.set_login_data(data), Err(err) => warn!("The login data could not be loaded from SecretService. It will not \ be available. Error message: {}", err), _ => (), } Ok(()) } /// Set the URL of the Musicus server to connect to. pub fn set_server_url(&self, url: &str) { if let Err(err) = self.settings.set_string("server-url", url) { warn!("An error happened while trying to save the server URL to GSettings. Most \ likely it will not be available at the next startup. Error message: {}", err); } self.client.set_server_url(url); } /// Get the currently set server URL. pub fn get_server_url(&self) -> Option { self.client.get_server_url() } /// Set the user credentials to use. pub async fn set_login_data(&self, data: LoginData) { if let Err(err) = Self::store_login_data(data.clone()).await { warn!("An error happened while trying to store the login data using SecretService. \ This means, that they will not be available at the next startup most likely. \ Error message: {}", err); } self.client.set_login_data(data); } pub fn cl(&self) -> &Client { &self.client } /// Get the currently stored login credentials. pub fn get_login_data(&self) -> Option { self.client.get_login_data() } /// Set the current state and notify the user interface. fn set_state(&self, state: BackendState) { self.state_sender.borrow_mut().try_send(state).unwrap(); } }