Make music library path changable

This commit is contained in:
Elias Projahn 2020-11-01 10:13:21 +01:00
parent cf96792029
commit 543f5ed1fd
5 changed files with 480 additions and 236 deletions

View file

@ -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>

View file

@ -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()
} }
} }

View file

@ -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();
});
} }
} }
})); }));

View file

@ -64,8 +64,6 @@ impl PoeList {
result.list.invalidate_filter(); result.list.invalidate_filter();
})); }));
result.clone().reload();
result result
} }

View file

@ -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
} }