database: Introduce single PersonOrEnsemble enum

This commit is contained in:
Elias Projahn 2021-05-24 15:37:19 +02:00
parent 15e5849730
commit cec955eb9f
5 changed files with 118 additions and 143 deletions

View file

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

View file

@ -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<Recording> 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<String>,
pub ensemble: Option<String>,
pub role: Option<String>,
}
/// 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<Person>,
pub ensemble: Option<Ensemble>,
pub role: Option<Instrument>,
}
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<Instrument>,
}
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<Recording> 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<String>,
pub ensemble: Option<String>,
pub role: Option<String>,
}
impl Database {
/// Update an existing recording or insert a new one.
// TODO: Think about whether to also insert the other items.
@ -134,17 +125,18 @@ impl Database {
}
for performance in &recording.performances {
if let Some(person) = &performance.person {
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 {
PersonOrEnsemble::Ensemble(ensemble) => {
if self.get_ensemble(&ensemble.id)?.is_none() {
self.update_ensemble(ensemble.clone())?;
}
}
}
if let Some(role) = &performance.role {
if self.get_instrument(&role.id)?.is_none() {
@ -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(

View file

@ -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<Option<Performance>, 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<Option<Performance>, Performance> for PerformanceEditor {
// Initialize
if let Some(performance) = performance {
if let Some(person) = performance.person {
match performance.performer {
PersonOrEnsemble::Person(person) => {
this.show_person(Some(&person));
this.person.replace(Some(person));
} else if let Some(ensemble) = performance.ensemble {
}
PersonOrEnsemble::Ensemble(ensemble) => {
this.show_ensemble(Some(&ensemble));
this.ensemble.replace(Some(ensemble));
}
};
if let Some(role) = performance.role {
this.show_role(Some(&role));

View file

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

View file

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