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