From cec955eb9f86ecd511193fa89eda62597a52c11a Mon Sep 17 00:00:00 2001 From: Elias Projahn Date: Mon, 24 May 2021 15:37:19 +0200 Subject: [PATCH] database: Introduce single PersonOrEnsemble enum --- database/src/error.rs | 3 + database/src/recordings.rs | 190 ++++++++++++++--------------- musicus/src/editors/performance.rs | 30 +++-- musicus/src/screens/main.rs | 19 +-- musicus/src/selectors/medium.rs | 19 +-- 5 files changed, 118 insertions(+), 143 deletions(-) diff --git a/database/src/error.rs b/database/src/error.rs index d0b1c8a..48b5dab 100644 --- a/database/src/error.rs +++ b/database/src/error.rs @@ -21,6 +21,9 @@ pub enum Error { #[error(transparent)] ReceiveError(#[from] tokio::sync::oneshot::error::RecvError), + + #[error("{0}")] + Other(&'static str), } /// Return type for database methods. diff --git a/database/src/recordings.rs b/database/src/recordings.rs index b78ffc6..de3ff79 100644 --- a/database/src/recordings.rs +++ b/database/src/recordings.rs @@ -5,84 +5,6 @@ use diesel::prelude::*; use log::info; use serde::{Deserialize, Serialize}; -/// Database table data for a recording. -#[derive(Insertable, Queryable, Debug, Clone)] -#[table_name = "recordings"] -struct RecordingRow { - pub id: String, - pub work: String, - pub comment: String, -} - -impl From for RecordingRow { - fn from(recording: Recording) -> Self { - RecordingRow { - id: recording.id, - work: recording.work.id, - comment: recording.comment, - } - } -} - -/// Database table data for a performance. -#[derive(Insertable, Queryable, Debug, Clone)] -#[table_name = "performances"] -struct PerformanceRow { - pub id: i64, - pub recording: String, - pub person: Option, - pub ensemble: Option, - pub role: Option, -} - -/// How a person or ensemble was involved in a recording. -// TODO: Replace person/ensemble with an enum. -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct Performance { - pub person: Option, - pub ensemble: Option, - pub role: Option, -} - -impl Performance { - /// Get a string representation of the performance. - // TODO: Replace with impl Display. - pub fn get_title(&self) -> String { - let mut text = if self.is_person() { - self.unwrap_person().name_fl() - } else { - self.unwrap_ensemble().name - }; - - if self.has_role() { - text = text + " (" + &self.unwrap_role().name + ")"; - } - - text - } - - pub fn is_person(&self) -> bool { - self.person.is_some() - } - - pub fn unwrap_person(&self) -> Person { - self.person.clone().unwrap() - } - - pub fn unwrap_ensemble(&self) -> Ensemble { - self.ensemble.clone().unwrap() - } - - pub fn has_role(&self) -> bool { - self.role.clone().is_some() - } - - pub fn unwrap_role(&self) -> Instrument { - self.role.clone().unwrap() - } -} - /// A specific recording of a work. #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] @@ -117,6 +39,75 @@ impl Recording { } } +/// How a person or ensemble was involved in a recording. +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Performance { + pub performer: PersonOrEnsemble, + pub role: Option, +} + +impl Performance { + /// Get a string representation of the performance. + // TODO: Replace with impl Display. + pub fn get_title(&self) -> String { + let performer_title = self.performer.get_title(); + + if let Some(role) = &self.role { + format!("{} ({})", performer_title, role.name) + } else { + performer_title + } + } +} + +/// Either a person or an ensemble. +#[derive(Serialize, Deserialize, Clone, Debug)] +pub enum PersonOrEnsemble { + Person(Person), + Ensemble(Ensemble), +} + +impl PersonOrEnsemble { + /// Get a short textual representation of the item. + pub fn get_title(&self) -> String { + match self { + PersonOrEnsemble::Person(person) => person.name_lf(), + PersonOrEnsemble::Ensemble(ensemble) => ensemble.name.clone(), + } + } +} + +/// Database table data for a recording. +#[derive(Insertable, Queryable, Debug, Clone)] +#[table_name = "recordings"] +struct RecordingRow { + pub id: String, + pub work: String, + pub comment: String, +} + +impl From for RecordingRow { + fn from(recording: Recording) -> Self { + RecordingRow { + id: recording.id, + work: recording.work.id, + comment: recording.comment, + } + } +} + +/// Database table data for a performance. +#[derive(Insertable, Queryable, Debug, Clone)] +#[table_name = "performances"] +struct PerformanceRow { + pub id: i64, + pub recording: String, + pub person: Option, + pub ensemble: Option, + pub role: Option, +} + impl Database { /// Update an existing recording or insert a new one. // TODO: Think about whether to also insert the other items. @@ -134,15 +125,16 @@ impl Database { } for performance in &recording.performances { - if let Some(person) = &performance.person { - if self.get_person(&person.id)?.is_none() { - self.update_person(person.clone())?; + match &performance.performer { + PersonOrEnsemble::Person(person) => { + if self.get_person(&person.id)?.is_none() { + self.update_person(person.clone())?; + } } - } - - if let Some(ensemble) = &performance.ensemble { - if self.get_ensemble(&ensemble.id)?.is_none() { - self.update_ensemble(ensemble.clone())?; + PersonOrEnsemble::Ensemble(ensemble) => { + if self.get_ensemble(&ensemble.id)?.is_none() { + self.update_ensemble(ensemble.clone())?; + } } } @@ -161,11 +153,16 @@ impl Database { .execute(&self.connection)?; for performance in recording.performances { + let (person, ensemble) = match performance.performer { + PersonOrEnsemble::Person(person) => (Some(person.id), None), + PersonOrEnsemble::Ensemble(ensemble) => (None, Some(ensemble.id)), + }; + let row = PerformanceRow { id: rand::random(), recording: recording_id.to_string(), - person: performance.person.map(|person| person.id), - ensemble: performance.ensemble.map(|ensemble| ensemble.id), + person, + ensemble, role: performance.role.map(|role| role.id), }; @@ -217,19 +214,18 @@ impl Database { for row in performance_rows { performance_descriptions.push(Performance { - person: match row.person { - Some(id) => Some( + performer: if let Some(id) = row.person { + PersonOrEnsemble::Person( self.get_person(&id)? .ok_or(Error::MissingItem("person", id))?, - ), - None => None, - }, - ensemble: match row.ensemble { - Some(id) => Some( + ) + } else if let Some(id) = row.ensemble { + PersonOrEnsemble::Ensemble( self.get_ensemble(&id)? .ok_or(Error::MissingItem("ensemble", id))?, - ), - None => None, + ) + } else { + return Err(Error::Other("Performance without performer")); }, role: match row.role { Some(id) => Some( diff --git a/musicus/src/editors/performance.rs b/musicus/src/editors/performance.rs index c2dd0d6..b500985 100644 --- a/musicus/src/editors/performance.rs +++ b/musicus/src/editors/performance.rs @@ -5,7 +5,8 @@ use adw::prelude::*; use gettextrs::gettext; use glib::clone; use gtk::prelude::*; -use musicus_backend::db::{Ensemble, Instrument, Performance, Person}; +use log::error; +use musicus_backend::db::{Ensemble, Instrument, Performance, Person, PersonOrEnsemble}; use std::cell::RefCell; use std::rc::Rc; @@ -85,8 +86,14 @@ impl Screen, Performance> for PerformanceEditor { this.editor.set_save_cb(clone!(@weak this => move || { let performance = Performance { - person: this.person.borrow().clone(), - ensemble: this.ensemble.borrow().clone(), + performer: if let Some(person) = this.person.borrow().clone() { + PersonOrEnsemble::Person(person) + } else if let Some(ensemble) = this.ensemble.borrow().clone() { + PersonOrEnsemble::Ensemble(ensemble) + } else { + error!("Tried to save performance without performer"); + return; + }, role: this.role.borrow().clone(), }; @@ -133,13 +140,16 @@ impl Screen, Performance> for PerformanceEditor { // Initialize if let Some(performance) = performance { - if let Some(person) = performance.person { - this.show_person(Some(&person)); - this.person.replace(Some(person)); - } else if let Some(ensemble) = performance.ensemble { - this.show_ensemble(Some(&ensemble)); - this.ensemble.replace(Some(ensemble)); - } + match performance.performer { + PersonOrEnsemble::Person(person) => { + this.show_person(Some(&person)); + this.person.replace(Some(person)); + } + PersonOrEnsemble::Ensemble(ensemble) => { + this.show_ensemble(Some(&ensemble)); + this.ensemble.replace(Some(ensemble)); + } + }; if let Some(role) = performance.role { this.show_role(Some(&role)); diff --git a/musicus/src/screens/main.rs b/musicus/src/screens/main.rs index 5b4989e..78f77f6 100644 --- a/musicus/src/screens/main.rs +++ b/musicus/src/screens/main.rs @@ -9,27 +9,10 @@ use gettextrs::gettext; use glib::clone; use gtk::prelude::*; use gtk_macros::get_widget; -use musicus_backend::db::{Ensemble, Person}; +use musicus_backend::db::PersonOrEnsemble; use std::cell::RefCell; use std::rc::Rc; -/// Either a person or an ensemble to be shown in the list. -#[derive(Clone, Debug)] -pub enum PersonOrEnsemble { - Person(Person), - Ensemble(Ensemble), -} - -impl PersonOrEnsemble { - /// Get a short textual representation of the item. - pub fn get_title(&self) -> String { - match self { - PersonOrEnsemble::Person(person) => person.name_lf(), - PersonOrEnsemble::Ensemble(ensemble) => ensemble.name.clone(), - } - } -} - /// The main screen of the app, once it's set up and finished loading. The screen assumes that the /// music library and the player are available and initialized. pub struct MainScreen { diff --git a/musicus/src/selectors/medium.rs b/musicus/src/selectors/medium.rs index 6ec623e..8235e25 100644 --- a/musicus/src/selectors/medium.rs +++ b/musicus/src/selectors/medium.rs @@ -5,26 +5,9 @@ use adw::prelude::*; use gettextrs::gettext; use glib::clone; use gtk::prelude::*; -use musicus_backend::db::{Ensemble, Medium, Person}; +use musicus_backend::db::{Medium, PersonOrEnsemble}; use std::rc::Rc; -/// Either a person or an ensemble to be shown in the list. -#[derive(Clone, Debug)] -pub enum PersonOrEnsemble { - Person(Person), - Ensemble(Ensemble), -} - -impl PersonOrEnsemble { - /// Get a short textual representation of the item. - pub fn get_title(&self) -> String { - match self { - PersonOrEnsemble::Person(person) => person.name_lf(), - PersonOrEnsemble::Ensemble(ensemble) => ensemble.name.clone(), - } - } -} - /// A screen for selecting a medium. pub struct MediumSelector { handle: NavigationHandle,