mirror of
				https://github.com/johrpan/musicus.git
				synced 2025-10-26 11:47:25 +01:00 
			
		
		
		
	database: Remove wrapper thread
This commit is contained in:
		
							parent
							
								
									678367ec1a
								
							
						
					
					
						commit
						42d1d047e3
					
				
					 31 changed files with 267 additions and 826 deletions
				
			
		
							
								
								
									
										1
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							|  | @ -1021,7 +1021,6 @@ dependencies = [ | ||||||
|  "log", |  "log", | ||||||
|  "rand", |  "rand", | ||||||
|  "thiserror", |  "thiserror", | ||||||
|  "tokio", |  | ||||||
|  "uuid", |  "uuid", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| use musicus_database::DbThread; | use musicus_database::Database; | ||||||
| use std::cell::RefCell; | use std::cell::RefCell; | ||||||
| use std::path::PathBuf; | use std::path::PathBuf; | ||||||
| use std::rc::Rc; | use std::rc::Rc; | ||||||
|  | @ -19,7 +19,7 @@ pub mod player; | ||||||
| pub use player::*; | pub use player::*; | ||||||
| 
 | 
 | ||||||
| /// General states the application can be in.
 | /// General states the application can be in.
 | ||||||
| #[derive(Debug, Clone)] | #[derive(Debug, Copy, Clone)] | ||||||
| pub enum BackendState { | pub enum BackendState { | ||||||
|     /// The backend is not set up yet. This means that no backend methods except for setting the
 |     /// 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
 |     /// music library path should be called. The user interface should adapt and only present this
 | ||||||
|  | @ -36,8 +36,8 @@ pub enum BackendState { | ||||||
| 
 | 
 | ||||||
| /// A collection of all backend state and functionality.
 | /// A collection of all backend state and functionality.
 | ||||||
| pub struct Backend { | pub struct Backend { | ||||||
|     /// The internal sender to publish the state via state_stream.
 |     /// A closure that will be called whenever the backend state changes.
 | ||||||
|     state_sender: Sender<BackendState>, |     state_cb: RefCell<Option<Box<dyn Fn(BackendState)>>>, | ||||||
| 
 | 
 | ||||||
|     /// Access to GSettings.
 |     /// Access to GSettings.
 | ||||||
|     settings: gio::Settings, |     settings: gio::Settings, | ||||||
|  | @ -50,7 +50,7 @@ pub struct Backend { | ||||||
|     library_updated_sender: Sender<()>, |     library_updated_sender: Sender<()>, | ||||||
| 
 | 
 | ||||||
|     /// The database. This can be assumed to exist, when the state is set to BackendState::Ready.
 |     /// The database. This can be assumed to exist, when the state is set to BackendState::Ready.
 | ||||||
|     database: RefCell<Option<Rc<DbThread>>>, |     database: RefCell<Option<Rc<Database>>>, | ||||||
| 
 | 
 | ||||||
|     /// The player handling playlist and playback. This can be assumed to exist, when the state is
 |     /// The player handling playlist and playback. This can be assumed to exist, when the state is
 | ||||||
|     /// set to BackendState::Ready.
 |     /// set to BackendState::Ready.
 | ||||||
|  | @ -64,40 +64,46 @@ impl Backend { | ||||||
|     pub fn new() -> Self { |     pub fn new() -> Self { | ||||||
|         logger::register(); |         logger::register(); | ||||||
| 
 | 
 | ||||||
|         let (state_sender, _) = broadcast::channel(1024); |  | ||||||
|         let (library_updated_sender, _) = broadcast::channel(1024); |         let (library_updated_sender, _) = broadcast::channel(1024); | ||||||
| 
 | 
 | ||||||
|         Backend { |         Backend { | ||||||
|             state_sender, |             state_cb: RefCell::new(None), | ||||||
|             settings: gio::Settings::new("de.johrpan.musicus"), |             settings: gio::Settings::new("de.johrpan.musicus"), | ||||||
|             music_library_path: RefCell::new(None), |             music_library_path: RefCell::new(None), | ||||||
|             library_updated_sender, |             library_updated_sender, | ||||||
|             database: RefCell::new(None), |             database: RefCell::new(None), | ||||||
|             player: RefCell::new(None) |             player: RefCell::new(None), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Wait for the next state change. Initially, the state should be assumed to be
 |     /// Set the closure to be called whenever the backend state changes.
 | ||||||
|     /// BackendState::Loading. Changes should be awaited before calling init().
 |     pub fn set_state_cb<F: Fn(BackendState) + 'static>(&self, cb: F) { | ||||||
|     pub async fn next_state(&self) -> Result<BackendState> { |         self.state_cb.replace(Some(Box::new(cb))); | ||||||
|         Ok(self.state_sender.subscribe().recv().await?) |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Initialize the backend updating the state accordingly.
 |     /// Initialize the backend. A state callback should already have been registered using
 | ||||||
|     pub async fn init(&self) -> Result<()> { |     /// [`set_state_cb()`] to react to the result.
 | ||||||
|         self.init_library().await?; |     pub fn init(&self) -> Result<()> { | ||||||
|  |         self.init_library()?; | ||||||
| 
 | 
 | ||||||
|         if self.get_music_library_path().is_none() { |         match self.get_music_library_path() { | ||||||
|             self.set_state(BackendState::NoMusicLibrary); |             None => self.set_state(BackendState::NoMusicLibrary), | ||||||
|         } else { |             Some(_) => self.set_state(BackendState::Ready), | ||||||
|             self.set_state(BackendState::Ready); |         }; | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Set the current state and notify the user interface.
 |     /// Set the current state and notify the user interface.
 | ||||||
|     fn set_state(&self, state: BackendState) { |     fn set_state(&self, state: BackendState) { | ||||||
|         self.state_sender.send(state).unwrap(); |         if let Some(cb) = &*self.state_cb.borrow() { | ||||||
|  |             cb(state); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Default for Backend { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         Self::new() | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,24 +1,23 @@ | ||||||
| use crate::{Backend, BackendState, Player, Result}; | use crate::{Backend, BackendState, Player, Result}; | ||||||
| use gio::prelude::*; | use gio::prelude::*; | ||||||
| use log::warn; | use log::warn; | ||||||
| use musicus_database::DbThread; | use musicus_database::Database; | ||||||
| use std::path::PathBuf; | use std::path::PathBuf; | ||||||
| use std::rc::Rc; | use std::rc::Rc; | ||||||
| 
 | 
 | ||||||
| impl Backend { | impl Backend { | ||||||
|     /// Initialize the music library if it is set in the settings.
 |     /// Initialize the music library if it is set in the settings.
 | ||||||
|     pub(super) async fn init_library(&self) -> Result<()> { |     pub(super) fn init_library(&self) -> Result<()> { | ||||||
|         let path = self.settings.string("music-library-path"); |         let path = self.settings.string("music-library-path"); | ||||||
|         if !path.is_empty() { |         if !path.is_empty() { | ||||||
|             self.set_music_library_path_priv(PathBuf::from(path.to_string())) |             self.set_music_library_path_priv(PathBuf::from(path.to_string()))?; | ||||||
|                 .await?; |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Set the path to the music library folder and start a database thread in the background.
 |     /// Set the path to the music library folder and connect to the database.
 | ||||||
|     pub async fn set_music_library_path(&self, path: PathBuf) -> Result<()> { |     pub fn set_music_library_path(&self, path: PathBuf) -> Result<()> { | ||||||
|         if let Err(err) = self |         if let Err(err) = self | ||||||
|             .settings |             .settings | ||||||
|             .set_string("music-library-path", path.to_str().unwrap()) |             .set_string("music-library-path", path.to_str().unwrap()) | ||||||
|  | @ -30,23 +29,19 @@ impl Backend { | ||||||
|             ); |             ); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         self.set_music_library_path_priv(path).await |         self.set_music_library_path_priv(path) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Set the path to the music library folder and start a database thread in the background.
 |     /// Set the path to the music library folder and and connect to the database.
 | ||||||
|     pub async fn set_music_library_path_priv(&self, path: PathBuf) -> Result<()> { |     pub fn set_music_library_path_priv(&self, path: PathBuf) -> Result<()> { | ||||||
|         self.set_state(BackendState::Loading); |         self.set_state(BackendState::Loading); | ||||||
| 
 | 
 | ||||||
|         if let Some(db) = &*self.database.borrow() { |  | ||||||
|             db.stop().await?; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         self.music_library_path.replace(Some(path.clone())); |         self.music_library_path.replace(Some(path.clone())); | ||||||
| 
 | 
 | ||||||
|         let mut db_path = path.clone(); |         let mut db_path = path.clone(); | ||||||
|         db_path.push("musicus.db"); |         db_path.push("musicus.db"); | ||||||
| 
 | 
 | ||||||
|         let database = DbThread::new(db_path.to_str().unwrap().to_string()).await?; |         let database = Database::new(db_path.to_str().unwrap())?; | ||||||
|         self.database.replace(Some(Rc::new(database))); |         self.database.replace(Some(Rc::new(database))); | ||||||
| 
 | 
 | ||||||
|         let player = Player::new(path); |         let player = Player::new(path); | ||||||
|  | @ -62,14 +57,9 @@ impl Backend { | ||||||
|         self.music_library_path.borrow().clone() |         self.music_library_path.borrow().clone() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Get an interface to the current music library database.
 |  | ||||||
|     pub fn get_database(&self) -> Option<Rc<DbThread>> { |  | ||||||
|         self.database.borrow().clone() |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Get an interface to the database and panic if there is none.
 |     /// Get an interface to the database and panic if there is none.
 | ||||||
|     pub fn db(&self) -> Rc<DbThread> { |     pub fn db(&self) -> Rc<Database> { | ||||||
|         self.get_database().unwrap() |         self.database.borrow().clone().unwrap() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Get an interface to the playback service.
 |     /// Get an interface to the playback service.
 | ||||||
|  |  | ||||||
|  | @ -9,5 +9,4 @@ diesel_migrations = "1.4.0" | ||||||
| log = "0.4.14" | log = "0.4.14" | ||||||
| rand = "0.7.3" | rand = "0.7.3" | ||||||
| thiserror = "1.0.23" | thiserror = "1.0.23" | ||||||
| tokio = { version = "1.4.0", features = ["sync"] } |  | ||||||
| uuid = { version = "0.8", features = ["v4"] } | uuid = { version = "0.8", features = ["v4"] } | ||||||
|  |  | ||||||
|  | @ -16,12 +16,6 @@ pub enum Error { | ||||||
|     #[error("Failed to parse {0} from '{1}'")] |     #[error("Failed to parse {0} from '{1}'")] | ||||||
|     ParsingError(&'static str, String), |     ParsingError(&'static str, String), | ||||||
| 
 | 
 | ||||||
|     #[error(transparent)] |  | ||||||
|     SendError(#[from] std::sync::mpsc::SendError<super::thread::Action>), |  | ||||||
| 
 |  | ||||||
|     #[error(transparent)] |  | ||||||
|     ReceiveError(#[from] tokio::sync::oneshot::error::RecvError), |  | ||||||
| 
 |  | ||||||
|     #[error("{0}")] |     #[error("{0}")] | ||||||
|     Other(&'static str), |     Other(&'static str), | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -27,9 +27,6 @@ pub use persons::*; | ||||||
| pub mod recordings; | pub mod recordings; | ||||||
| pub use recordings::*; | pub use recordings::*; | ||||||
| 
 | 
 | ||||||
| pub mod thread; |  | ||||||
| pub use thread::*; |  | ||||||
| 
 |  | ||||||
| pub mod works; | pub mod works; | ||||||
| pub use works::*; | pub use works::*; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,397 +0,0 @@ | ||||||
| use super::*; |  | ||||||
| use log::debug; |  | ||||||
| use std::sync::mpsc; |  | ||||||
| use std::thread; |  | ||||||
| use tokio::sync::oneshot::{self, Sender}; |  | ||||||
| 
 |  | ||||||
| /// An action the database thread can perform.
 |  | ||||||
| #[derive(Debug)] |  | ||||||
| pub enum Action { |  | ||||||
|     UpdatePerson(Person, Sender<Result<()>>), |  | ||||||
|     GetPerson(String, Sender<Result<Option<Person>>>), |  | ||||||
|     DeletePerson(String, Sender<Result<()>>), |  | ||||||
|     GetPersons(Sender<Result<Vec<Person>>>), |  | ||||||
|     UpdateInstrument(Instrument, Sender<Result<()>>), |  | ||||||
|     GetInstrument(String, Sender<Result<Option<Instrument>>>), |  | ||||||
|     DeleteInstrument(String, Sender<Result<()>>), |  | ||||||
|     GetInstruments(Sender<Result<Vec<Instrument>>>), |  | ||||||
|     UpdateWork(Work, Sender<Result<()>>), |  | ||||||
|     DeleteWork(String, Sender<Result<()>>), |  | ||||||
|     GetWorks(String, Sender<Result<Vec<Work>>>), |  | ||||||
|     UpdateEnsemble(Ensemble, Sender<Result<()>>), |  | ||||||
|     GetEnsemble(String, Sender<Result<Option<Ensemble>>>), |  | ||||||
|     DeleteEnsemble(String, Sender<Result<()>>), |  | ||||||
|     GetEnsembles(Sender<Result<Vec<Ensemble>>>), |  | ||||||
|     UpdateRecording(Recording, Sender<Result<()>>), |  | ||||||
|     DeleteRecording(String, Sender<Result<()>>), |  | ||||||
|     GetRecordingsForPerson(String, Sender<Result<Vec<Recording>>>), |  | ||||||
|     GetRecordingsForEnsemble(String, Sender<Result<Vec<Recording>>>), |  | ||||||
|     GetRecordingsForWork(String, Sender<Result<Vec<Recording>>>), |  | ||||||
|     RecordingExists(String, Sender<Result<bool>>), |  | ||||||
|     UpdateMedium(Medium, Sender<Result<()>>), |  | ||||||
|     GetMedium(String, Sender<Result<Option<Medium>>>), |  | ||||||
|     GetMediumsBySourceId(String, Sender<Result<Vec<Medium>>>), |  | ||||||
|     GetMediumsForPerson(String, Sender<Result<Vec<Medium>>>), |  | ||||||
|     GetMediumsForEnsemble(String, Sender<Result<Vec<Medium>>>), |  | ||||||
|     DeleteMedium(String, Sender<Result<()>>), |  | ||||||
|     GetTracks(String, Sender<Result<Vec<Track>>>), |  | ||||||
|     Stop(Sender<()>), |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| use Action::*; |  | ||||||
| 
 |  | ||||||
| /// A database running within a thread.
 |  | ||||||
| pub struct DbThread { |  | ||||||
|     action_sender: mpsc::Sender<Action>, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl DbThread { |  | ||||||
|     /// Create a new database connection in a background thread.
 |  | ||||||
|     pub async fn new(path: String) -> Result<Self> { |  | ||||||
|         let (action_sender, action_receiver) = mpsc::channel(); |  | ||||||
|         let (ready_sender, ready_receiver) = oneshot::channel(); |  | ||||||
| 
 |  | ||||||
|         thread::spawn(move || { |  | ||||||
|             debug!("Database thread for '{}' started", path); |  | ||||||
| 
 |  | ||||||
|             let db = match Database::new(&path) { |  | ||||||
|                 Ok(db) => { |  | ||||||
|                     ready_sender.send(Ok(())).unwrap(); |  | ||||||
|                     db |  | ||||||
|                 } |  | ||||||
|                 Err(error) => { |  | ||||||
|                     ready_sender.send(Err(error)).unwrap(); |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
|             }; |  | ||||||
| 
 |  | ||||||
|             for action in action_receiver { |  | ||||||
|                 debug!("Database thread for '{}' got action {:?}", path, action); |  | ||||||
|                 match action { |  | ||||||
|                     UpdatePerson(person, sender) => { |  | ||||||
|                         sender.send(db.update_person(person)).unwrap(); |  | ||||||
|                     } |  | ||||||
|                     GetPerson(id, sender) => { |  | ||||||
|                         sender.send(db.get_person(&id)).unwrap(); |  | ||||||
|                     } |  | ||||||
|                     DeletePerson(id, sender) => { |  | ||||||
|                         sender.send(db.delete_person(&id)).unwrap(); |  | ||||||
|                     } |  | ||||||
|                     GetPersons(sender) => { |  | ||||||
|                         sender.send(db.get_persons()).unwrap(); |  | ||||||
|                     } |  | ||||||
|                     UpdateInstrument(instrument, sender) => { |  | ||||||
|                         sender.send(db.update_instrument(instrument)).unwrap(); |  | ||||||
|                     } |  | ||||||
|                     GetInstrument(id, sender) => { |  | ||||||
|                         sender.send(db.get_instrument(&id)).unwrap(); |  | ||||||
|                     } |  | ||||||
|                     DeleteInstrument(id, sender) => { |  | ||||||
|                         sender.send(db.delete_instrument(&id)).unwrap(); |  | ||||||
|                     } |  | ||||||
|                     GetInstruments(sender) => { |  | ||||||
|                         sender.send(db.get_instruments()).unwrap(); |  | ||||||
|                     } |  | ||||||
|                     UpdateWork(work, sender) => { |  | ||||||
|                         sender.send(db.update_work(work)).unwrap(); |  | ||||||
|                     } |  | ||||||
|                     DeleteWork(id, sender) => { |  | ||||||
|                         sender.send(db.delete_work(&id)).unwrap(); |  | ||||||
|                     } |  | ||||||
|                     GetWorks(id, sender) => { |  | ||||||
|                         sender.send(db.get_works(&id)).unwrap(); |  | ||||||
|                     } |  | ||||||
|                     UpdateEnsemble(ensemble, sender) => { |  | ||||||
|                         sender.send(db.update_ensemble(ensemble)).unwrap(); |  | ||||||
|                     } |  | ||||||
|                     GetEnsemble(id, sender) => { |  | ||||||
|                         sender.send(db.get_ensemble(&id)).unwrap(); |  | ||||||
|                     } |  | ||||||
|                     DeleteEnsemble(id, sender) => { |  | ||||||
|                         sender.send(db.delete_ensemble(&id)).unwrap(); |  | ||||||
|                     } |  | ||||||
|                     GetEnsembles(sender) => { |  | ||||||
|                         sender.send(db.get_ensembles()).unwrap(); |  | ||||||
|                     } |  | ||||||
|                     UpdateRecording(recording, sender) => { |  | ||||||
|                         sender.send(db.update_recording(recording)).unwrap(); |  | ||||||
|                     } |  | ||||||
|                     DeleteRecording(id, sender) => { |  | ||||||
|                         sender.send(db.delete_recording(&id)).unwrap(); |  | ||||||
|                     } |  | ||||||
|                     GetRecordingsForPerson(id, sender) => { |  | ||||||
|                         sender.send(db.get_recordings_for_person(&id)).unwrap(); |  | ||||||
|                     } |  | ||||||
|                     GetRecordingsForEnsemble(id, sender) => { |  | ||||||
|                         sender.send(db.get_recordings_for_ensemble(&id)).unwrap(); |  | ||||||
|                     } |  | ||||||
|                     GetRecordingsForWork(id, sender) => { |  | ||||||
|                         sender.send(db.get_recordings_for_work(&id)).unwrap(); |  | ||||||
|                     } |  | ||||||
|                     RecordingExists(id, sender) => { |  | ||||||
|                         sender.send(db.recording_exists(&id)).unwrap(); |  | ||||||
|                     } |  | ||||||
|                     UpdateMedium(medium, sender) => { |  | ||||||
|                         sender.send(db.update_medium(medium)).unwrap(); |  | ||||||
|                     } |  | ||||||
|                     GetMedium(id, sender) => { |  | ||||||
|                         sender.send(db.get_medium(&id)).unwrap(); |  | ||||||
|                     } |  | ||||||
|                     GetMediumsBySourceId(id, sender) => { |  | ||||||
|                         sender.send(db.get_mediums_by_source_id(&id)).unwrap(); |  | ||||||
|                     } |  | ||||||
|                     GetMediumsForPerson(id, sender) => { |  | ||||||
|                         sender.send(db.get_mediums_for_person(&id)).unwrap(); |  | ||||||
|                     } |  | ||||||
|                     GetMediumsForEnsemble(id, sender) => { |  | ||||||
|                         sender.send(db.get_mediums_for_ensemble(&id)).unwrap(); |  | ||||||
|                     } |  | ||||||
|                     DeleteMedium(id, sender) => { |  | ||||||
|                         sender.send(db.delete_medium(&id)).unwrap(); |  | ||||||
|                     } |  | ||||||
|                     GetTracks(recording_id, sender) => { |  | ||||||
|                         sender.send(db.get_tracks(&recording_id)).unwrap(); |  | ||||||
|                     } |  | ||||||
|                     Stop(sender) => { |  | ||||||
|                         sender.send(()).unwrap(); |  | ||||||
|                         break; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             debug!("Database thread for '{}' stopped", path); |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         ready_receiver.await??; |  | ||||||
|         Ok(Self { action_sender }) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Update an existing person or insert a new one.
 |  | ||||||
|     pub async fn update_person(&self, person: Person) -> Result<()> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender.send(UpdatePerson(person, sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Get an existing person.
 |  | ||||||
|     pub async fn get_person(&self, id: &str) -> Result<Option<Person>> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender.send(GetPerson(id.to_string(), sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Delete an existing person. This will fail, if there are still other items referencing
 |  | ||||||
|     /// this person.
 |  | ||||||
|     pub async fn delete_person(&self, id: &str) -> Result<()> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender |  | ||||||
|             .send(DeletePerson(id.to_string(), sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Get all existing persons.
 |  | ||||||
|     pub async fn get_persons(&self) -> Result<Vec<Person>> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender.send(GetPersons(sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Update an existing instrument or insert a new one.
 |  | ||||||
|     pub async fn update_instrument(&self, instrument: Instrument) -> Result<()> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender |  | ||||||
|             .send(UpdateInstrument(instrument, sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Get an existing instrument.
 |  | ||||||
|     pub async fn get_instrument(&self, id: &str) -> Result<Option<Instrument>> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender |  | ||||||
|             .send(GetInstrument(id.to_string(), sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Delete an existing instrument. This will fail, if there are still other items referencing
 |  | ||||||
|     /// this instrument.
 |  | ||||||
|     pub async fn delete_instrument(&self, id: &str) -> Result<()> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender |  | ||||||
|             .send(DeleteInstrument(id.to_string(), sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Get all existing instruments.
 |  | ||||||
|     pub async fn get_instruments(&self) -> Result<Vec<Instrument>> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender.send(GetInstruments(sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Update an existing work or insert a new one.
 |  | ||||||
|     pub async fn update_work(&self, work: Work) -> Result<()> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender.send(UpdateWork(work, sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Delete an existing work. This will fail, if there are still other items referencing
 |  | ||||||
|     /// this work.
 |  | ||||||
|     pub async fn delete_work(&self, id: &str) -> Result<()> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender |  | ||||||
|             .send(DeleteWork(id.to_string(), sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Get information on all existing works by a composer.
 |  | ||||||
|     pub async fn get_works(&self, person_id: &str) -> Result<Vec<Work>> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender |  | ||||||
|             .send(GetWorks(person_id.to_string(), sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Update an existing ensemble or insert a new one.
 |  | ||||||
|     pub async fn update_ensemble(&self, ensemble: Ensemble) -> Result<()> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender.send(UpdateEnsemble(ensemble, sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Get an existing ensemble.
 |  | ||||||
|     pub async fn get_ensemble(&self, id: &str) -> Result<Option<Ensemble>> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender |  | ||||||
|             .send(GetEnsemble(id.to_string(), sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Delete an existing ensemble. This will fail, if there are still other items referencing
 |  | ||||||
|     /// this ensemble.
 |  | ||||||
|     pub async fn delete_ensemble(&self, id: &str) -> Result<()> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender |  | ||||||
|             .send(DeleteEnsemble(id.to_string(), sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Get all existing ensembles.
 |  | ||||||
|     pub async fn get_ensembles(&self) -> Result<Vec<Ensemble>> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender.send(GetEnsembles(sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Update an existing recording or insert a new one.
 |  | ||||||
|     pub async fn update_recording(&self, recording: Recording) -> Result<()> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender |  | ||||||
|             .send(UpdateRecording(recording, sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Delete an existing recording.
 |  | ||||||
|     pub async fn delete_recording(&self, id: &str) -> Result<()> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender |  | ||||||
|             .send(DeleteRecording(id.to_string(), sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Get information on all recordings in which a person performs.
 |  | ||||||
|     pub async fn get_recordings_for_person(&self, person_id: &str) -> Result<Vec<Recording>> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender |  | ||||||
|             .send(GetRecordingsForPerson(person_id.to_string(), sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Get information on all recordings in which an ensemble performs.
 |  | ||||||
|     pub async fn get_recordings_for_ensemble(&self, ensemble_id: &str) -> Result<Vec<Recording>> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender |  | ||||||
|             .send(GetRecordingsForEnsemble(ensemble_id.to_string(), sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Get information on all recordings of a work.
 |  | ||||||
|     pub async fn get_recordings_for_work(&self, work_id: &str) -> Result<Vec<Recording>> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender |  | ||||||
|             .send(GetRecordingsForWork(work_id.to_string(), sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Check whether a recording exists within the database.
 |  | ||||||
|     pub async fn recording_exists(&self, id: &str) -> Result<bool> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender |  | ||||||
|             .send(RecordingExists(id.to_string(), sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Update an existing medium or insert a new one.
 |  | ||||||
|     pub async fn update_medium(&self, medium: Medium) -> Result<()> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender.send(UpdateMedium(medium, sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Delete an existing medium. This will fail, if there are still other
 |  | ||||||
|     /// items referencing this medium.
 |  | ||||||
|     pub async fn delete_medium(&self, id: &str) -> Result<()> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
| 
 |  | ||||||
|         self.action_sender |  | ||||||
|             .send(DeleteMedium(id.to_owned(), sender))?; |  | ||||||
| 
 |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Get an existing medium.
 |  | ||||||
|     pub async fn get_medium(&self, id: &str) -> Result<Option<Medium>> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender.send(GetMedium(id.to_owned(), sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Get all mediums with the specified source ID.
 |  | ||||||
|     pub async fn get_mediums_by_source_id(&self, id: &str) -> Result<Vec<Medium>> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender |  | ||||||
|             .send(GetMediumsBySourceId(id.to_owned(), sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Get all mediums on which a person performs.
 |  | ||||||
|     pub async fn get_mediums_for_person(&self, id: &str) -> Result<Vec<Medium>> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender |  | ||||||
|             .send(GetMediumsForPerson(id.to_owned(), sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Get all mediums on which an ensemble performs.
 |  | ||||||
|     pub async fn get_mediums_for_ensemble(&self, id: &str) -> Result<Vec<Medium>> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender |  | ||||||
|             .send(GetMediumsForEnsemble(id.to_owned(), sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Get all tracks for a recording.
 |  | ||||||
|     pub async fn get_tracks(&self, recording_id: &str) -> Result<Vec<Track>> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender |  | ||||||
|             .send(GetTracks(recording_id.to_owned(), sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Stop the database thread. Any future access to the database will fail.
 |  | ||||||
|     pub async fn stop(&self) -> Result<()> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender.send(Stop(sender))?; |  | ||||||
|         Ok(receiver.await?) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -53,38 +53,6 @@ | ||||||
|         </property> |         </property> | ||||||
|       </object> |       </object> | ||||||
|     </child> |     </child> | ||||||
|     <child> |  | ||||||
|       <object class="GtkStackPage"> |  | ||||||
|         <property name="name">loading</property> |  | ||||||
|         <property name="child"> |  | ||||||
|           <object class="GtkBox"> |  | ||||||
|             <property name="orientation">vertical</property> |  | ||||||
|             <child> |  | ||||||
|               <object class="AdwHeaderBar"> |  | ||||||
|                 <property name="show-start-title-buttons">false</property> |  | ||||||
|                 <property name="show-end-title-buttons">false</property> |  | ||||||
|                 <property name="title-widget"> |  | ||||||
|                   <object class="AdwWindowTitle"> |  | ||||||
|                     <property name="title" translatable="true">Loading</property> |  | ||||||
|                   </object> |  | ||||||
|                 </property> |  | ||||||
|               </object> |  | ||||||
|             </child> |  | ||||||
|             <child> |  | ||||||
|               <object class="GtkSpinner"> |  | ||||||
|                 <property name="hexpand">true</property> |  | ||||||
|                 <property name="vexpand">true</property> |  | ||||||
|                 <property name="halign">center</property> |  | ||||||
|                 <property name="valign">center</property> |  | ||||||
|                 <property name="width-request">32</property> |  | ||||||
|                 <property name="height-request">32</property> |  | ||||||
|                 <property name="spinning">true</property> |  | ||||||
|               </object> |  | ||||||
|             </child> |  | ||||||
|           </object> |  | ||||||
|         </property> |  | ||||||
|       </object> |  | ||||||
|     </child> |  | ||||||
|     <child> |     <child> | ||||||
|       <object class="GtkStackPage"> |       <object class="GtkStackPage"> | ||||||
|         <property name="name">error</property> |         <property name="name">error</property> | ||||||
|  |  | ||||||
|  | @ -56,18 +56,15 @@ impl Screen<Option<Ensemble>, Ensemble> for EnsembleEditor { | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.editor.set_save_cb(clone!(@weak this => move || { |         this.editor.set_save_cb(clone!(@weak this => move || { | ||||||
|             spawn!(@clone this, async move { |             match this.save() { | ||||||
|                 this.editor.loading(); |                 Ok(ensemble) => { | ||||||
|                 match this.save().await { |                     this.handle.pop(Some(ensemble)); | ||||||
|                     Ok(ensemble) => { |  | ||||||
|                         this.handle.pop(Some(ensemble)); |  | ||||||
|                     } |  | ||||||
|                     Err(err) => { |  | ||||||
|                         let description = gettext!("Cause: {}", err); |  | ||||||
|                         this.editor.error(&gettext("Failed to save ensemble!"), &description); |  | ||||||
|                     } |  | ||||||
|                 } |                 } | ||||||
|             }); |                 Err(err) => { | ||||||
|  |                     let description = gettext!("Cause: {}", err); | ||||||
|  |                     this.editor.error(&gettext("Failed to save ensemble!"), &description); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.name |         this.name | ||||||
|  | @ -87,7 +84,7 @@ impl EnsembleEditor { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Save the ensemble.
 |     /// Save the ensemble.
 | ||||||
|     async fn save(&self) -> Result<Ensemble> { |     fn save(&self) -> Result<Ensemble> { | ||||||
|         let name = self.name.get_text(); |         let name = self.name.get_text(); | ||||||
| 
 | 
 | ||||||
|         let ensemble = Ensemble { |         let ensemble = Ensemble { | ||||||
|  | @ -95,12 +92,7 @@ impl EnsembleEditor { | ||||||
|             name, |             name, | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         self.handle |         self.handle.backend.db().update_ensemble(ensemble.clone())?; | ||||||
|             .backend |  | ||||||
|             .db() |  | ||||||
|             .update_ensemble(ensemble.clone()) |  | ||||||
|             .await?; |  | ||||||
| 
 |  | ||||||
|         self.handle.backend.library_changed(); |         self.handle.backend.library_changed(); | ||||||
| 
 | 
 | ||||||
|         Ok(ensemble) |         Ok(ensemble) | ||||||
|  |  | ||||||
|  | @ -56,18 +56,15 @@ impl Screen<Option<Instrument>, Instrument> for InstrumentEditor { | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.editor.set_save_cb(clone!(@weak this => move || { |         this.editor.set_save_cb(clone!(@weak this => move || { | ||||||
|             spawn!(@clone this, async move { |             match this.save() { | ||||||
|                 this.editor.loading(); |                 Ok(instrument) => { | ||||||
|                 match this.save().await { |                     this.handle.pop(Some(instrument)); | ||||||
|                     Ok(instrument) => { |  | ||||||
|                         this.handle.pop(Some(instrument)); |  | ||||||
|                     } |  | ||||||
|                     Err(err) => { |  | ||||||
|                         let description = gettext!("Cause: {}", err); |  | ||||||
|                         this.editor.error(&gettext("Failed to save instrument!"), &description); |  | ||||||
|                     } |  | ||||||
|                 } |                 } | ||||||
|             }); |                 Err(err) => { | ||||||
|  |                     let description = gettext!("Cause: {}", err); | ||||||
|  |                     this.editor.error(&gettext("Failed to save instrument!"), &description); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.name |         this.name | ||||||
|  | @ -87,7 +84,7 @@ impl InstrumentEditor { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Save the instrument.
 |     /// Save the instrument.
 | ||||||
|     async fn save(&self) -> Result<Instrument> { |     fn save(&self) -> Result<Instrument> { | ||||||
|         let name = self.name.get_text(); |         let name = self.name.get_text(); | ||||||
| 
 | 
 | ||||||
|         let instrument = Instrument { |         let instrument = Instrument { | ||||||
|  | @ -95,12 +92,7 @@ impl InstrumentEditor { | ||||||
|             name, |             name, | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         self.handle |         self.handle.backend.db().update_instrument(instrument.clone())?; | ||||||
|             .backend |  | ||||||
|             .db() |  | ||||||
|             .update_instrument(instrument.clone()) |  | ||||||
|             .await?; |  | ||||||
| 
 |  | ||||||
|         self.handle.backend.library_changed(); |         self.handle.backend.library_changed(); | ||||||
| 
 | 
 | ||||||
|         Ok(instrument) |         Ok(instrument) | ||||||
|  |  | ||||||
|  | @ -63,18 +63,15 @@ impl Screen<Option<Person>, Person> for PersonEditor { | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.editor.set_save_cb(clone!(@strong this => move || { |         this.editor.set_save_cb(clone!(@strong this => move || { | ||||||
|             spawn!(@clone this, async move { |             match this.save() { | ||||||
|                 this.editor.loading(); |                 Ok(person) => { | ||||||
|                 match this.save().await { |                     this.handle.pop(Some(person)); | ||||||
|                     Ok(person) => { |  | ||||||
|                         this.handle.pop(Some(person)); |  | ||||||
|                     } |  | ||||||
|                     Err(err) => { |  | ||||||
|                         let description = gettext!("Cause: {}", err); |  | ||||||
|                         this.editor.error(&gettext("Failed to save person!"), &description); |  | ||||||
|                     } |  | ||||||
|                 } |                 } | ||||||
|             }); |                 Err(err) => { | ||||||
|  |                     let description = gettext!("Cause: {}", err); | ||||||
|  |                     this.editor.error(&gettext("Failed to save person!"), &description); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.first_name |         this.first_name | ||||||
|  | @ -100,7 +97,7 @@ impl PersonEditor { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Save the person.
 |     /// Save the person.
 | ||||||
|     async fn save(self: &Rc<Self>) -> Result<Person> { |     fn save(self: &Rc<Self>) -> Result<Person> { | ||||||
|         let first_name = self.first_name.get_text(); |         let first_name = self.first_name.get_text(); | ||||||
|         let last_name = self.last_name.get_text(); |         let last_name = self.last_name.get_text(); | ||||||
| 
 | 
 | ||||||
|  | @ -110,11 +107,7 @@ impl PersonEditor { | ||||||
|             last_name, |             last_name, | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         self.handle |         self.handle.backend.db().update_person(person.clone())?; | ||||||
|             .backend |  | ||||||
|             .db() |  | ||||||
|             .update_person(person.clone()) |  | ||||||
|             .await?; |  | ||||||
|         self.handle.backend.library_changed(); |         self.handle.backend.library_changed(); | ||||||
| 
 | 
 | ||||||
|         Ok(person) |         Ok(person) | ||||||
|  |  | ||||||
|  | @ -74,18 +74,15 @@ impl Screen<Option<Recording>, Recording> for RecordingEditor { | ||||||
| 
 | 
 | ||||||
|         this.save_button |         this.save_button | ||||||
|             .connect_clicked(clone!(@weak this =>  move |_| { |             .connect_clicked(clone!(@weak this =>  move |_| { | ||||||
|                 spawn!(@clone this, async move { |                 match this.save() { | ||||||
|                     this.widget.set_visible_child_name("loading"); |                     Ok(recording) => { | ||||||
|                     match this.save().await { |                         this.handle.pop(Some(recording)); | ||||||
|                         Ok(recording) => { |  | ||||||
|                             this.handle.pop(Some(recording)); |  | ||||||
|                         } |  | ||||||
|                         Err(_) => { |  | ||||||
|                             this.info_bar.set_revealed(true); |  | ||||||
|                             this.widget.set_visible_child_name("content"); |  | ||||||
|                         } |  | ||||||
|                     } |                     } | ||||||
|                 }); |                     Err(_) => { | ||||||
|  |                         this.info_bar.set_revealed(true); | ||||||
|  |                         this.widget.set_visible_child_name("content"); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|             })); |             })); | ||||||
| 
 | 
 | ||||||
|         work_button.connect_clicked(clone!(@weak this =>  move |_| { |         work_button.connect_clicked(clone!(@weak this =>  move |_| { | ||||||
|  | @ -179,7 +176,7 @@ impl RecordingEditor { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Save the recording.
 |     /// Save the recording.
 | ||||||
|     async fn save(self: &Rc<Self>) -> Result<Recording> { |     fn save(self: &Rc<Self>) -> Result<Recording> { | ||||||
|         let recording = Recording { |         let recording = Recording { | ||||||
|             id: self.id.clone(), |             id: self.id.clone(), | ||||||
|             work: self |             work: self | ||||||
|  | @ -191,13 +188,7 @@ impl RecordingEditor { | ||||||
|             performances: self.performances.borrow().clone(), |             performances: self.performances.borrow().clone(), | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         self.handle |         self.handle.backend.db().update_recording(recording.clone())?; | ||||||
|             .backend |  | ||||||
|             .db() |  | ||||||
|             .update_recording(recording.clone()) |  | ||||||
|             .await |  | ||||||
|             .unwrap(); |  | ||||||
| 
 |  | ||||||
|         self.handle.backend.library_changed(); |         self.handle.backend.library_changed(); | ||||||
| 
 | 
 | ||||||
|         Ok(recording) |         Ok(recording) | ||||||
|  |  | ||||||
|  | @ -113,18 +113,15 @@ impl Screen<Option<Work>, Work> for WorkEditor { | ||||||
| 
 | 
 | ||||||
|         this.save_button |         this.save_button | ||||||
|             .connect_clicked(clone!(@weak this =>  move |_| { |             .connect_clicked(clone!(@weak this =>  move |_| { | ||||||
|                 spawn!(@clone this, async move { |                 match this.save() { | ||||||
|                     this.widget.set_visible_child_name("loading"); |                     Ok(work) => { | ||||||
|                     match this.save().await { |                         this.handle.pop(Some(work)); | ||||||
|                         Ok(work) => { |  | ||||||
|                             this.handle.pop(Some(work)); |  | ||||||
|                         } |  | ||||||
|                         Err(_) => { |  | ||||||
|                             this.info_bar.set_revealed(true); |  | ||||||
|                             this.widget.set_visible_child_name("content"); |  | ||||||
|                         } |  | ||||||
|                     } |                     } | ||||||
|                 }); |                     Err(_) => { | ||||||
|  |                         this.info_bar.set_revealed(true); | ||||||
|  |                         this.widget.set_visible_child_name("content"); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|             })); |             })); | ||||||
| 
 | 
 | ||||||
|         composer_button.connect_clicked(clone!(@weak this =>  move |_| { |         composer_button.connect_clicked(clone!(@weak this =>  move |_| { | ||||||
|  | @ -313,7 +310,7 @@ impl WorkEditor { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Save the work.
 |     /// Save the work.
 | ||||||
|     async fn save(self: &Rc<Self>) -> Result<Work> { |     fn save(self: &Rc<Self>) -> Result<Work> { | ||||||
|         let mut section_count: usize = 0; |         let mut section_count: usize = 0; | ||||||
|         let mut parts = Vec::new(); |         let mut parts = Vec::new(); | ||||||
|         let mut sections = Vec::new(); |         let mut sections = Vec::new(); | ||||||
|  | @ -343,13 +340,7 @@ impl WorkEditor { | ||||||
|             sections, |             sections, | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         self.handle |         self.handle.backend.db().update_work(work.clone())?; | ||||||
|             .backend |  | ||||||
|             .db() |  | ||||||
|             .update_work(work.clone()) |  | ||||||
|             .await |  | ||||||
|             .unwrap(); |  | ||||||
| 
 |  | ||||||
|         self.handle.backend.library_changed(); |         self.handle.backend.library_changed(); | ||||||
| 
 | 
 | ||||||
|         Ok(work) |         Ok(work) | ||||||
|  |  | ||||||
|  | @ -28,7 +28,7 @@ impl ImportScreen { | ||||||
| 
 | 
 | ||||||
|         let this = self; |         let this = self; | ||||||
|         spawn!(@clone this, async move { |         spawn!(@clone this, async move { | ||||||
|             let mediums = this.handle.backend.db().get_mediums_by_source_id(this.session.source_id()).await; |             let mediums = this.handle.backend.db().get_mediums_by_source_id(this.session.source_id()); | ||||||
| 
 | 
 | ||||||
|             match mediums { |             match mediums { | ||||||
|                 Ok(mediums) => { |                 Ok(mediums) => { | ||||||
|  |  | ||||||
|  | @ -256,12 +256,7 @@ impl MediumPreview { | ||||||
|             tracks, |             tracks, | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         self.handle |         self.handle.backend.db().update_medium(medium)?; | ||||||
|             .backend |  | ||||||
|             .db() |  | ||||||
|             .update_medium(medium.clone()) |  | ||||||
|             .await?; |  | ||||||
| 
 |  | ||||||
|         self.handle.backend.library_changed(); |         self.handle.backend.library_changed(); | ||||||
| 
 | 
 | ||||||
|         Ok(()) |         Ok(()) | ||||||
|  |  | ||||||
|  | @ -48,11 +48,8 @@ impl Preferences { | ||||||
|                 if let gtk::ResponseType::Accept = response { |                 if let gtk::ResponseType::Accept = response { | ||||||
|                     if let Some(file) = dialog.file() { |                     if let Some(file) = dialog.file() { | ||||||
|                         if let Some(path) = file.path() { |                         if let Some(path) = file.path() { | ||||||
|  |                             this.backend.set_music_library_path(path.clone()).unwrap(); | ||||||
|                             this.music_library_path_row.set_subtitle(path.to_str().unwrap()); |                             this.music_library_path_row.set_subtitle(path.to_str().unwrap()); | ||||||
| 
 |  | ||||||
|                             spawn!(@clone this, async move { |  | ||||||
|                                 this.backend.set_music_library_path(path).await.unwrap(); |  | ||||||
|                             }); |  | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  | @ -59,7 +59,7 @@ impl Screen<Ensemble, ()> for EnsembleScreen { | ||||||
|             &gettext("Delete ensemble"), |             &gettext("Delete ensemble"), | ||||||
|             clone!(@weak this =>  move || { |             clone!(@weak this =>  move || { | ||||||
|                 spawn!(@clone this, async move { |                 spawn!(@clone this, async move { | ||||||
|                     this.handle.backend.db().delete_ensemble(&this.ensemble.id).await.unwrap(); |                     this.handle.backend.db().delete_ensemble(&this.ensemble.id).unwrap(); | ||||||
|                     this.handle.backend.library_changed(); |                     this.handle.backend.library_changed(); | ||||||
|                 }); |                 }); | ||||||
|             }), |             }), | ||||||
|  | @ -128,43 +128,41 @@ impl Screen<Ensemble, ()> for EnsembleScreen { | ||||||
|                 search.is_empty() || name.contains(&search) |                 search.is_empty() || name.contains(&search) | ||||||
|             })); |             })); | ||||||
| 
 | 
 | ||||||
|         // Load the content asynchronously.
 |         // Load the content.
 | ||||||
| 
 | 
 | ||||||
|         spawn!(@clone this, async move { |         let recordings = this | ||||||
|             let recordings = this.handle |             .handle | ||||||
|                 .backend |             .backend | ||||||
|                 .db() |             .db() | ||||||
|                 .get_recordings_for_ensemble(&this.ensemble.id) |             .get_recordings_for_ensemble(&this.ensemble.id) | ||||||
|                 .await |             .unwrap(); | ||||||
|                 .unwrap(); |  | ||||||
| 
 | 
 | ||||||
|             let mediums = this.handle |         let mediums = this | ||||||
|                 .backend |             .handle | ||||||
|                 .db() |             .backend | ||||||
|                 .get_mediums_for_ensemble(&this.ensemble.id) |             .db() | ||||||
|                 .await |             .get_mediums_for_ensemble(&this.ensemble.id) | ||||||
|                 .unwrap(); |             .unwrap(); | ||||||
| 
 | 
 | ||||||
|             if !recordings.is_empty() { |         if !recordings.is_empty() { | ||||||
|                 let length = recordings.len(); |             let length = recordings.len(); | ||||||
|                 this.recordings.replace(recordings); |             this.recordings.replace(recordings); | ||||||
|                 this.recording_list.update(length); |             this.recording_list.update(length); | ||||||
| 
 | 
 | ||||||
|                 let section = Section::new("Recordings", &this.recording_list.widget); |             let section = Section::new("Recordings", &this.recording_list.widget); | ||||||
|                 this.widget.add_content(§ion.widget); |             this.widget.add_content(§ion.widget); | ||||||
|             } |         } | ||||||
| 
 | 
 | ||||||
|             if !mediums.is_empty() { |         if !mediums.is_empty() { | ||||||
|                 let length = mediums.len(); |             let length = mediums.len(); | ||||||
|                 this.mediums.replace(mediums); |             this.mediums.replace(mediums); | ||||||
|                 this.medium_list.update(length); |             this.medium_list.update(length); | ||||||
| 
 | 
 | ||||||
|                 let section = Section::new("Mediums", &this.medium_list.widget); |             let section = Section::new("Mediums", &this.medium_list.widget); | ||||||
|                 this.widget.add_content(§ion.widget); |             this.widget.add_content(§ion.widget); | ||||||
|             } |         } | ||||||
| 
 | 
 | ||||||
|             this.widget.ready(); |         this.widget.ready(); | ||||||
|         }); |  | ||||||
| 
 | 
 | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -136,16 +136,15 @@ impl Screen<(), ()> for MainScreen { | ||||||
|             }); |             }); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         // Load the content asynchronously.
 |         // Load the content whenever there is a new library update.
 | ||||||
| 
 |  | ||||||
|         spawn!(@clone this, async move { |         spawn!(@clone this, async move { | ||||||
|             loop { |             loop { | ||||||
|                 this.navigator.reset(); |                 this.navigator.reset(); | ||||||
| 
 | 
 | ||||||
|                 let mut poes = Vec::new(); |                 let mut poes = Vec::new(); | ||||||
| 
 | 
 | ||||||
|                 let persons = this.handle.backend.db().get_persons().await.unwrap(); |                 let persons = this.handle.backend.db().get_persons().unwrap(); | ||||||
|                 let ensembles = this.handle.backend.db().get_ensembles().await.unwrap(); |                 let ensembles = this.handle.backend.db().get_ensembles().unwrap(); | ||||||
| 
 | 
 | ||||||
|                 for person in persons { |                 for person in persons { | ||||||
|                     poes.push(PersonOrEnsemble::Person(person)); |                     poes.push(PersonOrEnsemble::Person(person)); | ||||||
|  |  | ||||||
|  | @ -64,7 +64,7 @@ impl Screen<Person, ()> for PersonScreen { | ||||||
|             &gettext("Delete person"), |             &gettext("Delete person"), | ||||||
|             clone!(@weak this =>  move || { |             clone!(@weak this =>  move || { | ||||||
|                 spawn!(@clone this, async move { |                 spawn!(@clone this, async move { | ||||||
|                     this.handle.backend.db().delete_person(&this.person.id).await.unwrap(); |                     this.handle.backend.db().delete_person(&this.person.id).unwrap(); | ||||||
|                     this.handle.backend.library_changed(); |                     this.handle.backend.library_changed(); | ||||||
|                 }); |                 }); | ||||||
|             }), |             }), | ||||||
|  | @ -162,59 +162,52 @@ impl Screen<Person, ()> for PersonScreen { | ||||||
|                 search.is_empty() || name.contains(&search) |                 search.is_empty() || name.contains(&search) | ||||||
|             })); |             })); | ||||||
| 
 | 
 | ||||||
|         // Load the content asynchronously.
 |         // Load the content.
 | ||||||
| 
 | 
 | ||||||
|         spawn!(@clone this, async move { |         let works = this.handle.backend.db().get_works(&this.person.id).unwrap(); | ||||||
|             let works = this.handle |  | ||||||
|                 .backend |  | ||||||
|                 .db() |  | ||||||
|                 .get_works(&this.person.id) |  | ||||||
|                 .await |  | ||||||
|                 .unwrap(); |  | ||||||
| 
 | 
 | ||||||
|             let recordings = this.handle |         let recordings = this | ||||||
|                 .backend |             .handle | ||||||
|                 .db() |             .backend | ||||||
|                 .get_recordings_for_person(&this.person.id) |             .db() | ||||||
|                 .await |             .get_recordings_for_person(&this.person.id) | ||||||
|                 .unwrap(); |             .unwrap(); | ||||||
| 
 | 
 | ||||||
|             let mediums = this.handle |         let mediums = this | ||||||
|                 .backend |             .handle | ||||||
|                 .db() |             .backend | ||||||
|                 .get_mediums_for_person(&this.person.id) |             .db() | ||||||
|                 .await |             .get_mediums_for_person(&this.person.id) | ||||||
|                 .unwrap(); |             .unwrap(); | ||||||
| 
 | 
 | ||||||
|             if !works.is_empty() { |         if !works.is_empty() { | ||||||
|                 let length = works.len(); |             let length = works.len(); | ||||||
|                 this.works.replace(works); |             this.works.replace(works); | ||||||
|                 this.work_list.update(length); |             this.work_list.update(length); | ||||||
| 
 | 
 | ||||||
|                 let section = Section::new("Works", &this.work_list.widget); |             let section = Section::new("Works", &this.work_list.widget); | ||||||
|                 this.widget.add_content(§ion.widget); |             this.widget.add_content(§ion.widget); | ||||||
|             } |         } | ||||||
| 
 | 
 | ||||||
|             if !recordings.is_empty() { |         if !recordings.is_empty() { | ||||||
|                 let length = recordings.len(); |             let length = recordings.len(); | ||||||
|                 this.recordings.replace(recordings); |             this.recordings.replace(recordings); | ||||||
|                 this.recording_list.update(length); |             this.recording_list.update(length); | ||||||
| 
 | 
 | ||||||
|                 let section = Section::new("Recordings", &this.recording_list.widget); |             let section = Section::new("Recordings", &this.recording_list.widget); | ||||||
|                 this.widget.add_content(§ion.widget); |             this.widget.add_content(§ion.widget); | ||||||
|             } |         } | ||||||
| 
 | 
 | ||||||
|             if !mediums.is_empty() { |         if !mediums.is_empty() { | ||||||
|                 let length = mediums.len(); |             let length = mediums.len(); | ||||||
|                 this.mediums.replace(mediums); |             this.mediums.replace(mediums); | ||||||
|                 this.medium_list.update(length); |             this.medium_list.update(length); | ||||||
| 
 | 
 | ||||||
|                 let section = Section::new("Mediums", &this.medium_list.widget); |             let section = Section::new("Mediums", &this.medium_list.widget); | ||||||
|                 this.widget.add_content(§ion.widget); |             this.widget.add_content(§ion.widget); | ||||||
|             } |         } | ||||||
| 
 | 
 | ||||||
|             this.widget.ready(); |         this.widget.ready(); | ||||||
|         }); |  | ||||||
| 
 | 
 | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -65,7 +65,7 @@ impl Screen<Recording, ()> for RecordingScreen { | ||||||
|             &gettext("Delete recording"), |             &gettext("Delete recording"), | ||||||
|             clone!(@weak this =>  move || { |             clone!(@weak this =>  move || { | ||||||
|                 spawn!(@clone this, async move { |                 spawn!(@clone this, async move { | ||||||
|                     this.handle.backend.db().delete_recording(&this.recording.id).await.unwrap(); |                     this.handle.backend.db().delete_recording(&this.recording.id).unwrap(); | ||||||
|                     this.handle.backend.library_changed(); |                     this.handle.backend.library_changed(); | ||||||
|                 }); |                 }); | ||||||
|             }), |             }), | ||||||
|  | @ -93,19 +93,17 @@ impl Screen<Recording, ()> for RecordingScreen { | ||||||
|                 row.upcast() |                 row.upcast() | ||||||
|             })); |             })); | ||||||
| 
 | 
 | ||||||
|         // Load the content asynchronously.
 |         // Load the content.
 | ||||||
| 
 | 
 | ||||||
|         spawn!(@clone this, async move { |         let tracks = this | ||||||
|             let tracks = this.handle |             .handle | ||||||
|                 .backend |             .backend | ||||||
|                 .db() |             .db() | ||||||
|                 .get_tracks(&this.recording.id) |             .get_tracks(&this.recording.id) | ||||||
|                 .await |             .unwrap(); | ||||||
|                 .unwrap(); |  | ||||||
| 
 | 
 | ||||||
|             this.show_tracks(tracks); |         this.show_tracks(tracks); | ||||||
|             this.widget.ready(); |         this.widget.ready(); | ||||||
|         }); |  | ||||||
| 
 | 
 | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -62,9 +62,7 @@ impl Screen<(), ()> for WelcomeScreen { | ||||||
|                 if let gtk::ResponseType::Accept = response { |                 if let gtk::ResponseType::Accept = response { | ||||||
|                     if let Some(file) = dialog.file() { |                     if let Some(file) = dialog.file() { | ||||||
|                         if let Some(path) = file.path() { |                         if let Some(path) = file.path() { | ||||||
|                             spawn!(@clone this, async move { |                             this.handle.backend.set_music_library_path(path).unwrap(); | ||||||
|                                 this.handle.backend.set_music_library_path(path).await.unwrap(); |  | ||||||
|                             }); |  | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  | @ -55,7 +55,7 @@ impl Screen<Work, ()> for WorkScreen { | ||||||
|             &gettext("Delete work"), |             &gettext("Delete work"), | ||||||
|             clone!(@weak this =>  move || { |             clone!(@weak this =>  move || { | ||||||
|                 spawn!(@clone this, async move { |                 spawn!(@clone this, async move { | ||||||
|                     this.handle.backend.db().delete_work(&this.work.id).await.unwrap(); |                     this.handle.backend.db().delete_work(&this.work.id).unwrap(); | ||||||
|                     this.handle.backend.library_changed(); |                     this.handle.backend.library_changed(); | ||||||
|                 }); |                 }); | ||||||
|             }), |             }), | ||||||
|  | @ -95,27 +95,25 @@ impl Screen<Work, ()> for WorkScreen { | ||||||
|                 search.is_empty() || text.to_lowercase().contains(&search) |                 search.is_empty() || text.to_lowercase().contains(&search) | ||||||
|             })); |             })); | ||||||
| 
 | 
 | ||||||
|         // Load the content asynchronously.
 |         // Load the content.
 | ||||||
| 
 | 
 | ||||||
|         spawn!(@clone this, async move { |         let recordings = this | ||||||
|             let recordings = this.handle |             .handle | ||||||
|                 .backend |             .backend | ||||||
|                 .db() |             .db() | ||||||
|                 .get_recordings_for_work(&this.work.id) |             .get_recordings_for_work(&this.work.id) | ||||||
|                 .await |             .unwrap(); | ||||||
|                 .unwrap(); |  | ||||||
| 
 | 
 | ||||||
|             if !recordings.is_empty() { |         if !recordings.is_empty() { | ||||||
|                 let length = recordings.len(); |             let length = recordings.len(); | ||||||
|                 this.recordings.replace(recordings); |             this.recordings.replace(recordings); | ||||||
|                 this.recording_list.update(length); |             this.recording_list.update(length); | ||||||
| 
 | 
 | ||||||
|                 let section = Section::new("Recordings", &this.recording_list.widget); |             let section = Section::new("Recordings", &this.recording_list.widget); | ||||||
|                 this.widget.add_content(§ion.widget); |             this.widget.add_content(§ion.widget); | ||||||
|             } |         } | ||||||
| 
 | 
 | ||||||
|             this.widget.ready(); |         this.widget.ready(); | ||||||
|         }); |  | ||||||
| 
 | 
 | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -38,12 +38,6 @@ impl Screen<(), Ensemble> for EnsembleSelector { | ||||||
|             }); |             }); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.selector |  | ||||||
|             .set_load_local(clone!(@weak this =>  @default-panic, move || { |  | ||||||
|                 let clone = this; |  | ||||||
|                 async move { clone.handle.backend.db().get_ensembles().await.unwrap() } |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|         this.selector |         this.selector | ||||||
|             .set_make_widget(clone!(@weak this => @default-panic,  move |ensemble| { |             .set_make_widget(clone!(@weak this => @default-panic,  move |ensemble| { | ||||||
|                 let row = adw::ActionRowBuilder::new() |                 let row = adw::ActionRowBuilder::new() | ||||||
|  | @ -62,6 +56,9 @@ impl Screen<(), Ensemble> for EnsembleSelector { | ||||||
|         this.selector |         this.selector | ||||||
|             .set_filter(|search, ensemble| ensemble.name.to_lowercase().contains(search)); |             .set_filter(|search, ensemble| ensemble.name.to_lowercase().contains(search)); | ||||||
| 
 | 
 | ||||||
|  |         this.selector | ||||||
|  |             .set_items(this.handle.backend.db().get_ensembles().unwrap()); | ||||||
|  | 
 | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -38,12 +38,6 @@ impl Screen<(), Instrument> for InstrumentSelector { | ||||||
|             }); |             }); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.selector |  | ||||||
|             .set_load_local(clone!(@weak this =>  @default-panic, move || { |  | ||||||
|                 let clone = this; |  | ||||||
|                 async move { clone.handle.backend.db().get_instruments().await.unwrap() } |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|         this.selector |         this.selector | ||||||
|             .set_make_widget(clone!(@weak this =>  @default-panic, move |instrument| { |             .set_make_widget(clone!(@weak this =>  @default-panic, move |instrument| { | ||||||
|                 let row = adw::ActionRowBuilder::new() |                 let row = adw::ActionRowBuilder::new() | ||||||
|  | @ -62,6 +56,9 @@ impl Screen<(), Instrument> for InstrumentSelector { | ||||||
|         this.selector |         this.selector | ||||||
|             .set_filter(|search, instrument| instrument.name.to_lowercase().contains(search)); |             .set_filter(|search, instrument| instrument.name.to_lowercase().contains(search)); | ||||||
| 
 | 
 | ||||||
|  |         this.selector | ||||||
|  |             .set_items(this.handle.backend.db().get_instruments().unwrap()); | ||||||
|  | 
 | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -28,26 +28,6 @@ impl Screen<(), Medium> for MediumSelector { | ||||||
|             this.handle.pop(None); |             this.handle.pop(None); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.selector |  | ||||||
|             .set_load_local(clone!(@weak this =>  @default-panic, move || { |  | ||||||
|                 async move { |  | ||||||
|                     let mut poes = Vec::new(); |  | ||||||
| 
 |  | ||||||
|                     let persons = this.handle.backend.db().get_persons().await.unwrap(); |  | ||||||
|                     let ensembles = this.handle.backend.db().get_ensembles().await.unwrap(); |  | ||||||
| 
 |  | ||||||
|                     for person in persons { |  | ||||||
|                         poes.push(PersonOrEnsemble::Person(person)); |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     for ensemble in ensembles { |  | ||||||
|                         poes.push(PersonOrEnsemble::Ensemble(ensemble)); |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     poes |  | ||||||
|                 } |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|         this.selector.set_make_widget(clone!(@weak this =>  @default-panic, move |poe| { |         this.selector.set_make_widget(clone!(@weak this =>  @default-panic, move |poe| { | ||||||
|             let row = adw::ActionRowBuilder::new() |             let row = adw::ActionRowBuilder::new() | ||||||
|                 .activatable(true) |                 .activatable(true) | ||||||
|  | @ -70,6 +50,23 @@ impl Screen<(), Medium> for MediumSelector { | ||||||
|         this.selector |         this.selector | ||||||
|             .set_filter(|search, poe| poe.get_title().to_lowercase().contains(search)); |             .set_filter(|search, poe| poe.get_title().to_lowercase().contains(search)); | ||||||
| 
 | 
 | ||||||
|  |         // Initialize items.
 | ||||||
|  | 
 | ||||||
|  |         let mut poes = Vec::new(); | ||||||
|  | 
 | ||||||
|  |         let persons = this.handle.backend.db().get_persons().unwrap(); | ||||||
|  |         let ensembles = this.handle.backend.db().get_ensembles().unwrap(); | ||||||
|  | 
 | ||||||
|  |         for person in persons { | ||||||
|  |             poes.push(PersonOrEnsemble::Person(person)); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         for ensemble in ensembles { | ||||||
|  |             poes.push(PersonOrEnsemble::Ensemble(ensemble)); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         this.selector.set_items(poes); | ||||||
|  | 
 | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -103,25 +100,6 @@ impl Screen<PersonOrEnsemble, Medium> for MediumSelectorMediumScreen { | ||||||
|             this.handle.pop(None); |             this.handle.pop(None); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         match this.poe.clone() { |  | ||||||
|             PersonOrEnsemble::Person(person) => { |  | ||||||
|                 // this.selector.set_load_online(clone!(@weak this =>  move || {
 |  | ||||||
|                 //     async move { this.handle.backend.cl().get_mediums_for_person(&person.id).await }
 |  | ||||||
|                 // }));
 |  | ||||||
| 
 |  | ||||||
|                 this.selector.set_load_local(clone!(@weak this =>  @default-panic, move || { |  | ||||||
|                     let person = person.clone(); |  | ||||||
|                     async move { this.handle.backend.db().get_mediums_for_person(&person.id).await.unwrap() } |  | ||||||
|                 })); |  | ||||||
|             } |  | ||||||
|             PersonOrEnsemble::Ensemble(ensemble) => { |  | ||||||
|                 this.selector.set_load_local(clone!(@weak this =>  @default-panic, move || { |  | ||||||
|                     let ensemble = ensemble.clone(); |  | ||||||
|                     async move { this.handle.backend.db().get_mediums_for_ensemble(&ensemble.id).await.unwrap() } |  | ||||||
|                 })); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         this.selector |         this.selector | ||||||
|             .set_make_widget(clone!(@weak this =>  @default-panic, move |medium| { |             .set_make_widget(clone!(@weak this =>  @default-panic, move |medium| { | ||||||
|                 let row = adw::ActionRowBuilder::new() |                 let row = adw::ActionRowBuilder::new() | ||||||
|  | @ -140,6 +118,28 @@ impl Screen<PersonOrEnsemble, Medium> for MediumSelectorMediumScreen { | ||||||
|         this.selector |         this.selector | ||||||
|             .set_filter(|search, medium| medium.name.to_lowercase().contains(search)); |             .set_filter(|search, medium| medium.name.to_lowercase().contains(search)); | ||||||
| 
 | 
 | ||||||
|  |         // Initialize items.
 | ||||||
|  |         match this.poe.clone() { | ||||||
|  |             PersonOrEnsemble::Person(person) => { | ||||||
|  |                 this.selector.set_items( | ||||||
|  |                     this.handle | ||||||
|  |                         .backend | ||||||
|  |                         .db() | ||||||
|  |                         .get_mediums_for_person(&person.id) | ||||||
|  |                         .unwrap(), | ||||||
|  |                 ); | ||||||
|  |             } | ||||||
|  |             PersonOrEnsemble::Ensemble(ensemble) => { | ||||||
|  |                 this.selector.set_items( | ||||||
|  |                     this.handle | ||||||
|  |                         .backend | ||||||
|  |                         .db() | ||||||
|  |                         .get_mediums_for_ensemble(&ensemble.id) | ||||||
|  |                         .unwrap(), | ||||||
|  |                 ); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -38,12 +38,6 @@ impl Screen<(), Person> for PersonSelector { | ||||||
|             }); |             }); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.selector |  | ||||||
|             .set_load_local(clone!(@weak this =>  @default-panic, move || { |  | ||||||
|                 let clone = this; |  | ||||||
|                 async move { clone.handle.backend.db().get_persons().await.unwrap() } |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|         this.selector |         this.selector | ||||||
|             .set_make_widget(clone!(@weak this =>  @default-panic, move |person| { |             .set_make_widget(clone!(@weak this =>  @default-panic, move |person| { | ||||||
|                 let row = adw::ActionRowBuilder::new() |                 let row = adw::ActionRowBuilder::new() | ||||||
|  | @ -62,6 +56,9 @@ impl Screen<(), Person> for PersonSelector { | ||||||
|         this.selector |         this.selector | ||||||
|             .set_filter(|search, person| person.name_fl().to_lowercase().contains(search)); |             .set_filter(|search, person| person.name_fl().to_lowercase().contains(search)); | ||||||
| 
 | 
 | ||||||
|  |         this.selector | ||||||
|  |             .set_items(this.handle.backend.db().get_persons().unwrap()); | ||||||
|  | 
 | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -50,11 +50,6 @@ impl Screen<(), Recording> for RecordingSelector { | ||||||
|             }); |             }); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.selector |  | ||||||
|             .set_load_local(clone!(@weak this =>  @default-panic, move || { |  | ||||||
|                 async move { this.handle.backend.db().get_persons().await.unwrap() } |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|         this.selector.set_make_widget(clone!(@weak this =>  @default-panic, move |person| { |         this.selector.set_make_widget(clone!(@weak this =>  @default-panic, move |person| { | ||||||
|             let row = adw::ActionRowBuilder::new() |             let row = adw::ActionRowBuilder::new() | ||||||
|                 .activatable(true) |                 .activatable(true) | ||||||
|  | @ -84,6 +79,9 @@ impl Screen<(), Recording> for RecordingSelector { | ||||||
|         this.selector |         this.selector | ||||||
|             .set_filter(|search, person| person.name_fl().to_lowercase().contains(search)); |             .set_filter(|search, person| person.name_fl().to_lowercase().contains(search)); | ||||||
| 
 | 
 | ||||||
|  |         this.selector | ||||||
|  |             .set_items(this.handle.backend.db().get_persons().unwrap()); | ||||||
|  | 
 | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -126,11 +124,6 @@ impl Screen<Person, Work> for RecordingSelectorWorkScreen { | ||||||
|             }); |             }); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.selector |  | ||||||
|             .set_load_local(clone!(@weak this =>  @default-panic, move || { |  | ||||||
|                 async move { this.handle.backend.db().get_works(&this.person.id).await.unwrap() } |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|         this.selector |         this.selector | ||||||
|             .set_make_widget(clone!(@weak this =>  @default-panic, move |work| { |             .set_make_widget(clone!(@weak this =>  @default-panic, move |work| { | ||||||
|                 let row = adw::ActionRowBuilder::new() |                 let row = adw::ActionRowBuilder::new() | ||||||
|  | @ -149,6 +142,9 @@ impl Screen<Person, Work> for RecordingSelectorWorkScreen { | ||||||
|         this.selector |         this.selector | ||||||
|             .set_filter(|search, work| work.title.to_lowercase().contains(search)); |             .set_filter(|search, work| work.title.to_lowercase().contains(search)); | ||||||
| 
 | 
 | ||||||
|  |         this.selector | ||||||
|  |             .set_items(this.handle.backend.db().get_works(&this.person.id).unwrap()); | ||||||
|  | 
 | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -191,10 +187,6 @@ impl Screen<Work, Recording> for RecordingSelectorRecordingScreen { | ||||||
|             }); |             }); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.selector.set_load_local(clone!(@weak this =>  @default-panic, move || { |  | ||||||
|             async move { this.handle.backend.db().get_recordings_for_work(&this.work.id).await.unwrap() } |  | ||||||
|         })); |  | ||||||
| 
 |  | ||||||
|         this.selector |         this.selector | ||||||
|             .set_make_widget(clone!(@weak this =>  @default-panic, move |recording| { |             .set_make_widget(clone!(@weak this =>  @default-panic, move |recording| { | ||||||
|                 let row = adw::ActionRowBuilder::new() |                 let row = adw::ActionRowBuilder::new() | ||||||
|  | @ -214,6 +206,14 @@ impl Screen<Work, Recording> for RecordingSelectorRecordingScreen { | ||||||
|             recording.get_performers().to_lowercase().contains(search) |             recording.get_performers().to_lowercase().contains(search) | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|  |         this.selector.set_items( | ||||||
|  |             this.handle | ||||||
|  |                 .backend | ||||||
|  |                 .db() | ||||||
|  |                 .get_recordings_for_work(&this.work.id) | ||||||
|  |                 .unwrap(), | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -3,8 +3,6 @@ use glib::clone; | ||||||
| use gtk::prelude::*; | use gtk::prelude::*; | ||||||
| use gtk_macros::get_widget; | use gtk_macros::get_widget; | ||||||
| use std::cell::RefCell; | use std::cell::RefCell; | ||||||
| use std::future::Future; |  | ||||||
| use std::pin::Pin; |  | ||||||
| use std::rc::Rc; | use std::rc::Rc; | ||||||
| 
 | 
 | ||||||
| /// A screen that presents a list of items from the library.
 | /// A screen that presents a list of items from the library.
 | ||||||
|  | @ -19,7 +17,6 @@ pub struct Selector<T: 'static> { | ||||||
|     back_cb: RefCell<Option<Box<dyn Fn()>>>, |     back_cb: RefCell<Option<Box<dyn Fn()>>>, | ||||||
|     add_cb: RefCell<Option<Box<dyn Fn()>>>, |     add_cb: RefCell<Option<Box<dyn Fn()>>>, | ||||||
|     make_widget: RefCell<Option<Box<dyn Fn(&T) -> gtk::Widget>>>, |     make_widget: RefCell<Option<Box<dyn Fn(&T) -> gtk::Widget>>>, | ||||||
|     load_local: RefCell<Option<Box<dyn Fn() -> Box<dyn Future<Output = Vec<T>>>>>>, |  | ||||||
|     filter: RefCell<Option<Box<dyn Fn(&str, &T) -> bool>>>, |     filter: RefCell<Option<Box<dyn Fn(&str, &T) -> bool>>>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -53,7 +50,6 @@ impl<T> Selector<T> { | ||||||
|             back_cb: RefCell::new(None), |             back_cb: RefCell::new(None), | ||||||
|             add_cb: RefCell::new(None), |             add_cb: RefCell::new(None), | ||||||
|             make_widget: RefCell::new(None), |             make_widget: RefCell::new(None), | ||||||
|             load_local: RefCell::new(None), |  | ||||||
|             filter: RefCell::new(None), |             filter: RefCell::new(None), | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|  | @ -98,9 +94,6 @@ impl<T> Selector<T> { | ||||||
|                 } |                 } | ||||||
|             })); |             })); | ||||||
| 
 | 
 | ||||||
|         // Initialize
 |  | ||||||
|         this.clone().load_local(); |  | ||||||
| 
 |  | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -125,16 +118,6 @@ impl<T> Selector<T> { | ||||||
|         self.add_cb.replace(Some(Box::new(cb))); |         self.add_cb.replace(Some(Box::new(cb))); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Set the async closure to be called to get local items.
 |  | ||||||
|     pub fn set_load_local<F, R>(&self, cb: F) |  | ||||||
|     where |  | ||||||
|         F: (Fn() -> R) + 'static, |  | ||||||
|         R: Future<Output = Vec<T>> + 'static, |  | ||||||
|     { |  | ||||||
|         self.load_local |  | ||||||
|             .replace(Some(Box::new(move || Box::new(cb())))); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Set the closure to be called for creating a new list row.
 |     /// Set the closure to be called for creating a new list row.
 | ||||||
|     pub fn set_make_widget<F: Fn(&T) -> gtk::Widget + 'static>(&self, make_widget: F) { |     pub fn set_make_widget<F: Fn(&T) -> gtk::Widget + 'static>(&self, make_widget: F) { | ||||||
|         self.make_widget.replace(Some(Box::new(make_widget))); |         self.make_widget.replace(Some(Box::new(make_widget))); | ||||||
|  | @ -146,20 +129,8 @@ impl<T> Selector<T> { | ||||||
|         self.filter.replace(Some(Box::new(filter))); |         self.filter.replace(Some(Box::new(filter))); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn load_local(self: Rc<Self>) { |     /// Set the list items the user may select from.
 | ||||||
|         let context = glib::MainContext::default(); |     pub fn set_items(&self, items: Vec<T>) { | ||||||
|         let clone = self.clone(); |  | ||||||
|         context.spawn_local(async move { |  | ||||||
|             if let Some(cb) = &*self.load_local.borrow() { |  | ||||||
|                 self.stack.set_visible_child_name("loading"); |  | ||||||
| 
 |  | ||||||
|                 let items = Pin::from(cb()).await; |  | ||||||
|                 clone.show_items(items); |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fn show_items(&self, items: Vec<T>) { |  | ||||||
|         let length = items.len(); |         let length = items.len(); | ||||||
|         self.items.replace(items); |         self.items.replace(items); | ||||||
|         self.list.update(length); |         self.list.update(length); | ||||||
|  |  | ||||||
|  | @ -44,11 +44,6 @@ impl Screen<(), Work> for WorkSelector { | ||||||
|             }); |             }); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.selector |  | ||||||
|             .set_load_local(clone!(@weak this =>  @default-panic, move || { |  | ||||||
|                 async move { this.handle.backend.db().get_persons().await.unwrap() } |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|         this.selector.set_make_widget(clone!(@weak this =>  @default-panic, move |person| { |         this.selector.set_make_widget(clone!(@weak this =>  @default-panic, move |person| { | ||||||
|             let row = adw::ActionRowBuilder::new() |             let row = adw::ActionRowBuilder::new() | ||||||
|                 .activatable(true) |                 .activatable(true) | ||||||
|  | @ -74,6 +69,9 @@ impl Screen<(), Work> for WorkSelector { | ||||||
|         this.selector |         this.selector | ||||||
|             .set_filter(|search, person| person.name_fl().to_lowercase().contains(search)); |             .set_filter(|search, person| person.name_fl().to_lowercase().contains(search)); | ||||||
| 
 | 
 | ||||||
|  |         this.selector | ||||||
|  |             .set_items(this.handle.backend.db().get_persons().unwrap()); | ||||||
|  | 
 | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -116,11 +114,6 @@ impl Screen<Person, Work> for WorkSelectorWorkScreen { | ||||||
|             }); |             }); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         this.selector |  | ||||||
|             .set_load_local(clone!(@weak this =>  @default-panic, move || { |  | ||||||
|                 async move { this.handle.backend.db().get_works(&this.person.id).await.unwrap() } |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|         this.selector |         this.selector | ||||||
|             .set_make_widget(clone!(@weak this =>  @default-panic, move |work| { |             .set_make_widget(clone!(@weak this =>  @default-panic, move |work| { | ||||||
|                 let row = adw::ActionRowBuilder::new() |                 let row = adw::ActionRowBuilder::new() | ||||||
|  | @ -139,6 +132,9 @@ impl Screen<Person, Work> for WorkSelectorWorkScreen { | ||||||
|         this.selector |         this.selector | ||||||
|             .set_filter(|search, work| work.title.to_lowercase().contains(search)); |             .set_filter(|search, work| work.title.to_lowercase().contains(search)); | ||||||
| 
 | 
 | ||||||
|  |         this.selector | ||||||
|  |             .set_items(this.handle.backend.db().get_works(&this.person.id).unwrap()); | ||||||
|  | 
 | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -70,11 +70,6 @@ impl Editor { | ||||||
|         self.save_button.connect_clicked(move |_| cb()); |         self.save_button.connect_clicked(move |_| cb()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Show a loading page.
 |  | ||||||
|     pub fn loading(&self) { |  | ||||||
|         self.widget.set_visible_child_name("loading"); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Show an error page. The page contains a button to get back to the
 |     /// Show an error page. The page contains a button to get back to the
 | ||||||
|     /// actual editor.
 |     /// actual editor.
 | ||||||
|     pub fn error(&self, title: &str, description: &str) { |     pub fn error(&self, title: &str, description: &str) { | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| use crate::navigator::Navigator; | use crate::navigator::Navigator; | ||||||
| use crate::screens::{MainScreen, WelcomeScreen}; | use crate::screens::{MainScreen, WelcomeScreen}; | ||||||
|  | use glib::clone; | ||||||
| use gtk::prelude::*; | use gtk::prelude::*; | ||||||
| use musicus_backend::{Backend, BackendState}; | use musicus_backend::{Backend, BackendState}; | ||||||
| use std::rc::Rc; | use std::rc::Rc; | ||||||
|  | @ -50,21 +51,17 @@ impl Window { | ||||||
|             navigator, |             navigator, | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         spawn!(@clone this, async move { |         // Listen for backend state changes.
 | ||||||
|             while let Ok(state) = this.backend.next_state().await { |         this.backend.set_state_cb(clone!(@weak this => move |state| { | ||||||
|                 match state { |             match state { | ||||||
|                     BackendState::Loading => this.navigator.reset(), |                 BackendState::Loading => this.navigator.reset(), | ||||||
|                     BackendState::NoMusicLibrary => this.show_welcome_screen(), |                 BackendState::NoMusicLibrary => this.show_welcome_screen(), | ||||||
|                     BackendState::Ready => this.show_main_screen(), |                 BackendState::Ready => this.show_main_screen(), | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|         }); |         })); | ||||||
| 
 | 
 | ||||||
|         spawn!(@clone this, async move { |         // Initialize the backend.
 | ||||||
|             // This is not done in the async block above, because backend state changes may happen
 |         this.backend.init().unwrap(); | ||||||
|             // while this method is running.
 |  | ||||||
|             this.backend.init().await.unwrap(); |  | ||||||
|         }); |  | ||||||
| 
 | 
 | ||||||
|         this |         this | ||||||
|     } |     } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue