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