mirror of
				https://github.com/johrpan/musicus.git
				synced 2025-10-26 11:47:25 +01:00 
			
		
		
		
	Make music library path changable
This commit is contained in:
		
							parent
							
								
									cf96792029
								
							
						
					
					
						commit
						543f5ed1fd
					
				
					 5 changed files with 480 additions and 236 deletions
				
			
		
							
								
								
									
										240
									
								
								res/ui/window.ui
									
										
									
									
									
								
							
							
						
						
									
										240
									
								
								res/ui/window.ui
									
										
									
									
									
								
							|  | @ -88,57 +88,21 @@ | ||||||
|     <property name="default-width">800</property> |     <property name="default-width">800</property> | ||||||
|     <property name="default-height">566</property> |     <property name="default-height">566</property> | ||||||
|     <child> |     <child> | ||||||
|       <object class="HdyLeaflet" id="leaflet"> |       <object class="GtkStack" id="stack"> | ||||||
|         <property name="visible">True</property> |         <property name="visible">True</property> | ||||||
|         <property name="can-focus">False</property> |         <property name="can-focus">False</property> | ||||||
|         <property name="visible-child">sidebar_box</property> |         <property name="transition-type">crossfade</property> | ||||||
|         <property name="can-swipe-back">True</property> |  | ||||||
|         <child> |         <child> | ||||||
|           <object class="GtkBox" id="sidebar_box"> |           <object class="GtkBox"> | ||||||
|             <property name="width-request">250</property> |  | ||||||
|             <property name="visible">True</property> |             <property name="visible">True</property> | ||||||
|             <property name="can-focus">False</property> |             <property name="can-focus">False</property> | ||||||
|             <property name="hexpand">False</property> |  | ||||||
|             <property name="orientation">vertical</property> |             <property name="orientation">vertical</property> | ||||||
|             <child> |             <child> | ||||||
|               <object class="HdyHeaderBar"> |               <object class="HdyHeaderBar"> | ||||||
|                 <property name="visible">True</property> |                 <property name="visible">True</property> | ||||||
|                 <property name="can-focus">False</property> |                 <property name="can-focus">False</property> | ||||||
|                 <property name="show-close-button" bind-source="leaflet" bind-property="folded" bind-flags="sync-create">False</property> |                 <property name="title" translatable="yes">Musicus Editor</property> | ||||||
|                 <child> |                 <property name="show-close-button">True</property> | ||||||
|                   <object class="GtkButton" id="add_button"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">True</property> |  | ||||||
|                     <property name="receives-default">True</property> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkImage"> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">False</property> |  | ||||||
|                         <property name="icon-name">list-add-symbolic</property> |  | ||||||
|                       </object> |  | ||||||
|                     </child> |  | ||||||
|                   </object> |  | ||||||
|                 </child> |  | ||||||
|                 <child> |  | ||||||
|                   <object class="GtkMenuButton"> |  | ||||||
|                     <property name="visible">True</property> |  | ||||||
|                     <property name="can-focus">True</property> |  | ||||||
|                     <property name="focus-on-click">False</property> |  | ||||||
|                     <property name="receives-default">True</property> |  | ||||||
|                     <property name="menu-model">menu</property> |  | ||||||
|                     <child> |  | ||||||
|                       <object class="GtkImage"> |  | ||||||
|                         <property name="visible">True</property> |  | ||||||
|                         <property name="can-focus">False</property> |  | ||||||
|                         <property name="icon-name">open-menu-symbolic</property> |  | ||||||
|                       </object> |  | ||||||
|                     </child> |  | ||||||
|                   </object> |  | ||||||
|                   <packing> |  | ||||||
|                     <property name="pack-type">end</property> |  | ||||||
|                     <property name="position">1</property> |  | ||||||
|                   </packing> |  | ||||||
|                 </child> |  | ||||||
|               </object> |               </object> | ||||||
|               <packing> |               <packing> | ||||||
|                 <property name="expand">False</property> |                 <property name="expand">False</property> | ||||||
|  | @ -146,26 +110,206 @@ | ||||||
|                 <property name="position">0</property> |                 <property name="position">0</property> | ||||||
|               </packing> |               </packing> | ||||||
|             </child> |             </child> | ||||||
|  |             <child> | ||||||
|  |               <object class="GtkBox"> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">False</property> | ||||||
|  |                 <property name="halign">center</property> | ||||||
|  |                 <property name="valign">center</property> | ||||||
|  |                 <property name="orientation">vertical</property> | ||||||
|  |                 <property name="spacing">18</property> | ||||||
|  |                 <property name="border-width">18</property> | ||||||
|  |                 <child> | ||||||
|  |                   <object class="GtkImage"> | ||||||
|  |                     <property name="visible">True</property> | ||||||
|  |                     <property name="can-focus">False</property> | ||||||
|  |                     <property name="opacity">0.5019607843137255</property> | ||||||
|  |                     <property name="pixel-size">80</property> | ||||||
|  |                     <property name="icon-name">folder-music-symbolic</property> | ||||||
|  |                   </object> | ||||||
|  |                   <packing> | ||||||
|  |                     <property name="expand">False</property> | ||||||
|  |                     <property name="fill">True</property> | ||||||
|  |                     <property name="position">0</property> | ||||||
|  |                   </packing> | ||||||
|  |                 </child> | ||||||
|  |                 <child> | ||||||
|  |                   <object class="GtkLabel"> | ||||||
|  |                     <property name="visible">True</property> | ||||||
|  |                     <property name="can-focus">False</property> | ||||||
|  |                     <property name="opacity">0.5019607843137255</property> | ||||||
|  |                     <property name="label" translatable="yes">Welcome to Musicus Editor!</property> | ||||||
|  |                     <attributes> | ||||||
|  |                       <attribute name="size" value="16384" /> | ||||||
|  |                     </attributes> | ||||||
|  |                   </object> | ||||||
|  |                   <packing> | ||||||
|  |                     <property name="expand">False</property> | ||||||
|  |                     <property name="fill">True</property> | ||||||
|  |                     <property name="position">1</property> | ||||||
|  |                   </packing> | ||||||
|  |                 </child> | ||||||
|  |                 <child> | ||||||
|  |                   <object class="GtkLabel"> | ||||||
|  |                     <property name="visible">True</property> | ||||||
|  |                     <property name="can-focus">False</property> | ||||||
|  |                     <property name="opacity">0.5019607843137255</property> | ||||||
|  |                     <property name="label" translatable="yes">Get startet by selecting the folder containing your music files! Musicus will create a new database there or open one that already exists.</property> | ||||||
|  |                     <property name="justify">center</property> | ||||||
|  |                     <property name="wrap">True</property> | ||||||
|  |                     <property name="max-width-chars">40</property> | ||||||
|  |                   </object> | ||||||
|  |                   <packing> | ||||||
|  |                     <property name="expand">False</property> | ||||||
|  |                     <property name="fill">True</property> | ||||||
|  |                     <property name="position">2</property> | ||||||
|  |                   </packing> | ||||||
|  |                 </child> | ||||||
|  |                 <child> | ||||||
|  |                   <object class="GtkButton" id="select_music_library_path_button"> | ||||||
|  |                     <property name="label" translatable="yes">Select folder</property> | ||||||
|  |                     <property name="visible">True</property> | ||||||
|  |                     <property name="can-focus">True</property> | ||||||
|  |                     <property name="receives-default">True</property> | ||||||
|  |                     <property name="halign">center</property> | ||||||
|  |                     <style> | ||||||
|  |                       <class name="suggested-action" /> | ||||||
|  |                     </style> | ||||||
|  |                   </object> | ||||||
|  |                   <packing> | ||||||
|  |                     <property name="expand">False</property> | ||||||
|  |                     <property name="fill">True</property> | ||||||
|  |                     <property name="position">3</property> | ||||||
|  |                   </packing> | ||||||
|  |                 </child> | ||||||
|  |               </object> | ||||||
|  |               <packing> | ||||||
|  |                 <property name="expand">True</property> | ||||||
|  |                 <property name="fill">True</property> | ||||||
|  |                 <property name="position">1</property> | ||||||
|  |               </packing> | ||||||
|  |             </child> | ||||||
|           </object> |           </object> | ||||||
|           <packing> |           <packing> | ||||||
|             <property name="name">sidebar</property> |             <property name="name">empty</property> | ||||||
|           </packing> |           </packing> | ||||||
|         </child> |         </child> | ||||||
|         <child> |         <child> | ||||||
|           <object class="GtkSeparator"> |           <object class="GtkBox"> | ||||||
|             <property name="visible">True</property> |             <property name="visible">True</property> | ||||||
|             <property name="can-focus">False</property> |             <property name="can-focus">False</property> | ||||||
|             <property name="orientation">vertical</property> |             <property name="orientation">vertical</property> | ||||||
|             <style> |             <child> | ||||||
|               <class name="sidebar" /> |               <object class="HdyHeaderBar"> | ||||||
|             </style> |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">False</property> | ||||||
|  |                 <property name="title" translatable="yes">Musicus Editor</property> | ||||||
|  |                 <property name="show-close-button">True</property> | ||||||
|  |               </object> | ||||||
|  |               <packing> | ||||||
|  |                 <property name="expand">False</property> | ||||||
|  |                 <property name="fill">True</property> | ||||||
|  |                 <property name="position">0</property> | ||||||
|  |               </packing> | ||||||
|  |             </child> | ||||||
|  |             <child> | ||||||
|  |               <object class="GtkSpinner"> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">False</property> | ||||||
|  |                 <property name="active">True</property> | ||||||
|  |               </object> | ||||||
|  |               <packing> | ||||||
|  |                 <property name="expand">True</property> | ||||||
|  |                 <property name="fill">True</property> | ||||||
|  |                 <property name="position">1</property> | ||||||
|  |               </packing> | ||||||
|  |             </child> | ||||||
|           </object> |           </object> | ||||||
|           <packing> |           <packing> | ||||||
|             <property name="navigatable">False</property> |             <property name="name">loading</property> | ||||||
|  |             <property name="position">1</property> | ||||||
|           </packing> |           </packing> | ||||||
|         </child> |         </child> | ||||||
|         <child> |         <child> | ||||||
|           <placeholder /> |           <object class="HdyLeaflet" id="leaflet"> | ||||||
|  |             <property name="visible">True</property> | ||||||
|  |             <property name="can-focus">False</property> | ||||||
|  |             <property name="can-swipe-back">True</property> | ||||||
|  |             <child> | ||||||
|  |               <object class="GtkBox" id="sidebar_box"> | ||||||
|  |                 <property name="width-request">250</property> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">False</property> | ||||||
|  |                 <property name="hexpand">False</property> | ||||||
|  |                 <property name="orientation">vertical</property> | ||||||
|  |                 <child> | ||||||
|  |                   <object class="HdyHeaderBar"> | ||||||
|  |                     <property name="visible">True</property> | ||||||
|  |                     <property name="can-focus">False</property> | ||||||
|  |                     <child> | ||||||
|  |                       <object class="GtkButton" id="add_button"> | ||||||
|  |                         <property name="visible">True</property> | ||||||
|  |                         <property name="can-focus">True</property> | ||||||
|  |                         <property name="receives-default">True</property> | ||||||
|  |                         <child> | ||||||
|  |                           <object class="GtkImage"> | ||||||
|  |                             <property name="visible">True</property> | ||||||
|  |                             <property name="can-focus">False</property> | ||||||
|  |                             <property name="icon-name">list-add-symbolic</property> | ||||||
|  |                           </object> | ||||||
|  |                         </child> | ||||||
|  |                       </object> | ||||||
|  |                     </child> | ||||||
|  |                     <child> | ||||||
|  |                       <object class="GtkMenuButton"> | ||||||
|  |                         <property name="visible">True</property> | ||||||
|  |                         <property name="can-focus">True</property> | ||||||
|  |                         <property name="focus-on-click">False</property> | ||||||
|  |                         <property name="receives-default">True</property> | ||||||
|  |                         <property name="menu-model">menu</property> | ||||||
|  |                         <child> | ||||||
|  |                           <object class="GtkImage"> | ||||||
|  |                             <property name="visible">True</property> | ||||||
|  |                             <property name="can-focus">False</property> | ||||||
|  |                             <property name="icon-name">open-menu-symbolic</property> | ||||||
|  |                           </object> | ||||||
|  |                         </child> | ||||||
|  |                       </object> | ||||||
|  |                       <packing> | ||||||
|  |                         <property name="pack-type">end</property> | ||||||
|  |                         <property name="position">1</property> | ||||||
|  |                       </packing> | ||||||
|  |                     </child> | ||||||
|  |                   </object> | ||||||
|  |                   <packing> | ||||||
|  |                     <property name="expand">False</property> | ||||||
|  |                     <property name="fill">True</property> | ||||||
|  |                     <property name="position">0</property> | ||||||
|  |                   </packing> | ||||||
|  |                 </child> | ||||||
|  |               </object> | ||||||
|  |               <packing> | ||||||
|  |                 <property name="name">sidebar</property> | ||||||
|  |               </packing> | ||||||
|  |             </child> | ||||||
|  |             <child> | ||||||
|  |               <object class="GtkSeparator"> | ||||||
|  |                 <property name="visible">True</property> | ||||||
|  |                 <property name="can-focus">False</property> | ||||||
|  |                 <property name="orientation">vertical</property> | ||||||
|  |                 <style> | ||||||
|  |                   <class name="sidebar" /> | ||||||
|  |                 </style> | ||||||
|  |               </object> | ||||||
|  |               <packing> | ||||||
|  |                 <property name="navigatable">False</property> | ||||||
|  |               </packing> | ||||||
|  |             </child> | ||||||
|  |           </object> | ||||||
|  |           <packing> | ||||||
|  |             <property name="name">content</property> | ||||||
|  |             <property name="position">2</property> | ||||||
|  |           </packing> | ||||||
|         </child> |         </child> | ||||||
|       </object> |       </object> | ||||||
|     </child> |     </child> | ||||||
|  |  | ||||||
							
								
								
									
										385
									
								
								src/backend.rs
									
										
									
									
									
								
							
							
						
						
									
										385
									
								
								src/backend.rs
									
										
									
									
									
								
							|  | @ -1,10 +1,16 @@ | ||||||
