editor: Functional recording and work editor

This commit is contained in:
Elias Projahn 2025-01-17 17:54:16 +01:00
parent f0135cd415
commit d7401195b3
8 changed files with 487 additions and 138 deletions

View file

@ -0,0 +1,21 @@
using Gtk 4.0;
using Adw 1;
template $MusicusWorkEditorPartRow: Adw.ActionRow {
activatable: true;
activated => $edit() swapped;
Gtk.Image {
icon-name: "document-edit-symbolic";
}
Gtk.Button {
icon-name: "user-trash-symbolic";
valign: center;
clicked => $remove() swapped;
styles [
"flat"
]
}
}

View file

@ -17,19 +17,11 @@ pub use tables::{Album, Instrument, Person, Role};
pub struct Work { pub struct Work {
pub work_id: String, pub work_id: String,
pub name: TranslatedString, pub name: TranslatedString,
pub parts: Vec<WorkPart>, pub parts: Vec<Work>,
pub persons: Vec<Composer>, pub persons: Vec<Composer>,
pub instruments: Vec<Instrument>, pub instruments: Vec<Instrument>,
} }
// TODO: Handle part composers.
#[derive(Default, Clone, Debug)]
pub struct WorkPart {
pub work_id: String,
pub level: u8,
pub name: TranslatedString,
}
#[derive(Queryable, Selectable, Clone, Debug)] #[derive(Queryable, Selectable, Clone, Debug)]
pub struct Composer { pub struct Composer {
#[diesel(embed)] #[diesel(embed)]
@ -122,42 +114,17 @@ impl PartialEq for Composer {
} }
} }
impl Eq for WorkPart {}
impl PartialEq for WorkPart {
fn eq(&self, other: &Self) -> bool {
self.work_id == other.work_id
}
}
impl Work { impl Work {
pub fn from_table(data: tables::Work, connection: &mut SqliteConnection) -> Result<Self> { pub fn from_table(data: tables::Work, connection: &mut SqliteConnection) -> Result<Self> {
fn visit_children( // Note: Because this calls Work::from_table for each part, this recursively
work_id: &str, // adds all children. It does not check for circularity.
level: u8, let parts = works::table
connection: &mut SqliteConnection, .order(works::sequence_number)
) -> Result<Vec<WorkPart>> { .filter(works::parent_work_id.eq(&data.work_id))
let mut parts = Vec::new(); .load::<tables::Work>(connection)?
.into_iter()
let children: Vec<tables::Work> = works::table .map(|w| Work::from_table(w, connection))
.filter(works::parent_work_id.eq(work_id)) .collect::<Result<Vec<Work>>>()?;
.load(connection)?;
for child in children {
let mut grand_children = visit_children(&child.work_id, level + 1, connection)?;
parts.push(WorkPart {
work_id: child.work_id,
level,
name: child.name,
});
parts.append(&mut grand_children);
}
Ok(parts)
}
let parts = visit_children(&data.work_id, 0, connection)?;
let persons: Vec<Composer> = persons::table let persons: Vec<Composer> = persons::table
.inner_join(work_persons::table.inner_join(roles::table)) .inner_join(work_persons::table.inner_join(roles::table))

View file

@ -15,4 +15,5 @@ pub mod translation_editor;
pub mod translation_entry; pub mod translation_entry;
pub mod work_editor; pub mod work_editor;
pub mod work_editor_composer_row; pub mod work_editor_composer_row;
pub mod work_editor_part_row;
pub mod work_selector_popover; pub mod work_selector_popover;

View file

@ -102,7 +102,7 @@ mod imp {
let obj = self.obj().clone(); let obj = self.obj().clone();
work_selector_popover.connect_create(move |_| { work_selector_popover.connect_create(move |_| {
let editor = MusicusWorkEditor::new(&obj.navigation(), &obj.library(), None); let editor = MusicusWorkEditor::new(&obj.navigation(), &obj.library(), None, false);
editor.connect_created(clone!( editor.connect_created(clone!(
#[weak] #[weak]
@ -124,7 +124,7 @@ mod imp {
let obj = self.obj().clone(); let obj = self.obj().clone();
persons_popover.connect_person_selected(move |_, person| { persons_popover.connect_person_selected(move |_, person| {
obj.add_performer(person); obj.new_performer(person);
}); });
let obj = self.obj().clone(); let obj = self.obj().clone();
@ -135,7 +135,7 @@ mod imp {
#[weak] #[weak]
obj, obj,
move |_, person| { move |_, person| {
obj.add_performer(person); obj.new_performer(person);
} }
)); ));
@ -150,7 +150,7 @@ mod imp {
let obj = self.obj().clone(); let obj = self.obj().clone();
ensembles_popover.connect_ensemble_selected(move |_, ensemble| { ensembles_popover.connect_ensemble_selected(move |_, ensemble| {
obj.add_ensemble(ensemble); obj.new_ensemble_performer(ensemble);
}); });
let obj = self.obj().clone(); let obj = self.obj().clone();
@ -161,7 +161,7 @@ mod imp {
#[weak] #[weak]
obj, obj,
move |_, ensemble| { move |_, ensemble| {
obj.add_ensemble(ensemble); obj.new_ensemble_performer(ensemble);
} }
)); ));
@ -200,7 +200,20 @@ impl MusicusRecordingEditor {
.recording_id .recording_id
.set(recording.recording_id.clone()) .set(recording.recording_id.clone())
.unwrap(); .unwrap();
// TODO: Initialize data.
obj.set_work(recording.work.clone());
if let Some(year) = recording.year {
obj.imp().year_row.set_value(year as f64);
}
for performer in recording.persons.clone() {
obj.add_performer_row(performer);
}
for ensemble_performer in recording.ensembles.clone() {
obj.add_ensemble_row(ensemble_performer);
}
} }
obj obj
@ -227,14 +240,17 @@ impl MusicusRecordingEditor {
self.imp().work.replace(Some(work)); self.imp().work.replace(Some(work));
} }
fn add_performer(&self, person: Person) { fn new_performer(&self, person: Person) {
let role = self.library().performer_default_role().unwrap();
let performer = Performer { let performer = Performer {
person, person,
role, role: self.library().performer_default_role().unwrap(),
instrument: None, instrument: None,
}; };
self.add_performer_row(performer);
}
fn add_performer_row(&self, performer: Performer) {
let row = let row =
MusicusRecordingEditorPerformerRow::new(&self.navigation(), &self.library(), performer); MusicusRecordingEditorPerformerRow::new(&self.navigation(), &self.library(), performer);
@ -254,12 +270,21 @@ impl MusicusRecordingEditor {
self.imp().performer_rows.borrow_mut().push(row); self.imp().performer_rows.borrow_mut().push(row);
} }
fn add_ensemble(&self, ensemble: Ensemble) { fn new_ensemble_performer(&self, ensemble: Ensemble) {
let role = self.library().performer_default_role().unwrap(); let performer = EnsemblePerformer {
let performer = EnsemblePerformer { ensemble, role }; ensemble,
role: self.library().performer_default_role().unwrap(),
};
let row = self.add_ensemble_row(performer);
MusicusRecordingEditorEnsembleRow::new(&self.navigation(), &self.library(), performer); }
fn add_ensemble_row(&self, ensemble_performer: EnsemblePerformer) {
let row = MusicusRecordingEditorEnsembleRow::new(
&self.navigation(),
&self.library(),
ensemble_performer,
);
row.connect_remove(clone!( row.connect_remove(clone!(
#[weak(rename_to = this)] #[weak(rename_to = this)]

View file

@ -1,4 +1,18 @@
use std::cell::{OnceCell, RefCell}; use crate::{
db::{
self,
models::{Composer, Instrument, Person, Work},
},
editor::{
instrument_editor::MusicusInstrumentEditor,
instrument_selector_popover::MusicusInstrumentSelectorPopover,
person_editor::MusicusPersonEditor, person_selector_popover::MusicusPersonSelectorPopover,
translation_editor::MusicusTranslationEditor,
work_editor_composer_row::MusicusWorkEditorComposerRow,
work_editor_part_row::MusicusWorkEditorPartRow,
},
library::MusicusLibrary,
};
use adw::{prelude::*, subclass::prelude::*}; use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext; use gettextrs::gettext;
@ -8,20 +22,7 @@ use gtk::glib::{
}; };
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use crate::{ use std::cell::{Cell, OnceCell, RefCell};
db::{
self,
models::{Composer, Instrument, Person, Work, WorkPart},
},
editor::{
instrument_editor::MusicusInstrumentEditor,
instrument_selector_popover::MusicusInstrumentSelectorPopover,
person_editor::MusicusPersonEditor, person_selector_popover::MusicusPersonSelectorPopover,
translation_editor::MusicusTranslationEditor,
work_editor_composer_row::MusicusWorkEditorComposerRow,
},
library::MusicusLibrary,
};
mod imp { mod imp {
use super::*; use super::*;
@ -37,13 +38,14 @@ mod imp {
pub library: OnceCell<MusicusLibrary>, pub library: OnceCell<MusicusLibrary>,
pub work_id: OnceCell<String>, pub work_id: OnceCell<String>,
pub is_part_editor: Cell<bool>,
// Holding a reference to each composer row is the simplest way to enumerate all // Holding a reference to each composer row is the simplest way to enumerate all
// results when finishing the process of editing the work. The composer rows // results when finishing the process of editing the work. The composer rows
// handle all state related to the composer. // handle all state related to the composer.
pub composer_rows: RefCell<Vec<MusicusWorkEditorComposerRow>>, pub composer_rows: RefCell<Vec<MusicusWorkEditorComposerRow>>,
// TODO: These need to be PartRows! pub part_rows: RefCell<Vec<MusicusWorkEditorPartRow>>,
pub parts: RefCell<Vec<WorkPart>>,
pub instruments: RefCell<Vec<Instrument>>, pub instruments: RefCell<Vec<Instrument>>,
pub persons_popover: OnceCell<MusicusPersonSelectorPopover>, pub persons_popover: OnceCell<MusicusPersonSelectorPopover>,
@ -165,16 +167,36 @@ impl MusicusWorkEditor {
navigation: &adw::NavigationView, navigation: &adw::NavigationView,
library: &MusicusLibrary, library: &MusicusLibrary,
work: Option<&Work>, work: Option<&Work>,
is_part_editor: bool,
) -> Self { ) -> Self {
let obj: Self = glib::Object::builder() let obj: Self = glib::Object::builder()
.property("navigation", navigation) .property("navigation", navigation)
.property("library", library) .property("library", library)
.build(); .build();
if is_part_editor {
obj.set_title(&gettext("Work part"));
obj.imp().save_button.set_label(&gettext("Add work part"));
obj.imp().is_part_editor.set(true);
}
if let Some(work) = work { if let Some(work) = work {
obj.imp().save_button.set_label(&gettext("Save changes")); obj.imp().save_button.set_label(&gettext("Save changes"));
obj.imp().work_id.set(work.work_id.clone()).unwrap(); obj.imp().work_id.set(work.work_id.clone()).unwrap();
// TODO: Initialize work data.
obj.imp().name_editor.set_translation(&work.name);
for part in &work.parts {
obj.add_part_row(part.clone());
}
for composer in &work.persons {
obj.add_composer_row(composer.clone());
}
for instrument in &work.instruments {
obj.add_instrument_row(instrument.clone());
}
} }
obj obj
@ -196,41 +218,17 @@ impl MusicusWorkEditor {
#[template_callback] #[template_callback]
fn add_part(&self, _: &adw::ActionRow) { fn add_part(&self, _: &adw::ActionRow) {
let part = WorkPart { let editor = MusicusWorkEditor::new(&self.navigation(), &self.library(), None, true);
work_id: db::generate_id(),
..Default::default()
};
let row = adw::EntryRow::builder().title(gettext("Name")).build(); editor.connect_created(clone!(
let remove_button = gtk::Button::builder()
.icon_name("user-trash-symbolic")
.valign(gtk::Align::Center)
.css_classes(["flat"])
.build();
remove_button.connect_clicked(clone!(
#[weak(rename_to = this)] #[weak(rename_to = this)]
self, self,
#[weak] move |_, part| {
row, this.add_part_row(part);
#[strong]
part,
move |_| {
this.imp().part_list.remove(&row);
this.imp().parts.borrow_mut().retain(|p| *p != part);
} }
)); ));
row.add_suffix(&remove_button); self.navigation().push(&editor);
self.imp()
.part_list
.insert(&row, self.imp().parts.borrow().len() as i32);
row.grab_focus();
self.imp().parts.borrow_mut().push(part);
} }
#[template_callback] #[template_callback]
@ -241,6 +239,29 @@ impl MusicusWorkEditor {
fn add_composer(&self, person: Person) { fn add_composer(&self, person: Person) {
let role = self.library().composer_default_role().unwrap(); let role = self.library().composer_default_role().unwrap();
let composer = Composer { person, role }; let composer = Composer { person, role };
self.add_composer_row(composer);
}
fn add_part_row(&self, part: Work) {
let row = MusicusWorkEditorPartRow::new(&self.navigation(), &self.library(), part);
row.connect_remove(clone!(
#[weak(rename_to = this)]
self,
move |row| {
this.imp().part_list.remove(row);
this.imp().part_rows.borrow_mut().retain(|p| p != row);
}
));
self.imp()
.part_list
.insert(&row, self.imp().part_rows.borrow().len() as i32);
self.imp().part_rows.borrow_mut().push(row);
}
fn add_composer_row(&self, composer: Composer) {
let row = MusicusWorkEditorComposerRow::new(&self.navigation(), &self.library(), composer); let row = MusicusWorkEditorComposerRow::new(&self.navigation(), &self.library(), composer);
row.connect_remove(clone!( row.connect_remove(clone!(
@ -300,7 +321,15 @@ impl MusicusWorkEditor {
let library = self.imp().library.get().unwrap(); let library = self.imp().library.get().unwrap();
let name = self.imp().name_editor.translation(); let name = self.imp().name_editor.translation();
let parts = self.imp().parts.borrow().clone();
let parts = self
.imp()
.part_rows
.borrow()
.iter()
.map(|p| p.part())
.collect::<Vec<Work>>();
let composers = self let composers = self
.imp() .imp()
.composer_rows .composer_rows
@ -310,15 +339,34 @@ impl MusicusWorkEditor {
.collect::<Vec<Composer>>(); .collect::<Vec<Composer>>();
let instruments = self.imp().instruments.borrow().clone(); let instruments = self.imp().instruments.borrow().clone();
if let Some(work_id) = self.imp().work_id.get() { if self.imp().is_part_editor.get() {
library let work_id = self
.update_work(work_id, name, parts, composers, instruments) .imp()
.unwrap(); .work_id
.get()
.map(|w| w.to_string())
.unwrap_or_else(db::generate_id);
let part = Work {
work_id,
name,
parts,
persons: composers,
instruments,
};
self.emit_by_name::<()>("created", &[&part]);
} else { } else {
let work = library if let Some(work_id) = self.imp().work_id.get() {
.create_work(name, parts, composers, instruments) library
.unwrap(); .update_work(work_id, name, parts, composers, instruments)
self.emit_by_name::<()>("created", &[&work]); .unwrap();
} else {
let work = library
.create_work(name, parts, composers, instruments)
.unwrap();
self.emit_by_name::<()>("created", &[&work]);
}
} }
self.imp().navigation.get().unwrap().pop(); self.imp().navigation.get().unwrap().pop();

View file

@ -0,0 +1,129 @@
use crate::{db::models::Work, editor::work_editor::MusicusWorkEditor, library::MusicusLibrary};
use adw::{prelude::*, subclass::prelude::*};
use gtk::glib::{self, clone, subclass::Signal, Properties};
use once_cell::sync::Lazy;
use std::cell::{OnceCell, RefCell};
mod imp {
use super::*;
#[derive(Properties, Debug, Default, gtk::CompositeTemplate)]
#[properties(wrapper_type = super::MusicusWorkEditorPartRow)]
#[template(file = "data/ui/work_editor_part_row.blp")]
pub struct MusicusWorkEditorPartRow {
#[property(get, construct_only)]
pub navigation: OnceCell<adw::NavigationView>,
#[property(get, construct_only)]
pub library: OnceCell<MusicusLibrary>,
pub part: RefCell<Option<Work>>,
}
#[glib::object_subclass]
impl ObjectSubclass for MusicusWorkEditorPartRow {
const NAME: &'static str = "MusicusWorkEditorPartRow";
type Type = super::MusicusWorkEditorPartRow;
type ParentType = adw::ActionRow;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
klass.bind_template_instance_callbacks();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
#[glib::derived_properties]
impl ObjectImpl for MusicusWorkEditorPartRow {
fn signals() -> &'static [Signal] {
static SIGNALS: Lazy<Vec<Signal>> =
Lazy::new(|| vec![Signal::builder("remove").build()]);
SIGNALS.as_ref()
}
}
impl WidgetImpl for MusicusWorkEditorPartRow {}
impl ListBoxRowImpl for MusicusWorkEditorPartRow {}
impl PreferencesRowImpl for MusicusWorkEditorPartRow {}
impl ActionRowImpl for MusicusWorkEditorPartRow {}
}
glib::wrapper! {
pub struct MusicusWorkEditorPartRow(ObjectSubclass<imp::MusicusWorkEditorPartRow>)
@extends gtk::Widget, gtk::ListBoxRow, adw::PreferencesRow, adw::ActionRow;
}
#[gtk::template_callbacks]
impl MusicusWorkEditorPartRow {
pub fn new(navigation: &adw::NavigationView, library: &MusicusLibrary, part: Work) -> Self {
let obj: Self = glib::Object::builder()
.property("navigation", navigation)
.property("library", library)
.build();
obj.set_part(part);
obj
}
pub fn connect_remove<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
self.connect_local("remove", true, move |values| {
let obj = values[0].get::<Self>().unwrap();
f(&obj);
None
})
}
pub fn part(&self) -> Work {
self.imp().part.borrow().to_owned().unwrap()
}
fn set_part(&self, part: Work) {
self.set_title(&part.name.get());
if !part.parts.is_empty() {
self.set_subtitle(
&part
.parts
.iter()
.map(|p| p.name.get())
.collect::<Vec<&str>>()
.join("\n"),
);
} else {
self.set_subtitle("");
}
self.imp().part.replace(Some(part));
}
#[template_callback]
fn edit(&self) {
let editor = MusicusWorkEditor::new(
&self.navigation(),
&self.library(),
self.imp().part.borrow().as_ref(),
true,
);
editor.connect_created(clone!(
#[weak(rename_to = this)]
self,
move |_, part| {
this.set_part(part);
}
));
self.navigation().push(&editor);
}
#[template_callback]
fn remove(&self, _: &gtk::Button) {
self.emit_by_name::<()>("remove", &[]);
}
}

View file

@ -173,6 +173,7 @@ impl MusicusHomePage {
&self.navigation(), &self.navigation(),
&self.library(), &self.library(),
Some(work), Some(work),
false,
)), )),
} }
} }

View file

@ -848,20 +848,33 @@ impl MusicusLibrary {
pub fn create_work( pub fn create_work(
&self, &self,
name: TranslatedString, name: TranslatedString,
parts: Vec<WorkPart>, parts: Vec<Work>,
persons: Vec<Composer>, persons: Vec<Composer>,
instruments: Vec<Instrument>, instruments: Vec<Instrument>,
) -> Result<Work> { ) -> Result<Work> {
let mut binding = self.imp().connection.borrow_mut(); let mut binding = self.imp().connection.borrow_mut();
let connection = &mut *binding.as_mut().unwrap(); let connection = &mut *binding.as_mut().unwrap();
self.create_work_priv(connection, name, parts, persons, instruments, None, None)
}
fn create_work_priv(
&self,
connection: &mut SqliteConnection,
name: TranslatedString,
parts: Vec<Work>,
persons: Vec<Composer>,
instruments: Vec<Instrument>,
parent_work_id: Option<&str>,
sequence_number: Option<i32>,
) -> Result<Work> {
let work_id = db::generate_id(); let work_id = db::generate_id();
let now = Local::now().naive_local(); let now = Local::now().naive_local();
let work_data = tables::Work { let work_data = tables::Work {
work_id: work_id.clone(), work_id: work_id.clone(),
parent_work_id: None, parent_work_id: parent_work_id.map(|w| w.to_string()),
sequence_number: None, sequence_number: sequence_number,
name, name,
created_at: now, created_at: now,
edited_at: now, edited_at: now,
@ -874,20 +887,15 @@ impl MusicusLibrary {
.execute(connection)?; .execute(connection)?;
for (index, part) in parts.into_iter().enumerate() { for (index, part) in parts.into_iter().enumerate() {
let part_data = tables::Work { self.create_work_priv(
work_id: part.work_id, connection,
parent_work_id: Some(work_id.clone()), part.name,
sequence_number: Some(index as i32), part.parts,
name: part.name, part.persons,
created_at: now, part.instruments,
edited_at: now, Some(&work_id),
last_used_at: now, Some(index as i32),
last_played_at: None, )?;
};
diesel::insert_into(works::table)
.values(&part_data)
.execute(connection)?;
} }
for (index, composer) in persons.into_iter().enumerate() { for (index, composer) in persons.into_iter().enumerate() {
@ -922,20 +930,125 @@ impl MusicusLibrary {
pub fn update_work( pub fn update_work(
&self, &self,
id: &str, work_id: &str,
name: TranslatedString, name: TranslatedString,
parts: Vec<WorkPart>, parts: Vec<Work>,
persons: Vec<Composer>, persons: Vec<Composer>,
instruments: Vec<Instrument>, instruments: Vec<Instrument>,
) -> Result<()> { ) -> Result<()> {
let mut binding = self.imp().connection.borrow_mut(); let mut binding = self.imp().connection.borrow_mut();
let connection = &mut *binding.as_mut().unwrap(); let connection = &mut *binding.as_mut().unwrap();
self.update_work_priv(
connection,
work_id,
name,
parts,
persons,
instruments,
None,
None,
)
}
fn update_work_priv(
&self,
connection: &mut SqliteConnection,
work_id: &str,
name: TranslatedString,
parts: Vec<Work>,
persons: Vec<Composer>,
instruments: Vec<Instrument>,
parent_work_id: Option<&str>,
sequence_number: Option<i32>,
) -> Result<()> {
let now = Local::now().naive_local(); let now = Local::now().naive_local();
// TODO: Update work, check which work parts etc exist, update them, diesel::update(works::table)
// create new work parts, delete and readd composers and instruments. .filter(works::work_id.eq(work_id))
todo!() .set((
works::parent_work_id.eq(parent_work_id),
works::sequence_number.eq(sequence_number),
works::name.eq(name),
works::edited_at.eq(now),
works::last_used_at.eq(now),
))
.execute(connection)?;
diesel::delete(works::table)
.filter(
works::parent_work_id
.eq(work_id)
.and(works::work_id.ne_all(parts.iter().map(|p| p.work_id.clone()))),
)
.execute(connection)?;
for (index, part) in parts.into_iter().enumerate() {
if works::table
.filter(works::work_id.eq(&part.work_id))
.first::<tables::Work>(connection)
.optional()?
.is_some()
{
self.update_work_priv(
connection,
&part.work_id,
part.name,
part.parts,
part.persons,
part.instruments,
Some(work_id),
Some(index as i32),
)?;
} else {
// Note: The previously used ID is discarded. This should be OK, because
// at this point, the part ID should not have been used anywhere.
self.create_work_priv(
connection,
part.name,
part.parts,
part.persons,
part.instruments,
Some(work_id),
Some(index as i32),
)?;
}
}
diesel::delete(work_persons::table)
.filter(work_persons::work_id.eq(work_id))
.execute(connection)?;
for (index, composer) in persons.into_iter().enumerate() {
let composer_data = tables::WorkPerson {
work_id: work_id.to_string(),
person_id: composer.person.person_id,
role_id: composer.role.role_id,
sequence_number: index as i32,
};
diesel::insert_into(work_persons::table)
.values(composer_data)
.execute(connection)?;
}
diesel::delete(work_instruments::table)
.filter(work_instruments::work_id.eq(work_id))
.execute(connection)?;
for (index, instrument) in instruments.into_iter().enumerate() {
let instrument_data = tables::WorkInstrument {
work_id: work_id.to_string(),
instrument_id: instrument.instrument_id,
sequence_number: index as i32,
};
diesel::insert_into(work_instruments::table)
.values(instrument_data)
.execute(connection)?;
}
Ok(())
} }
pub fn create_ensemble(&self, name: TranslatedString) -> Result<Ensemble> { pub fn create_ensemble(&self, name: TranslatedString) -> Result<Ensemble> {
@ -1045,7 +1158,7 @@ impl MusicusLibrary {
pub fn update_recording( pub fn update_recording(
&self, &self,
id: &str, recording_id: &str,
work: Work, work: Work,
year: Option<i32>, year: Option<i32>,
performers: Vec<Performer>, performers: Vec<Performer>,
@ -1056,8 +1169,52 @@ impl MusicusLibrary {
let now = Local::now().naive_local(); let now = Local::now().naive_local();
// TODO: Update recording. diesel::update(recordings::table)
todo!() .filter(recordings::recording_id.eq(recording_id))
.set((
recordings::work_id.eq(work.work_id),
recordings::year.eq(year),
recordings::edited_at.eq(now),
recordings::last_used_at.eq(now),
))
.execute(connection)?;
diesel::delete(recording_persons::table)
.filter(recording_persons::recording_id.eq(recording_id))
.execute(connection)?;
for (index, performer) in performers.into_iter().enumerate() {
let recording_person_data = tables::RecordingPerson {
recording_id: recording_id.to_string(),
person_id: performer.person.person_id,
role_id: performer.role.role_id,
instrument_id: performer.instrument.map(|i| i.instrument_id),
sequence_number: index as i32,
};
diesel::insert_into(recording_persons::table)
.values(&recording_person_data)
.execute(connection)?;
}
diesel::delete(recording_ensembles::table)
.filter(recording_ensembles::recording_id.eq(recording_id))
.execute(connection)?;
for (index, ensemble) in ensembles.into_iter().enumerate() {
let recording_ensemble_data = tables::RecordingEnsemble {
recording_id: recording_id.to_string(),
ensemble_id: ensemble.ensemble.ensemble_id,
role_id: ensemble.role.role_id,
sequence_number: index as i32,
};
diesel::insert_into(recording_ensembles::table)
.values(&recording_ensemble_data)
.execute(connection)?;
}
Ok(())
} }
} }