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