| use super::database::*; | use super::database::*; | ||||||
| use anyhow::Result; | use anyhow::{anyhow, Result}; | ||||||
| use futures_channel::oneshot; |  | ||||||
| use futures_channel::oneshot::Sender; | use futures_channel::oneshot::Sender; | ||||||
|  | use futures_channel::{mpsc, oneshot}; | ||||||
| use std::cell::RefCell; | use std::cell::RefCell; | ||||||
| use std::path::PathBuf; | use std::path::PathBuf; | ||||||
| 
 | 
 | ||||||
|  | pub enum BackendState { | ||||||
|  |     NoMusicLibrary, | ||||||
|  |     Loading, | ||||||
|  |     Ready, | ||||||
|  | } | ||||||
|  | 
 | ||||||
| enum BackendAction { | enum BackendAction { | ||||||
|     UpdatePerson(Person, Sender<Result<()>>), |     UpdatePerson(Person, Sender<Result<()>>), | ||||||
|     GetPerson(i64, Sender<Result<Person>>), |     GetPerson(i64, Sender<Result<Person>>), | ||||||
|  | @ -28,24 +34,229 @@ enum BackendAction { | ||||||
|     GetRecordingsForPerson(i64, Sender<Result<Vec<RecordingDescription>>>), |     GetRecordingsForPerson(i64, Sender<Result<Vec<RecordingDescription>>>), | ||||||
|     GetRecordingsForEnsemble(i64, Sender<Result<Vec<RecordingDescription>>>), |     GetRecordingsForEnsemble(i64, Sender<Result<Vec<RecordingDescription>>>), | ||||||
|     GetRecordingsForWork(i64, Sender<Result<Vec<RecordingDescription>>>), |     GetRecordingsForWork(i64, Sender<Result<Vec<RecordingDescription>>>), | ||||||
|  |     Stop, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| use BackendAction::*; | use BackendAction::*; | ||||||
| 
 | 
 | ||||||
| pub struct Backend { | pub struct Backend { | ||||||
|     action_sender: std::sync::mpsc::Sender<BackendAction>, |     pub state_stream: RefCell<mpsc::Receiver<BackendState>>, | ||||||
|  |     state_sender: RefCell<mpsc::Sender<BackendState>>, | ||||||
|  |     action_sender: RefCell<Option<std::sync::mpsc::Sender<BackendAction>>>, | ||||||
|     music_library_path: RefCell<Option<PathBuf>>, |     music_library_path: RefCell<Option<PathBuf>>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl Backend { | impl Backend { | ||||||
|     pub fn new(url: &str, music_library_path: PathBuf) -> Self { |     pub fn new() -> Self { | ||||||
|         let url = url.to_string(); |         let (state_sender, state_stream) = mpsc::channel(1024); | ||||||
| 
 | 
 | ||||||
|  |         Backend { | ||||||
|  |             state_stream: RefCell::new(state_stream), | ||||||
|  |             state_sender: RefCell::new(state_sender), | ||||||
|  |             action_sender: RefCell::new(None), | ||||||
|  |             music_library_path: RefCell::new(None), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn update_person(&self, person: Person) -> Result<()> { | ||||||
|  |         let (sender, receiver) = oneshot::channel(); | ||||||
|  |         self.unwrap_action_sender()? | ||||||
|  |             .send(UpdatePerson(person, sender))?; | ||||||
|  |         receiver.await? | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn get_person(&self, id: i64) -> Result<Person> { | ||||||
|  |         let (sender, receiver) = oneshot::channel(); | ||||||
|  |         self.unwrap_action_sender()?.send(GetPerson(id, sender))?; | ||||||
|  |         receiver.await? | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn delete_person(&self, id: i64) -> Result<()> { | ||||||
|  |         let (sender, receiver) = oneshot::channel(); | ||||||
|  |         self.unwrap_action_sender()? | ||||||
|  |             .send(DeletePerson(id, sender))?; | ||||||
|  |         receiver.await? | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn get_persons(&self) -> Result<Vec<Person>> { | ||||||
|  |         let (sender, receiver) = oneshot::channel(); | ||||||
|  |         self.unwrap_action_sender()?.send(GetPersons(sender))?; | ||||||
|  |         receiver.await? | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn update_instrument(&self, instrument: Instrument) -> Result<()> { | ||||||
|  |         let (sender, receiver) = oneshot::channel(); | ||||||
|  |         self.unwrap_action_sender()? | ||||||
|  |             .send(UpdateInstrument(instrument, sender))?; | ||||||
|  |         receiver.await? | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn get_instrument(&self, id: i64) -> Result<Instrument> { | ||||||
|  |         let (sender, receiver) = oneshot::channel(); | ||||||
|  |         self.unwrap_action_sender()? | ||||||
|  |             .send(GetInstrument(id, sender))?; | ||||||
|  |         receiver.await? | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn delete_instrument(&self, id: i64) -> Result<()> { | ||||||
|  |         let (sender, receiver) = oneshot::channel(); | ||||||
|  |         self.unwrap_action_sender()? | ||||||
|  |             .send(DeleteInstrument(id, sender))?; | ||||||
|  |         receiver.await? | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn get_instruments(&self) -> Result<Vec<Instrument>> { | ||||||
|  |         let (sender, receiver) = oneshot::channel(); | ||||||
|  |         self.unwrap_action_sender()?.send(GetInstruments(sender))?; | ||||||
|  |         receiver.await? | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn update_work(&self, work_insertion: WorkInsertion) -> Result<()> { | ||||||
|  |         let (sender, receiver) = oneshot::channel(); | ||||||
|  |         self.unwrap_action_sender()? | ||||||
|  |             .send(UpdateWork(work_insertion, sender))?; | ||||||
|  |         receiver.await? | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn get_work_description(&self, id: i64) -> Result<WorkDescription> { | ||||||
|  |         let (sender, receiver) = oneshot::channel(); | ||||||
|  |         self.unwrap_action_sender()? | ||||||
|  |             .send(GetWorkDescription(id, sender))?; | ||||||
|  |         receiver.await? | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn delete_work(&self, id: i64) -> Result<()> { | ||||||
|  |         let (sender, receiver) = oneshot::channel(); | ||||||
|  |         self.unwrap_action_sender()?.send(DeleteWork(id, sender))?; | ||||||
|  |         receiver.await? | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn get_work_descriptions(&self, person_id: i64) -> Result<Vec<WorkDescription>> { | ||||||
|  |         let (sender, receiver) = oneshot::channel(); | ||||||
|  |         self.unwrap_action_sender()? | ||||||
|  |             .send(GetWorkDescriptions(person_id, sender))?; | ||||||
|  |         receiver.await? | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn update_ensemble(&self, ensemble: Ensemble) -> Result<()> { | ||||||
|  |         let (sender, receiver) = oneshot::channel(); | ||||||
|  |         self.unwrap_action_sender()? | ||||||
|  |             .send(UpdateEnsemble(ensemble, sender))?; | ||||||
|  |         receiver.await? | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn get_ensemble(&self, id: i64) -> Result<Ensemble> { | ||||||
|  |         let (sender, receiver) = oneshot::channel(); | ||||||
|  |         self.unwrap_action_sender()?.send(GetEnsemble(id, sender))?; | ||||||
|  |         receiver.await? | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn delete_ensemble(&self, id: i64) -> Result<()> { | ||||||
|  |         let (sender, receiver) = oneshot::channel(); | ||||||
|  |         self.unwrap_action_sender()? | ||||||
|  |             .send(DeleteEnsemble(id, sender))?; | ||||||
|  |         receiver.await? | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn get_ensembles(&self) -> Result<Vec<Ensemble>> { | ||||||
|  |         let (sender, receiver) = oneshot::channel(); | ||||||
|  |         self.unwrap_action_sender()?.send(GetEnsembles(sender))?; | ||||||
|  |         receiver.await? | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn update_recording(&self, recording_insertion: RecordingInsertion) -> Result<()> { | ||||||
|  |         let (sender, receiver) = oneshot::channel(); | ||||||
|  |         self.unwrap_action_sender()? | ||||||
|  |             .send(UpdateRecording(recording_insertion, sender))?; | ||||||
|  |         receiver.await? | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn get_recording_description(&self, id: i64) -> Result<RecordingDescription> { | ||||||
|  |         let (sender, receiver) = oneshot::channel(); | ||||||
|  |         self.unwrap_action_sender()? | ||||||
|  |             .send(GetRecordingDescription(id, sender))?; | ||||||
|  |         receiver.await? | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn delete_recording(&self, id: i64) -> Result<()> { | ||||||
|  |         let (sender, receiver) = oneshot::channel(); | ||||||
|  |         self.unwrap_action_sender()? | ||||||
|  |             .send(DeleteRecording(id, sender))?; | ||||||
|  |         receiver.await? | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn get_recordings_for_person( | ||||||
|  |         &self, | ||||||
|  |         person_id: i64, | ||||||
|  |     ) -> Result<Vec<RecordingDescription>> { | ||||||
|  |         let (sender, receiver) = oneshot::channel(); | ||||||
|  |         self.unwrap_action_sender()? | ||||||
|  |             .send(GetRecordingsForPerson(person_id, sender))?; | ||||||
|  |         receiver.await? | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn get_recordings_for_ensemble( | ||||||
|  |         &self, | ||||||
|  |         ensemble_id: i64, | ||||||
|  |     ) -> Result<Vec<RecordingDescription>> { | ||||||
|  |         let (sender, receiver) = oneshot::channel(); | ||||||
|  |         self.unwrap_action_sender()? | ||||||
|  |             .send(GetRecordingsForEnsemble(ensemble_id, sender))?; | ||||||
|  |         receiver.await? | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn get_recordings_for_work(&self, work_id: i64) -> Result<Vec<RecordingDescription>> { | ||||||
|  |         let (sender, receiver) = oneshot::channel(); | ||||||
|  |         self.unwrap_action_sender()? | ||||||
|  |             .send(GetRecordingsForWork(work_id, sender))?; | ||||||
|  |         receiver.await? | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub async fn set_music_library_path(&self, path: PathBuf) -> Result<()> { | ||||||
|  |         self.music_library_path.replace(Some(path.clone())); | ||||||
|  |         self.set_state(BackendState::Loading); | ||||||
|  | 
 | ||||||
|  |         if let Some(action_sender) = self.action_sender.borrow_mut().take() { | ||||||
|  |             action_sender.send(Stop)?; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let mut db_path = path.clone(); | ||||||
|  |         db_path.push("musicus.db"); | ||||||
|  | 
 | ||||||
|  |         self.start_db_thread(String::from(db_path.to_str().unwrap())) | ||||||
|  |             .await?; | ||||||
|  | 
 | ||||||
|  |         self.set_state(BackendState::Ready); | ||||||
|  | 
 | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn get_music_library_path(&self) -> Option<PathBuf> { | ||||||
|  |         self.music_library_path.borrow().clone() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn set_state(&self, state: BackendState) { | ||||||
|  |         self.state_sender.borrow_mut().try_send(state).unwrap(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn unwrap_action_sender(&self) -> Result<std::sync::mpsc::Sender<BackendAction>> { | ||||||
|  |         match &*self.action_sender.borrow() { | ||||||
|  |             Some(action_sender) => Ok(action_sender.clone()), | ||||||
|  |             None => Err(anyhow!("Database thread is not running!")), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async fn start_db_thread(&self, url: String) -> Result<()> { | ||||||
|  |         let (ready_sender, ready_receiver) = oneshot::channel(); | ||||||
|         let (action_sender, action_receiver) = std::sync::mpsc::channel::<BackendAction>(); |         let (action_sender, action_receiver) = std::sync::mpsc::channel::<BackendAction>(); | ||||||
| 
 | 
 | ||||||
|         std::thread::spawn(move || { |         std::thread::spawn(move || { | ||||||
|             let db = Database::new(&url).expect("Failed to open database!"); |             let db = Database::new(&url).expect("Failed to open database!"); | ||||||
| 
 | 
 | ||||||
|  |             ready_sender | ||||||
|  |                 .send(()) | ||||||
|  |                 .expect("Failed to communicate to main thread!"); | ||||||
|  | 
 | ||||||
|             for action in action_receiver { |             for action in action_receiver { | ||||||
|                 match action { |                 match action { | ||||||
|                     UpdatePerson(person, sender) => { |                     UpdatePerson(person, sender) => { | ||||||
|  | @ -158,167 +369,15 @@ impl Backend { | ||||||
|                             .send(db.get_recordings_for_work(id)) |                             .send(db.get_recordings_for_work(id)) | ||||||
|                             .expect("Failed to send result from database thread!"); |                             .expect("Failed to send result from database thread!"); | ||||||
|                     } |                     } | ||||||
|  |                     Stop => { | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         Backend { |         ready_receiver.await?; | ||||||
|             action_sender: action_sender, |         self.action_sender.replace(Some(action_sender)); | ||||||
|             music_library_path: RefCell::new(Some(music_library_path)), |         Ok(()) | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub async fn update_person(&self, person: Person) -> Result<()> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender.send(UpdatePerson(person, sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub async fn get_person(&self, id: i64) -> Result<Person> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender.send(GetPerson(id, sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub async fn delete_person(&self, id: i64) -> Result<()> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender.send(DeletePerson(id, sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub async fn get_persons(&self) -> Result<Vec<Person>> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender.send(GetPersons(sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub async fn update_instrument(&self, instrument: Instrument) -> Result<()> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender |  | ||||||
|             .send(UpdateInstrument(instrument, sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub async fn get_instrument(&self, id: i64) -> Result<Instrument> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender.send(GetInstrument(id, sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub async fn delete_instrument(&self, id: i64) -> Result<()> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender.send(DeleteInstrument(id, sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub async fn get_instruments(&self) -> Result<Vec<Instrument>> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender.send(GetInstruments(sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub async fn update_work(&self, work_insertion: WorkInsertion) -> Result<()> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender |  | ||||||
|             .send(UpdateWork(work_insertion, sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub async fn get_work_description(&self, id: i64) -> Result<WorkDescription> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender.send(GetWorkDescription(id, sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub async fn delete_work(&self, id: i64) -> Result<()> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender.send(DeleteWork(id, sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub async fn get_work_descriptions(&self, person_id: i64) -> Result<Vec<WorkDescription>> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender |  | ||||||
|             .send(GetWorkDescriptions(person_id, sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub async fn update_ensemble(&self, ensemble: Ensemble) -> Result<()> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender.send(UpdateEnsemble(ensemble, sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub async fn get_ensemble(&self, id: i64) -> Result<Ensemble> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender.send(GetEnsemble(id, sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub async fn delete_ensemble(&self, id: i64) -> Result<()> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender.send(DeleteEnsemble(id, sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub async fn get_ensembles(&self) -> Result<Vec<Ensemble>> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender.send(GetEnsembles(sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub async fn update_recording(&self, recording_insertion: RecordingInsertion) -> Result<()> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender |  | ||||||
|             .send(UpdateRecording(recording_insertion, sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub async fn get_recording_description(&self, id: i64) -> Result<RecordingDescription> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender |  | ||||||
|             .send(GetRecordingDescription(id, sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub async fn delete_recording(&self, id: i64) -> Result<()> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender.send(DeleteRecording(id, sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub async fn get_recordings_for_person( |  | ||||||
|         &self, |  | ||||||
|         person_id: i64, |  | ||||||
|     ) -> Result<Vec<RecordingDescription>> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender |  | ||||||
|             .send(GetRecordingsForPerson(person_id, sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub async fn get_recordings_for_ensemble( |  | ||||||
|         &self, |  | ||||||
|         ensemble_id: i64, |  | ||||||
|     ) -> Result<Vec<RecordingDescription>> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender |  | ||||||
|             .send(GetRecordingsForEnsemble(ensemble_id, sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub async fn get_recordings_for_work(&self, work_id: i64) -> Result<Vec<RecordingDescription>> { |  | ||||||
|         let (sender, receiver) = oneshot::channel(); |  | ||||||
|         self.action_sender |  | ||||||
|             .send(GetRecordingsForWork(work_id, sender))?; |  | ||||||
|         receiver.await? |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub fn set_music_library_path(&self, path: PathBuf) { |  | ||||||
|         self.music_library_path.replace(Some(path.clone())); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub fn get_music_library_path(&self) -> Option<PathBuf> { |  | ||||||
|         self.music_library_path.borrow().clone() |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -29,7 +29,12 @@ impl Preferences { | ||||||
|             if let gtk::ResponseType::Accept = dialog.run() { |             if let gtk::ResponseType::Accept = dialog.run() { | ||||||
|                 if let Some(path) = dialog.get_filename() { |                 if let Some(path) = dialog.get_filename() { | ||||||
|                     music_library_path_row.set_subtitle(Some(path.to_str().unwrap())); |                     music_library_path_row.set_subtitle(Some(path.to_str().unwrap())); | ||||||
|                     backend.set_music_library_path(path); |                     
 | ||||||
|  |                     let context = glib::MainContext::default(); | ||||||
|  |                     let backend = backend.clone(); | ||||||
|  |                     context.spawn_local(async move { | ||||||
|  |                         backend.set_music_library_path(path).await.unwrap(); | ||||||
|  |                     }); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         })); |         })); | ||||||
|  |  | ||||||
|  | @ -64,8 +64,6 @@ impl PoeList { | ||||||
|             result.list.invalidate_filter(); |             result.list.invalidate_filter(); | ||||||
|         })); |         })); | ||||||
| 
 | 
 | ||||||
|         result.clone().reload(); |  | ||||||
| 
 |  | ||||||
|         result |         result | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ use crate::backend::*; | ||||||
| use crate::dialogs::*; | use crate::dialogs::*; | ||||||
| use crate::screens::*; | use crate::screens::*; | ||||||
| use crate::widgets::*; | use crate::widgets::*; | ||||||
|  | use futures::prelude::*; | ||||||
| use gio::prelude::*; | use gio::prelude::*; | ||||||
| use glib::clone; | use glib::clone; | ||||||
| use gtk::prelude::*; | use gtk::prelude::*; | ||||||
|  | @ -12,6 +13,7 @@ use std::rc::Rc; | ||||||
| pub struct Window { | pub struct Window { | ||||||
|     backend: Rc<Backend>, |     backend: Rc<Backend>, | ||||||
|     window: libhandy::ApplicationWindow, |     window: libhandy::ApplicationWindow, | ||||||
|  |     stack: gtk::Stack, | ||||||
|     leaflet: libhandy::Leaflet, |     leaflet: libhandy::Leaflet, | ||||||
|     sidebar_box: gtk::Box, |     sidebar_box: gtk::Box, | ||||||
|     poe_list: Rc<PoeList>, |     poe_list: Rc<PoeList>, | ||||||
|  | @ -23,47 +25,43 @@ impl Window { | ||||||
|         let builder = gtk::Builder::from_resource("/de/johrpan/musicus_editor/ui/window.ui"); |         let builder = gtk::Builder::from_resource("/de/johrpan/musicus_editor/ui/window.ui"); | ||||||
| 
 | 
 | ||||||
|         get_widget!(builder, libhandy::ApplicationWindow, window); |         get_widget!(builder, libhandy::ApplicationWindow, window); | ||||||
|  |         get_widget!(builder, gtk::Stack, stack); | ||||||
|  |         get_widget!(builder, gtk::Button, select_music_library_path_button); | ||||||
|         get_widget!(builder, libhandy::Leaflet, leaflet); |         get_widget!(builder, libhandy::Leaflet, leaflet); | ||||||
|         get_widget!(builder, gtk::Button, add_button); |         get_widget!(builder, gtk::Button, add_button); | ||||||
|         get_widget!(builder, gtk::Box, sidebar_box); |         get_widget!(builder, gtk::Box, sidebar_box); | ||||||
|         get_widget!(builder, gtk::Box, empty_screen); |         get_widget!(builder, gtk::Box, empty_screen); | ||||||
| 
 | 
 | ||||||
|         let backend = Rc::new(Backend::new( |         let backend = Rc::new(Backend::new()); | ||||||
|             "test.sqlite", |  | ||||||
|             std::env::current_dir().unwrap(), |  | ||||||
|         )); |  | ||||||
|         let poe_list = PoeList::new(backend.clone()); |         let poe_list = PoeList::new(backend.clone()); | ||||||
|         let navigator = Navigator::new(&empty_screen); |         let navigator = Navigator::new(&empty_screen); | ||||||
| 
 | 
 | ||||||
|         let result = Rc::new(Self { |         let result = Rc::new(Self { | ||||||
|             backend, |             backend, | ||||||
|             window, |             window, | ||||||
|  |             stack, | ||||||
|             leaflet, |             leaflet, | ||||||
|             sidebar_box, |             sidebar_box, | ||||||
|             poe_list, |             poe_list, | ||||||
|             navigator, |             navigator, | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         result |  | ||||||
|             .poe_list |  | ||||||
|             .set_selected(clone!(@strong result => move |poe| { |  | ||||||
|                 result.leaflet.set_visible_child(&result.navigator.widget); |  | ||||||
|                 match poe { |  | ||||||
|                     PersonOrEnsemble::Person(person) => { |  | ||||||
|                         result.navigator.clone().replace(PersonScreen::new(result.backend.clone(), person.clone())); |  | ||||||
|                     } |  | ||||||
|                     PersonOrEnsemble::Ensemble(ensemble) => { |  | ||||||
|                         result.navigator.clone().replace(EnsembleScreen::new(result.backend.clone(), ensemble.clone())); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             })); |  | ||||||
| 
 |  | ||||||
|         result.leaflet.add(&result.navigator.widget); |  | ||||||
|         result |  | ||||||
|             .sidebar_box |  | ||||||
|             .pack_start(&result.poe_list.widget, true, true, 0); |  | ||||||
|         result.window.set_application(Some(app)); |         result.window.set_application(Some(app)); | ||||||
| 
 | 
 | ||||||
|  |         select_music_library_path_button.connect_clicked(clone!(@strong result => move |_| { | ||||||
|  |             let dialog = gtk::FileChooserNative::new(Some("Select music library folder"), Some(&result.window), gtk::FileChooserAction::SelectFolder, None, None); | ||||||
|  | 
 | ||||||
|  |             if let gtk::ResponseType::Accept = dialog.run() { | ||||||
|  |                 if let Some(path) = dialog.get_filename() {                    
 | ||||||
|  |                     let context = glib::MainContext::default(); | ||||||
|  |                     let backend = result.backend.clone(); | ||||||
|  |                     context.spawn_local(async move { | ||||||
|  |                         backend.set_music_library_path(path).await.unwrap(); | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         })); | ||||||
|  | 
 | ||||||
|         add_button.connect_clicked(clone!(@strong result => move |_| { |         add_button.connect_clicked(clone!(@strong result => move |_| { | ||||||
|             TracksEditor::new(result.backend.clone(), &result.window, clone!(@strong result => move || { |             TracksEditor::new(result.backend.clone(), &result.window, clone!(@strong result => move || { | ||||||
|                 result.reload(); |                 result.reload(); | ||||||
|  | @ -266,6 +264,46 @@ impl Window { | ||||||
|             }) |             }) | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|  |         let context = glib::MainContext::default(); | ||||||
|  |         let clone = result.clone(); | ||||||
|  |         context.spawn_local(async move { | ||||||
|  |             let mut state_stream = clone.backend.state_stream.borrow_mut(); | ||||||
|  |             while let Some(state) = state_stream.next().await { | ||||||
|  |                 match state { | ||||||
|  |                     BackendState::NoMusicLibrary => { | ||||||
|  |                         clone.stack.set_visible_child_name("empty"); | ||||||
|  |                     } | ||||||
|  |                     BackendState::Loading => { | ||||||
|  |                         clone.stack.set_visible_child_name("loading"); | ||||||
|  |                     } | ||||||
|  |                     BackendState::Ready => { | ||||||
|  |                         clone.stack.set_visible_child_name("content"); | ||||||
|  |                         clone.poe_list.clone().reload(); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         result.leaflet.add(&result.navigator.widget); | ||||||
|  | 
 | ||||||
|  |         result | ||||||
|  |             .poe_list | ||||||
|  |             .set_selected(clone!(@strong result => move |poe| { | ||||||
|  |                 result.leaflet.set_visible_child(&result.navigator.widget); | ||||||
|  |                 match poe { | ||||||
|  |                     PersonOrEnsemble::Person(person) => { | ||||||
|  |                         result.navigator.clone().replace(PersonScreen::new(result.backend.clone(), person.clone())); | ||||||
|  |                     } | ||||||
|  |                     PersonOrEnsemble::Ensemble(ensemble) => { | ||||||
|  |                         result.navigator.clone().replace(EnsembleScreen::new(result.backend.clone(), ensemble.clone())); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             })); | ||||||
|  | 
 | ||||||
|  |         result | ||||||
|  |             .sidebar_box | ||||||
|  |             .pack_start(&result.poe_list.widget, true, true, 0); | ||||||
|  | 
 | ||||||
|         result |         result | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Elias Projahn
						Elias Projahn