From 75d4e82cf899dea1e7743c271870aeef6d5703de Mon Sep 17 00:00:00 2001 From: Elias Projahn Date: Sat, 11 Feb 2023 13:35:26 +0100 Subject: [PATCH] Use database connection directly --- crates/backend/src/lib.rs | 6 +- crates/backend/src/library.rs | 12 +- crates/backend/src/player.rs | 9 +- crates/database/src/ensembles.rs | 95 ++-- crates/database/src/instruments.rs | 103 +++-- crates/database/src/lib.rs | 48 +- crates/database/src/medium.rs | 467 ++++++++++---------- crates/database/src/persons.rs | 98 ++-- crates/database/src/recordings.rs | 425 +++++++++--------- crates/database/src/works.rs | 277 ++++++------ crates/musicus/src/editors/ensemble.rs | 4 +- crates/musicus/src/editors/instrument.rs | 11 +- crates/musicus/src/editors/person.rs | 8 +- crates/musicus/src/editors/recording.rs | 11 +- crates/musicus/src/editors/work.rs | 4 +- crates/musicus/src/import/import_screen.rs | 13 +- crates/musicus/src/import/medium_preview.rs | 8 +- crates/musicus/src/screens/ensemble.rs | 26 +- crates/musicus/src/screens/main.rs | 11 +- crates/musicus/src/screens/person.rs | 32 +- crates/musicus/src/screens/recording.rs | 15 +- crates/musicus/src/screens/work.rs | 15 +- crates/musicus/src/selectors/ensemble.rs | 6 +- crates/musicus/src/selectors/instrument.rs | 6 +- crates/musicus/src/selectors/medium.rs | 30 +- crates/musicus/src/selectors/person.rs | 6 +- crates/musicus/src/selectors/recording.rs | 28 +- crates/musicus/src/selectors/work.rs | 8 +- 28 files changed, 893 insertions(+), 889 deletions(-) diff --git a/crates/backend/src/lib.rs b/crates/backend/src/lib.rs index b0d5579..42e61f0 100644 --- a/crates/backend/src/lib.rs +++ b/crates/backend/src/lib.rs @@ -1,11 +1,11 @@ +use db::SqliteConnection; use gio::traits::SettingsExt; use log::warn; -use musicus_database::Database; use std::{ cell::{Cell, RefCell}, path::PathBuf, rc::Rc, - sync::Arc, + sync::{Arc, Mutex}, }; use tokio::sync::{broadcast, broadcast::Sender}; @@ -59,7 +59,7 @@ pub struct Backend { library_updated_sender: Sender<()>, /// The database. This can be assumed to exist, when the state is set to BackendState::Ready. - database: RefCell>>, + database: RefCell>>>, /// The player handling playlist and playback. This can be assumed to exist, when the state is /// set to BackendState::Ready. diff --git a/crates/backend/src/library.rs b/crates/backend/src/library.rs index e4db783..fc74e34 100644 --- a/crates/backend/src/library.rs +++ b/crates/backend/src/library.rs @@ -1,9 +1,10 @@ use crate::{Backend, BackendState, Player, Result}; use gio::prelude::*; use log::warn; -use musicus_database::Database; +use musicus_database::SqliteConnection; use std::path::PathBuf; use std::rc::Rc; +use std::sync::{Arc, Mutex}; impl Backend { /// Initialize the music library if it is set in the settings. @@ -41,8 +42,11 @@ impl Backend { let mut db_path = path.clone(); db_path.push("musicus.db"); - let database = Rc::new(Database::new(db_path.to_str().unwrap())?); - self.database.replace(Some(Rc::clone(&database))); + let database = Arc::new(Mutex::new(musicus_database::connect( + db_path.to_str().unwrap(), + )?)); + + self.database.replace(Some(database)); let player = Player::new(path); self.player.replace(Some(player)); @@ -60,7 +64,7 @@ impl Backend { } /// Get an interface to the database and panic if there is none. - pub fn db(&self) -> Rc { + pub fn db(&self) -> Arc> { self.database.borrow().clone().unwrap() } diff --git a/crates/backend/src/player.rs b/crates/backend/src/player.rs index a05ee0d..df914f4 100644 --- a/crates/backend/src/player.rs +++ b/crates/backend/src/player.rs @@ -1,6 +1,7 @@ use crate::{Backend, Error, Result}; +use db::Track; use glib::clone; -use musicus_database::Track; +use musicus_database as db; use std::cell::{Cell, RefCell}; use std::path::PathBuf; use std::rc::Rc; @@ -458,7 +459,7 @@ impl TrackGenerator for RandomTrackGenerator { } fn next(&self) -> Vec { - vec![self.backend.db().random_track().unwrap()] + vec![db::random_track(&mut self.backend.db().lock().unwrap()).unwrap()] } } @@ -479,7 +480,7 @@ impl TrackGenerator for RandomRecordingGenerator { } fn next(&self) -> Vec { - let recording = self.backend.db().random_recording().unwrap(); - self.backend.db().get_tracks(&recording.id).unwrap() + let recording = db::random_recording(&mut self.backend.db().lock().unwrap()).unwrap(); + db::get_tracks(&mut self.backend.db().lock().unwrap(), &recording.id).unwrap() } } diff --git a/crates/database/src/ensembles.rs b/crates/database/src/ensembles.rs index 1d21ca0..421c73e 100644 --- a/crates/database/src/ensembles.rs +++ b/crates/database/src/ensembles.rs @@ -1,9 +1,9 @@ -use super::schema::ensembles; -use super::{Database, Result}; use chrono::Utc; use diesel::prelude::*; use log::info; +use crate::{Result, schema::ensembles, defer_foreign_keys}; + /// An ensemble that takes part in recordings. #[derive(Insertable, Queryable, PartialEq, Eq, Hash, Debug, Clone)] pub struct Ensemble { @@ -24,54 +24,51 @@ impl Ensemble { } } -impl Database { - /// Update an existing ensemble or insert a new one. - pub fn update_ensemble(&self, mut ensemble: Ensemble) -> Result<()> { - info!("Updating ensemble {:?}", ensemble); - self.defer_foreign_keys()?; +/// Update an existing ensemble or insert a new one. +pub fn update_ensemble(connection: &mut SqliteConnection, mut ensemble: Ensemble) -> Result<()> { + info!("Updating ensemble {:?}", ensemble); + defer_foreign_keys(connection)?; - ensemble.last_used = Some(Utc::now().timestamp()); + ensemble.last_used = Some(Utc::now().timestamp()); - self.connection.lock().unwrap().transaction(|connection| { - diesel::replace_into(ensembles::table) - .values(ensemble) - .execute(connection) - })?; + connection.transaction(|connection| { + diesel::replace_into(ensembles::table) + .values(ensemble) + .execute(connection) + })?; - Ok(()) - } - - /// Get an existing ensemble. - pub fn get_ensemble(&self, id: &str) -> Result> { - let ensemble = ensembles::table - .filter(ensembles::id.eq(id)) - .load::(&mut *self.connection.lock().unwrap())? - .into_iter() - .next(); - - Ok(ensemble) - } - - /// Delete an existing ensemble. - pub fn delete_ensemble(&self, id: &str) -> Result<()> { - info!("Deleting ensemble {}", id); - diesel::delete(ensembles::table.filter(ensembles::id.eq(id))) - .execute(&mut *self.connection.lock().unwrap())?; - Ok(()) - } - - /// Get all existing ensembles. - pub fn get_ensembles(&self) -> Result> { - let ensembles = ensembles::table.load::(&mut *self.connection.lock().unwrap())?; - Ok(ensembles) - } - - /// Get recently used ensembles. - pub fn get_recent_ensembles(&self) -> Result> { - let ensembles = ensembles::table - .order(ensembles::last_used.desc()) - .load::(&mut *self.connection.lock().unwrap())?; - - Ok(ensembles) - } + Ok(()) +} + +/// Get an existing ensemble. +pub fn get_ensemble(connection: &mut SqliteConnection, id: &str) -> Result> { + let ensemble = ensembles::table + .filter(ensembles::id.eq(id)) + .load::(connection)? + .into_iter() + .next(); + + Ok(ensemble) +} + +/// Delete an existing ensemble. +pub fn delete_ensemble(connection: &mut SqliteConnection, id: &str) -> Result<()> { + info!("Deleting ensemble {}", id); + diesel::delete(ensembles::table.filter(ensembles::id.eq(id))).execute(connection)?; + Ok(()) +} + +/// Get all existing ensembles. +pub fn get_ensembles(connection: &mut SqliteConnection) -> Result> { + let ensembles = ensembles::table.load::(connection)?; + Ok(ensembles) +} + +/// Get recently used ensembles. +pub fn get_recent_ensembles(connection: &mut SqliteConnection) -> Result> { + let ensembles = ensembles::table + .order(ensembles::last_used.desc()) + .load::(connection)?; + + Ok(ensembles) } diff --git a/crates/database/src/instruments.rs b/crates/database/src/instruments.rs index 1b3f436..9aabce5 100644 --- a/crates/database/src/instruments.rs +++ b/crates/database/src/instruments.rs @@ -1,9 +1,9 @@ -use super::schema::instruments; -use super::{Database, Result}; use chrono::Utc; use diesel::prelude::*; use log::info; +use crate::{defer_foreign_keys, schema::instruments, Result}; + /// An instrument or any other possible role within a recording. #[derive(Insertable, Queryable, PartialEq, Eq, Hash, Debug, Clone)] pub struct Instrument { @@ -24,57 +24,56 @@ impl Instrument { } } -impl Database { - /// Update an existing instrument or insert a new one. - pub fn update_instrument(&self, mut instrument: Instrument) -> Result<()> { - info!("Updating instrument {:?}", instrument); - self.defer_foreign_keys()?; +/// Update an existing instrument or insert a new one. +pub fn update_instrument( + connection: &mut SqliteConnection, + mut instrument: Instrument, +) -> Result<()> { + info!("Updating instrument {:?}", instrument); + defer_foreign_keys(connection)?; - instrument.last_used = Some(Utc::now().timestamp()); + instrument.last_used = Some(Utc::now().timestamp()); - self.connection.lock().unwrap().transaction(|connection| { - diesel::replace_into(instruments::table) - .values(instrument) - .execute(connection) - })?; + connection.transaction(|connection| { + diesel::replace_into(instruments::table) + .values(instrument) + .execute(connection) + })?; - Ok(()) - } - - /// Get an existing instrument. - pub fn get_instrument(&self, id: &str) -> Result> { - let instrument = instruments::table - .filter(instruments::id.eq(id)) - .load::(&mut *self.connection.lock().unwrap())? - .into_iter() - .next(); - - Ok(instrument) - } - - /// Delete an existing instrument. - pub fn delete_instrument(&self, id: &str) -> Result<()> { - info!("Deleting instrument {}", id); - diesel::delete(instruments::table.filter(instruments::id.eq(id))) - .execute(&mut *self.connection.lock().unwrap())?; - - Ok(()) - } - - /// Get all existing instruments. - pub fn get_instruments(&self) -> Result> { - let instruments = - instruments::table.load::(&mut *self.connection.lock().unwrap())?; - - Ok(instruments) - } - - /// Get recently used instruments. - pub fn get_recent_instruments(&self) -> Result> { - let instruments = instruments::table - .order(instruments::last_used.desc()) - .load::(&mut *self.connection.lock().unwrap())?; - - Ok(instruments) - } + Ok(()) +} + +/// Get an existing instrument. +pub fn get_instrument(connection: &mut SqliteConnection, id: &str) -> Result> { + let instrument = instruments::table + .filter(instruments::id.eq(id)) + .load::(connection)? + .into_iter() + .next(); + + Ok(instrument) +} + +/// Delete an existing instrument. +pub fn delete_instrument(connection: &mut SqliteConnection, id: &str) -> Result<()> { + info!("Deleting instrument {}", id); + diesel::delete(instruments::table.filter(instruments::id.eq(id))).execute(connection)?; + + Ok(()) +} + +/// Get all existing instruments. +pub fn get_instruments(connection: &mut SqliteConnection) -> Result> { + let instruments = instruments::table.load::(connection)?; + + Ok(instruments) +} + +/// Get recently used instruments. +pub fn get_recent_instruments(connection: &mut SqliteConnection) -> Result> { + let instruments = instruments::table + .order(instruments::last_used.desc()) + .load::(connection)?; + + Ok(instruments) } diff --git a/crates/database/src/lib.rs b/crates/database/src/lib.rs index 3dc7780..50aa92d 100644 --- a/crates/database/src/lib.rs +++ b/crates/database/src/lib.rs @@ -1,9 +1,9 @@ -use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; -use std::sync::{Arc, Mutex}; - use diesel::prelude::*; +use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; use log::info; +pub use diesel::SqliteConnection; + pub mod ensembles; pub use ensembles::*; @@ -30,35 +30,25 @@ mod schema; // This makes the SQL migration scripts accessible from the code. const MIGRATIONS: EmbeddedMigrations = embed_migrations!(); +/// Connect to a Musicus database running migrations if necessary. +pub fn connect(file_name: &str) -> Result { + info!("Opening database file '{}'", file_name); + let mut connection = SqliteConnection::establish(file_name)?; + diesel::sql_query("PRAGMA foreign_keys = ON").execute(&mut connection)?; + + info!("Running migrations if necessary"); + connection.run_pending_migrations(MIGRATIONS)?; + + Ok(connection) +} + /// Generate a random string suitable as an item ID. pub fn generate_id() -> String { uuid::Uuid::new_v4().simple().to_string() } -/// Interface to a Musicus database. -pub struct Database { - connection: Arc>, -} - -impl Database { - /// Create a new database interface and run migrations if necessary. - pub fn new(file_name: &str) -> Result { - info!("Opening database file '{}'", file_name); - let mut connection = SqliteConnection::establish(file_name)?; - diesel::sql_query("PRAGMA foreign_keys = ON").execute(&mut connection)?; - - info!("Running migrations if necessary"); - connection.run_pending_migrations(MIGRATIONS)?; - - Ok(Database { - connection: Arc::new(Mutex::new(connection)), - }) - } - - /// Defer all foreign keys for the next transaction. - fn defer_foreign_keys(&self) -> Result<()> { - diesel::sql_query("PRAGMA defer_foreign_keys = ON") - .execute(&mut *self.connection.lock().unwrap())?; - Ok(()) - } +/// Defer all foreign keys for the next transaction. +fn defer_foreign_keys(connection: &mut SqliteConnection) -> Result<()> { + diesel::sql_query("PRAGMA defer_foreign_keys = ON").execute(connection)?; + Ok(()) } diff --git a/crates/database/src/medium.rs b/crates/database/src/medium.rs index d475287..4ae34e2 100644 --- a/crates/database/src/medium.rs +++ b/crates/database/src/medium.rs @@ -1,10 +1,13 @@ -use super::generate_id; -use super::schema::{ensembles, mediums, performances, persons, recordings, tracks}; -use super::{Database, Error, Recording, Result}; use chrono::{DateTime, TimeZone, Utc}; use diesel::prelude::*; use log::info; +use crate::{ + defer_foreign_keys, generate_id, get_recording, + schema::{ensembles, mediums, performances, persons, recordings, tracks}, + update_recording, Error, Recording, Result, +}; + /// Representation of someting like a physical audio disc or a folder with /// audio files (i.e. a collection of tracks for one or more recordings). #[derive(PartialEq, Eq, Hash, Debug, Clone)] @@ -103,244 +106,246 @@ struct TrackRow { pub last_played: Option, } -impl Database { - /// Update an existing medium or insert a new one. - pub fn update_medium(&self, medium: Medium) -> Result<()> { - info!("Updating medium {:?}", medium); - self.defer_foreign_keys()?; +/// Update an existing medium or insert a new one. +pub fn update_medium(connection: &mut SqliteConnection, medium: Medium) -> Result<()> { + info!("Updating medium {:?}", medium); + defer_foreign_keys(connection)?; - self.connection - .lock() - .unwrap() - .transaction::<(), Error, _>(|connection| { - let medium_id = &medium.id; + connection.transaction::<(), Error, _>(|connection| { + let medium_id = &medium.id; - // This will also delete the tracks. - self.delete_medium(medium_id)?; + // This will also delete the tracks. + delete_medium(connection, medium_id)?; - // Add the new medium. + // Add the new medium. - let medium_row = MediumRow { - id: medium_id.to_owned(), - name: medium.name.clone(), - discid: medium.discid.clone(), - last_used: Some(Utc::now().timestamp()), - last_played: medium.last_played.map(|t| t.timestamp()), - }; - - diesel::insert_into(mediums::table) - .values(medium_row) - .execute(connection)?; - - for (index, track) in medium.tracks.iter().enumerate() { - // Add associated items from the server, if they don't already exist. - - if self.get_recording(&track.recording.id)?.is_none() { - self.update_recording(track.recording.clone())?; - } - - // Add the actual track data. - - let work_parts = track - .work_parts - .iter() - .map(|part_index| part_index.to_string()) - .collect::>() - .join(","); - - let track_row = TrackRow { - id: generate_id(), - medium: Some(medium_id.to_owned()), - index: index as i32, - recording: track.recording.id.clone(), - work_parts, - source_index: track.source_index as i32, - path: track.path.clone(), - last_used: Some(Utc::now().timestamp()), - last_played: track.last_played.map(|t| t.timestamp()), - }; - - diesel::insert_into(tracks::table) - .values(track_row) - .execute(connection)?; - } - - Ok(()) - })?; - - Ok(()) - } - - /// Get an existing medium. - pub fn get_medium(&self, id: &str) -> Result> { - let row = mediums::table - .filter(mediums::id.eq(id)) - .load::(&mut *self.connection.lock().unwrap())? - .into_iter() - .next(); - - let medium = match row { - Some(row) => Some(self.get_medium_data(row)?), - None => None, + let medium_row = MediumRow { + id: medium_id.to_owned(), + name: medium.name.clone(), + discid: medium.discid.clone(), + last_used: Some(Utc::now().timestamp()), + last_played: medium.last_played.map(|t| t.timestamp()), }; - Ok(medium) - } + diesel::insert_into(mediums::table) + .values(medium_row) + .execute(connection)?; - /// Get mediums that have a specific source ID. - pub fn get_mediums_by_source_id(&self, source_id: &str) -> Result> { - let mut mediums: Vec = Vec::new(); + for (index, track) in medium.tracks.iter().enumerate() { + // Add associated items from the server, if they don't already exist. - let rows = mediums::table - .filter(mediums::discid.nullable().eq(source_id)) - .load::(&mut *self.connection.lock().unwrap())?; - - for row in rows { - let medium = self.get_medium_data(row)?; - mediums.push(medium); - } - - Ok(mediums) - } - - /// Get mediums on which this person is performing. - pub fn get_mediums_for_person(&self, person_id: &str) -> Result> { - let mut mediums: Vec = Vec::new(); - - let rows = mediums::table - .inner_join(tracks::table.on(tracks::medium.eq(mediums::id.nullable()))) - .inner_join(recordings::table.on(recordings::id.eq(tracks::recording))) - .inner_join(performances::table.on(performances::recording.eq(recordings::id))) - .inner_join(persons::table.on(persons::id.nullable().eq(performances::person))) - .filter(persons::id.eq(person_id)) - .select(mediums::table::all_columns()) - .distinct() - .load::(&mut *self.connection.lock().unwrap())?; - - for row in rows { - let medium = self.get_medium_data(row)?; - mediums.push(medium); - } - - Ok(mediums) - } - - /// Get mediums on which this ensemble is performing. - pub fn get_mediums_for_ensemble(&self, ensemble_id: &str) -> Result> { - let mut mediums: Vec = Vec::new(); - - let rows = mediums::table - .inner_join(tracks::table.on(tracks::medium.eq(tracks::id.nullable()))) - .inner_join(recordings::table.on(recordings::id.eq(tracks::recording))) - .inner_join(performances::table.on(performances::recording.eq(recordings::id))) - .inner_join(ensembles::table.on(ensembles::id.nullable().eq(performances::ensemble))) - .filter(ensembles::id.eq(ensemble_id)) - .select(mediums::table::all_columns()) - .distinct() - .load::(&mut *self.connection.lock().unwrap())?; - - for row in rows { - let medium = self.get_medium_data(row)?; - mediums.push(medium); - } - - Ok(mediums) - } - - /// Delete a medium and all of its tracks. This will fail, if the music - /// library contains audio files referencing any of those tracks. - pub fn delete_medium(&self, id: &str) -> Result<()> { - info!("Deleting medium {}", id); - diesel::delete(mediums::table.filter(mediums::id.eq(id))) - .execute(&mut *self.connection.lock().unwrap())?; - Ok(()) - } - - /// Get all available tracks for a recording. - pub fn get_tracks(&self, recording_id: &str) -> Result> { - let mut tracks: Vec = Vec::new(); - - let rows = tracks::table - .inner_join(recordings::table.on(recordings::id.eq(tracks::recording))) - .filter(recordings::id.eq(recording_id)) - .select(tracks::table::all_columns()) - .load::(&mut *self.connection.lock().unwrap())?; - - for row in rows { - let track = self.get_track_from_row(row)?; - tracks.push(track); - } - - Ok(tracks) - } - - /// Get a random track from the database. - pub fn random_track(&self) -> Result { - let row = diesel::sql_query("SELECT * FROM tracks ORDER BY RANDOM() LIMIT 1") - .load::(&mut *self.connection.lock().unwrap())? - .into_iter() - .next() - .ok_or(Error::Other("Failed to generate random track"))?; - - self.get_track_from_row(row) - } - - /// Retrieve all available information on a medium from related tables. - fn get_medium_data(&self, row: MediumRow) -> Result { - let track_rows = tracks::table - .filter(tracks::medium.eq(&row.id)) - .order_by(tracks::index) - .load::(&mut *self.connection.lock().unwrap())?; - - let mut tracks = Vec::new(); - - for track_row in track_rows { - let track = self.get_track_from_row(track_row)?; - tracks.push(track); - } - - let medium = Medium { - id: row.id, - name: row.name, - discid: row.discid, - tracks, - last_used: row.last_used.map(|t| Utc.timestamp_opt(t, 0).unwrap()), - last_played: row.last_played.map(|t| Utc.timestamp_opt(t, 0).unwrap()), - }; - - Ok(medium) - } - - /// Convert a track row from the database to an actual track. - fn get_track_from_row(&self, row: TrackRow) -> Result { - let recording_id = row.recording; - - let recording = self - .get_recording(&recording_id)? - .ok_or(Error::MissingItem("recording", recording_id))?; - - let mut part_indices = Vec::new(); - - let work_parts = row.work_parts.split(','); - - for part_index in work_parts { - if !part_index.is_empty() { - let index = str::parse(part_index) - .map_err(|_| Error::ParsingError("part index", String::from(part_index)))?; - - part_indices.push(index); + if get_recording(connection, &track.recording.id)?.is_none() { + update_recording(connection, track.recording.clone())?; } + + // Add the actual track data. + + let work_parts = track + .work_parts + .iter() + .map(|part_index| part_index.to_string()) + .collect::>() + .join(","); + + let track_row = TrackRow { + id: generate_id(), + medium: Some(medium_id.to_owned()), + index: index as i32, + recording: track.recording.id.clone(), + work_parts, + source_index: track.source_index as i32, + path: track.path.clone(), + last_used: Some(Utc::now().timestamp()), + last_played: track.last_played.map(|t| t.timestamp()), + }; + + diesel::insert_into(tracks::table) + .values(track_row) + .execute(connection)?; } - let track = Track { - recording, - work_parts: part_indices, - source_index: row.source_index as usize, - path: row.path, - last_used: row.last_used.map(|t| Utc.timestamp_opt(t, 0).unwrap()), - last_played: row.last_played.map(|t| Utc.timestamp_opt(t, 0).unwrap()), - }; + Ok(()) + })?; - Ok(track) - } + Ok(()) +} + +/// Get an existing medium. +pub fn get_medium(connection: &mut SqliteConnection, id: &str) -> Result> { + let row = mediums::table + .filter(mediums::id.eq(id)) + .load::(connection)? + .into_iter() + .next(); + + let medium = match row { + Some(row) => Some(get_medium_data(connection, row)?), + None => None, + }; + + Ok(medium) +} + +/// Get mediums that have a specific source ID. +pub fn get_mediums_by_source_id( + connection: &mut SqliteConnection, + source_id: &str, +) -> Result> { + let mut mediums: Vec = Vec::new(); + + let rows = mediums::table + .filter(mediums::discid.nullable().eq(source_id)) + .load::(connection)?; + + for row in rows { + let medium = get_medium_data(connection, row)?; + mediums.push(medium); + } + + Ok(mediums) +} + +/// Get mediums on which this person is performing. +pub fn get_mediums_for_person( + connection: &mut SqliteConnection, + person_id: &str, +) -> Result> { + let mut mediums: Vec = Vec::new(); + + let rows = mediums::table + .inner_join(tracks::table.on(tracks::medium.eq(mediums::id.nullable()))) + .inner_join(recordings::table.on(recordings::id.eq(tracks::recording))) + .inner_join(performances::table.on(performances::recording.eq(recordings::id))) + .inner_join(persons::table.on(persons::id.nullable().eq(performances::person))) + .filter(persons::id.eq(person_id)) + .select(mediums::table::all_columns()) + .distinct() + .load::(connection)?; + + for row in rows { + let medium = get_medium_data(connection, row)?; + mediums.push(medium); + } + + Ok(mediums) +} + +/// Get mediums on which this ensemble is performing. +pub fn get_mediums_for_ensemble( + connection: &mut SqliteConnection, + ensemble_id: &str, +) -> Result> { + let mut mediums: Vec = Vec::new(); + + let rows = mediums::table + .inner_join(tracks::table.on(tracks::medium.eq(tracks::id.nullable()))) + .inner_join(recordings::table.on(recordings::id.eq(tracks::recording))) + .inner_join(performances::table.on(performances::recording.eq(recordings::id))) + .inner_join(ensembles::table.on(ensembles::id.nullable().eq(performances::ensemble))) + .filter(ensembles::id.eq(ensemble_id)) + .select(mediums::table::all_columns()) + .distinct() + .load::(connection)?; + + for row in rows { + let medium = get_medium_data(connection, row)?; + mediums.push(medium); + } + + Ok(mediums) +} + +/// Delete a medium and all of its tracks. This will fail, if the music +/// library contains audio files referencing any of those tracks. +pub fn delete_medium(connection: &mut SqliteConnection, id: &str) -> Result<()> { + info!("Deleting medium {}", id); + diesel::delete(mediums::table.filter(mediums::id.eq(id))).execute(connection)?; + Ok(()) +} + +/// Get all available tracks for a recording. +pub fn get_tracks(connection: &mut SqliteConnection, recording_id: &str) -> Result> { + let mut tracks: Vec = Vec::new(); + + let rows = tracks::table + .inner_join(recordings::table.on(recordings::id.eq(tracks::recording))) + .filter(recordings::id.eq(recording_id)) + .select(tracks::table::all_columns()) + .load::(connection)?; + + for row in rows { + let track = get_track_from_row(connection, row)?; + tracks.push(track); + } + + Ok(tracks) +} + +/// Get a random track from the database. +pub fn random_track(connection: &mut SqliteConnection) -> Result { + let row = diesel::sql_query("SELECT * FROM tracks ORDER BY RANDOM() LIMIT 1") + .load::(connection)? + .into_iter() + .next() + .ok_or(Error::Other("Failed to generate random track"))?; + + get_track_from_row(connection, row) +} + +/// Retrieve all available information on a medium from related tables. +fn get_medium_data(connection: &mut SqliteConnection, row: MediumRow) -> Result { + let track_rows = tracks::table + .filter(tracks::medium.eq(&row.id)) + .order_by(tracks::index) + .load::(connection)?; + + let mut tracks = Vec::new(); + + for track_row in track_rows { + let track = get_track_from_row(connection, track_row)?; + tracks.push(track); + } + + let medium = Medium { + id: row.id, + name: row.name, + discid: row.discid, + tracks, + last_used: row.last_used.map(|t| Utc.timestamp_opt(t, 0).unwrap()), + last_played: row.last_played.map(|t| Utc.timestamp_opt(t, 0).unwrap()), + }; + + Ok(medium) +} + +/// Convert a track row from the database to an actual track. +fn get_track_from_row(connection: &mut SqliteConnection, row: TrackRow) -> Result { + let recording_id = row.recording; + + let recording = get_recording(connection, &recording_id)? + .ok_or(Error::MissingItem("recording", recording_id))?; + + let mut part_indices = Vec::new(); + + let work_parts = row.work_parts.split(','); + + for part_index in work_parts { + if !part_index.is_empty() { + let index = str::parse(part_index) + .map_err(|_| Error::ParsingError("part index", String::from(part_index)))?; + + part_indices.push(index); + } + } + + let track = Track { + recording, + work_parts: part_indices, + source_index: row.source_index as usize, + path: row.path, + last_used: row.last_used.map(|t| Utc.timestamp_opt(t, 0).unwrap()), + last_played: row.last_played.map(|t| Utc.timestamp_opt(t, 0).unwrap()), + }; + + Ok(track) } diff --git a/crates/database/src/persons.rs b/crates/database/src/persons.rs index 6c401c3..157bb1c 100644 --- a/crates/database/src/persons.rs +++ b/crates/database/src/persons.rs @@ -1,9 +1,9 @@ -use super::schema::persons; -use super::{Database, Result}; use chrono::Utc; use diesel::prelude::*; use log::info; +use crate::{defer_foreign_keys, schema::persons, Result}; + /// A person that is a composer, an interpret or both. #[derive(Insertable, Queryable, PartialEq, Eq, Hash, Debug, Clone)] pub struct Person { @@ -35,56 +35,52 @@ impl Person { format!("{}, {}", self.last_name, self.first_name) } } +/// Update an existing person or insert a new one. +pub fn update_person(connection: &mut SqliteConnection, mut person: Person) -> Result<()> { + info!("Updating person {:?}", person); + defer_foreign_keys(connection)?; -impl Database { - /// Update an existing person or insert a new one. - pub fn update_person(&self, mut person: Person) -> Result<()> { - info!("Updating person {:?}", person); - self.defer_foreign_keys()?; + person.last_used = Some(Utc::now().timestamp()); - person.last_used = Some(Utc::now().timestamp()); + connection.transaction(|connection| { + diesel::replace_into(persons::table) + .values(person) + .execute(connection) + })?; - self.connection.lock().unwrap().transaction(|connection| { - diesel::replace_into(persons::table) - .values(person) - .execute(connection) - })?; - - Ok(()) - } - - /// Get an existing person. - pub fn get_person(&self, id: &str) -> Result> { - let person = persons::table - .filter(persons::id.eq(id)) - .load::(&mut *self.connection.lock().unwrap())? - .into_iter() - .next(); - - Ok(person) - } - - /// Delete an existing person. - pub fn delete_person(&self, id: &str) -> Result<()> { - info!("Deleting person {}", id); - diesel::delete(persons::table.filter(persons::id.eq(id))) - .execute(&mut *self.connection.lock().unwrap())?; - Ok(()) - } - - /// Get all existing persons. - pub fn get_persons(&self) -> Result> { - let persons = persons::table.load::(&mut *self.connection.lock().unwrap())?; - - Ok(persons) - } - - /// Get recently used persons. - pub fn get_recent_persons(&self) -> Result> { - let persons = persons::table - .order(persons::last_used.desc()) - .load::(&mut *self.connection.lock().unwrap())?; - - Ok(persons) - } + Ok(()) +} + +/// Get an existing person. +pub fn get_person(connection: &mut SqliteConnection, id: &str) -> Result> { + let person = persons::table + .filter(persons::id.eq(id)) + .load::(connection)? + .into_iter() + .next(); + + Ok(person) +} + +/// Delete an existing person. +pub fn delete_person(connection: &mut SqliteConnection, id: &str) -> Result<()> { + info!("Deleting person {}", id); + diesel::delete(persons::table.filter(persons::id.eq(id))).execute(connection)?; + Ok(()) +} + +/// Get all existing persons. +pub fn get_persons(connection: &mut SqliteConnection) -> Result> { + let persons = persons::table.load::(connection)?; + + Ok(persons) +} + +/// Get recently used persons. +pub fn get_recent_persons(connection: &mut SqliteConnection) -> Result> { + let persons = persons::table + .order(persons::last_used.desc()) + .load::(connection)?; + + Ok(persons) } diff --git a/crates/database/src/recordings.rs b/crates/database/src/recordings.rs index 16a2928..b62f488 100644 --- a/crates/database/src/recordings.rs +++ b/crates/database/src/recordings.rs @@ -1,10 +1,14 @@ -use super::generate_id; -use super::schema::{ensembles, performances, persons, recordings}; -use super::{Database, Ensemble, Error, Instrument, Person, Result, Work}; use chrono::{DateTime, TimeZone, Utc}; use diesel::prelude::*; use log::info; +use crate::{ + defer_foreign_keys, generate_id, get_ensemble, get_instrument, get_person, get_work, + schema::{ensembles, performances, persons, recordings}, + update_ensemble, update_instrument, update_person, update_work, Ensemble, Error, Instrument, + Person, Result, Work, +}; + /// A specific recording of a work. #[derive(PartialEq, Eq, Hash, Debug, Clone)] pub struct Recording { @@ -125,223 +129,222 @@ struct PerformanceRow { pub role: Option, } -impl Database { - /// Update an existing recording or insert a new one. - // TODO: Think about whether to also insert the other items. - pub fn update_recording(&self, recording: Recording) -> Result<()> { - info!("Updating recording {:?}", recording); - self.defer_foreign_keys()?; - self.connection - .lock() - .unwrap() - .transaction::<(), Error, _>(|connection| { - let recording_id = &recording.id; - self.delete_recording(recording_id)?; +/// Update an existing recording or insert a new one. +// TODO: Think about whether to also insert the other items. +pub fn update_recording(connection: &mut SqliteConnection, recording: Recording) -> Result<()> { + info!("Updating recording {:?}", recording); + defer_foreign_keys(connection)?; - // Add associated items from the server, if they don't already exist. + connection.transaction::<(), Error, _>(|connection| { + let recording_id = &recording.id; + delete_recording(connection, recording_id)?; - if self.get_work(&recording.work.id)?.is_none() { - self.update_work(recording.work.clone())?; - } + // Add associated items from the server, if they don't already exist. - for performance in &recording.performances { - match &performance.performer { - PersonOrEnsemble::Person(person) => { - if self.get_person(&person.id)?.is_none() { - self.update_person(person.clone())?; - } - } - PersonOrEnsemble::Ensemble(ensemble) => { - if self.get_ensemble(&ensemble.id)?.is_none() { - self.update_ensemble(ensemble.clone())?; - } - } - } + if get_work(connection, &recording.work.id)?.is_none() { + update_work(connection, recording.work.clone())?; + } - if let Some(role) = &performance.role { - if self.get_instrument(&role.id)?.is_none() { - self.update_instrument(role.clone())?; - } + for performance in &recording.performances { + match &performance.performer { + PersonOrEnsemble::Person(person) => { + if get_person(connection, &person.id)?.is_none() { + update_person(connection, person.clone())?; } } - - // Add the actual recording. - - let row: RecordingRow = recording.clone().into(); - diesel::insert_into(recordings::table) - .values(row) - .execute(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, - ensemble, - role: performance.role.map(|role| role.id), - }; - - diesel::insert_into(performances::table) - .values(row) - .execute(connection)?; + PersonOrEnsemble::Ensemble(ensemble) => { + if get_ensemble(connection, &ensemble.id)?.is_none() { + update_ensemble(connection, ensemble.clone())?; + } } + } - Ok(()) - })?; + if let Some(role) = &performance.role { + if get_instrument(connection, &role.id)?.is_none() { + update_instrument(connection, role.clone())?; + } + } + } + + // Add the actual recording. + + let row: RecordingRow = recording.clone().into(); + diesel::insert_into(recordings::table) + .values(row) + .execute(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, + ensemble, + role: performance.role.map(|role| role.id), + }; + + diesel::insert_into(performances::table) + .values(row) + .execute(connection)?; + } Ok(()) - } + })?; - /// Check whether the database contains a recording. - pub fn recording_exists(&self, id: &str) -> Result { - let exists = recordings::table - .filter(recordings::id.eq(id)) - .load::(&mut *self.connection.lock().unwrap())? - .first() - .is_some(); - - Ok(exists) - } - - /// Get an existing recording. - pub fn get_recording(&self, id: &str) -> Result> { - let row = recordings::table - .filter(recordings::id.eq(id)) - .load::(&mut *self.connection.lock().unwrap())? - .into_iter() - .next(); - - let recording = match row { - Some(row) => Some(self.get_recording_data(row)?), - None => None, - }; - - Ok(recording) - } - - /// Get a random recording from the database. - pub fn random_recording(&self) -> Result { - let row = diesel::sql_query("SELECT * FROM recordings ORDER BY RANDOM() LIMIT 1") - .load::(&mut *self.connection.lock().unwrap())? - .into_iter() - .next() - .ok_or(Error::Other("Failed to find random recording."))?; - - self.get_recording_data(row) - } - - /// Retrieve all available information on a recording from related tables. - fn get_recording_data(&self, row: RecordingRow) -> Result { - let mut performance_descriptions: Vec = Vec::new(); - - let performance_rows = performances::table - .filter(performances::recording.eq(&row.id)) - .load::(&mut *self.connection.lock().unwrap())?; - - for row in performance_rows { - performance_descriptions.push(Performance { - performer: if let Some(id) = row.person { - PersonOrEnsemble::Person( - self.get_person(&id)? - .ok_or(Error::MissingItem("person", id))?, - ) - } else if let Some(id) = row.ensemble { - PersonOrEnsemble::Ensemble( - self.get_ensemble(&id)? - .ok_or(Error::MissingItem("ensemble", id))?, - ) - } else { - return Err(Error::Other("Performance without performer")); - }, - role: match row.role { - Some(id) => Some( - self.get_instrument(&id)? - .ok_or(Error::MissingItem("instrument", id))?, - ), - None => None, - }, - }); - } - - let work_id = row.work; - let work = self - .get_work(&work_id)? - .ok_or(Error::MissingItem("work", work_id))?; - - let recording_description = Recording { - id: row.id, - work, - comment: row.comment, - performances: performance_descriptions, - last_used: row.last_used.map(|t| Utc.timestamp_opt(t, 0).unwrap()), - last_played: row.last_played.map(|t| Utc.timestamp_opt(t, 0).unwrap()), - }; - - Ok(recording_description) - } - - /// Get all available information on all recordings where a person is performing. - pub fn get_recordings_for_person(&self, person_id: &str) -> Result> { - let mut recordings: Vec = Vec::new(); - - let rows = recordings::table - .inner_join(performances::table.on(performances::recording.eq(recordings::id))) - .inner_join(persons::table.on(persons::id.nullable().eq(performances::person))) - .filter(persons::id.eq(person_id)) - .select(recordings::table::all_columns()) - .load::(&mut *self.connection.lock().unwrap())?; - - for row in rows { - recordings.push(self.get_recording_data(row)?); - } - - Ok(recordings) - } - - /// Get all available information on all recordings where an ensemble is performing. - pub fn get_recordings_for_ensemble(&self, ensemble_id: &str) -> Result> { - let mut recordings: Vec = Vec::new(); - - let rows = recordings::table - .inner_join(performances::table.on(performances::recording.eq(recordings::id))) - .inner_join(ensembles::table.on(ensembles::id.nullable().eq(performances::ensemble))) - .filter(ensembles::id.eq(ensemble_id)) - .select(recordings::table::all_columns()) - .load::(&mut *self.connection.lock().unwrap())?; - - for row in rows { - recordings.push(self.get_recording_data(row)?); - } - - Ok(recordings) - } - - /// Get allavailable information on all recordings of a work. - pub fn get_recordings_for_work(&self, work_id: &str) -> Result> { - let mut recordings: Vec = Vec::new(); - - let rows = recordings::table - .filter(recordings::work.eq(work_id)) - .load::(&mut *self.connection.lock().unwrap())?; - - for row in rows { - recordings.push(self.get_recording_data(row)?); - } - - Ok(recordings) - } - - /// Delete an existing recording. This will fail if there are still references to this - /// recording from other tables that are not directly part of the recording data. - pub fn delete_recording(&self, id: &str) -> Result<()> { - info!("Deleting recording {}", id); - diesel::delete(recordings::table.filter(recordings::id.eq(id))) - .execute(&mut *self.connection.lock().unwrap())?; - Ok(()) - } + Ok(()) +} + +/// Check whether the database contains a recording. +pub fn recording_exists(connection: &mut SqliteConnection, id: &str) -> Result { + let exists = recordings::table + .filter(recordings::id.eq(id)) + .load::(connection)? + .first() + .is_some(); + + Ok(exists) +} + +/// Get an existing recording. +pub fn get_recording(connection: &mut SqliteConnection, id: &str) -> Result> { + let row = recordings::table + .filter(recordings::id.eq(id)) + .load::(connection)? + .into_iter() + .next(); + + let recording = match row { + Some(row) => Some(get_recording_data(connection, row)?), + None => None, + }; + + Ok(recording) +} + +/// Get a random recording from the database. +pub fn random_recording(connection: &mut SqliteConnection) -> Result { + let row = diesel::sql_query("SELECT * FROM recordings ORDER BY RANDOM() LIMIT 1") + .load::(connection)? + .into_iter() + .next() + .ok_or(Error::Other("Failed to find random recording."))?; + + get_recording_data(connection, row) +} + +/// Retrieve all available information on a recording from related tables. +fn get_recording_data(connection: &mut SqliteConnection, row: RecordingRow) -> Result { + let mut performance_descriptions: Vec = Vec::new(); + + let performance_rows = performances::table + .filter(performances::recording.eq(&row.id)) + .load::(connection)?; + + for row in performance_rows { + performance_descriptions.push(Performance { + performer: if let Some(id) = row.person { + PersonOrEnsemble::Person( + get_person(connection, &id)?.ok_or(Error::MissingItem("person", id))?, + ) + } else if let Some(id) = row.ensemble { + PersonOrEnsemble::Ensemble( + get_ensemble(connection, &id)?.ok_or(Error::MissingItem("ensemble", id))?, + ) + } else { + return Err(Error::Other("Performance without performer")); + }, + role: match row.role { + Some(id) => Some( + get_instrument(connection, &id)?.ok_or(Error::MissingItem("instrument", id))?, + ), + None => None, + }, + }); + } + + let work_id = row.work; + let work = get_work(connection, &work_id)?.ok_or(Error::MissingItem("work", work_id))?; + + let recording_description = Recording { + id: row.id, + work, + comment: row.comment, + performances: performance_descriptions, + last_used: row.last_used.map(|t| Utc.timestamp_opt(t, 0).unwrap()), + last_played: row.last_played.map(|t| Utc.timestamp_opt(t, 0).unwrap()), + }; + + Ok(recording_description) +} + +/// Get all available information on all recordings where a person is performing. +pub fn get_recordings_for_person( + connection: &mut SqliteConnection, + person_id: &str, +) -> Result> { + let mut recordings: Vec = Vec::new(); + + let rows = recordings::table + .inner_join(performances::table.on(performances::recording.eq(recordings::id))) + .inner_join(persons::table.on(persons::id.nullable().eq(performances::person))) + .filter(persons::id.eq(person_id)) + .select(recordings::table::all_columns()) + .load::(connection)?; + + for row in rows { + recordings.push(get_recording_data(connection, row)?); + } + + Ok(recordings) +} + +/// Get all available information on all recordings where an ensemble is performing. +pub fn get_recordings_for_ensemble( + connection: &mut SqliteConnection, + ensemble_id: &str, +) -> Result> { + let mut recordings: Vec = Vec::new(); + + let rows = recordings::table + .inner_join(performances::table.on(performances::recording.eq(recordings::id))) + .inner_join(ensembles::table.on(ensembles::id.nullable().eq(performances::ensemble))) + .filter(ensembles::id.eq(ensemble_id)) + .select(recordings::table::all_columns()) + .load::(connection)?; + + for row in rows { + recordings.push(get_recording_data(connection, row)?); + } + + Ok(recordings) +} + +/// Get allavailable information on all recordings of a work. +pub fn get_recordings_for_work( + connection: &mut SqliteConnection, + work_id: &str, +) -> Result> { + let mut recordings: Vec = Vec::new(); + + let rows = recordings::table + .filter(recordings::work.eq(work_id)) + .load::(connection)?; + + for row in rows { + recordings.push(get_recording_data(connection, row)?); + } + + Ok(recordings) +} + +/// Delete an existing recording. This will fail if there are still references to this +/// recording from other tables that are not directly part of the recording data. +pub fn delete_recording(connection: &mut SqliteConnection, id: &str) -> Result<()> { + info!("Deleting recording {}", id); + diesel::delete(recordings::table.filter(recordings::id.eq(id))).execute(connection)?; + Ok(()) } diff --git a/crates/database/src/works.rs b/crates/database/src/works.rs index 776bab2..ecb8a2c 100644 --- a/crates/database/src/works.rs +++ b/crates/database/src/works.rs @@ -1,11 +1,13 @@ -use super::generate_id; -use super::schema::{instrumentations, work_parts, works}; -use super::{Database, Error, Instrument, Person, Result}; use chrono::{DateTime, TimeZone, Utc}; -use diesel::prelude::*; -use diesel::{Insertable, Queryable}; +use diesel::{prelude::*, Insertable, Queryable}; use log::info; +use crate::{ + defer_foreign_keys, generate_id, get_instrument, get_person, + schema::{instrumentations, work_parts, works}, + update_instrument, update_person, Error, Instrument, Person, Result, +}; + /// Table row data for a work. #[derive(Insertable, Queryable, Debug, Clone)] #[table_name = "works"] @@ -105,155 +107,146 @@ impl Work { } } -impl Database { - /// Update an existing work or insert a new one. - // TODO: Think about also inserting related items. - pub fn update_work(&self, work: Work) -> Result<()> { - info!("Updating work {:?}", work); - self.defer_foreign_keys()?; +/// Update an existing work or insert a new one. +// TODO: Think about also inserting related items. +pub fn update_work(connection: &mut SqliteConnection, work: Work) -> Result<()> { + info!("Updating work {:?}", work); + defer_foreign_keys(connection)?; - self.connection - .lock() - .unwrap() - .transaction::<(), Error, _>(|connection| { - let work_id = &work.id; - self.delete_work(work_id)?; + connection.transaction::<(), Error, _>(|connection| { + let work_id = &work.id; + delete_work(connection, work_id)?; - // Add associated items from the server, if they don't already exist. + // Add associated items from the server, if they don't already exist. - if self.get_person(&work.composer.id)?.is_none() { - self.update_person(work.composer.clone())?; - } + if get_person(connection, &work.composer.id)?.is_none() { + update_person(connection, work.composer.clone())?; + } - for instrument in &work.instruments { - if self.get_instrument(&instrument.id)?.is_none() { - self.update_instrument(instrument.clone())?; - } - } + for instrument in &work.instruments { + if get_instrument(connection, &instrument.id)?.is_none() { + update_instrument(connection, instrument.clone())?; + } + } - // Add the actual work. + // Add the actual work. - let row: WorkRow = work.clone().into(); - diesel::insert_into(works::table) - .values(row) - .execute(&mut *self.connection.lock().unwrap())?; + let row: WorkRow = work.clone().into(); + diesel::insert_into(works::table) + .values(row) + .execute(connection)?; - let Work { - instruments, parts, .. - } = work; + let Work { + instruments, parts, .. + } = work; - for instrument in instruments { - let row = InstrumentationRow { - id: rand::random(), - work: work_id.to_string(), - instrument: instrument.id, - }; + for instrument in instruments { + let row = InstrumentationRow { + id: rand::random(), + work: work_id.to_string(), + instrument: instrument.id, + }; - diesel::insert_into(instrumentations::table) - .values(row) - .execute(connection)?; - } + diesel::insert_into(instrumentations::table) + .values(row) + .execute(connection)?; + } - for (index, part) in parts.into_iter().enumerate() { - let row = WorkPartRow { - id: rand::random(), - work: work_id.to_string(), - part_index: index as i64, - title: part.title, - }; + for (index, part) in parts.into_iter().enumerate() { + let row = WorkPartRow { + id: rand::random(), + work: work_id.to_string(), + part_index: index as i64, + title: part.title, + }; - diesel::insert_into(work_parts::table) - .values(row) - .execute(connection)?; - } - - Ok(()) - })?; + diesel::insert_into(work_parts::table) + .values(row) + .execute(connection)?; + } Ok(()) - } + })?; - /// Get an existing work. - pub fn get_work(&self, id: &str) -> Result> { - let row = works::table - .filter(works::id.eq(id)) - .load::(&mut *self.connection.lock().unwrap())? - .first() - .cloned(); - - let work = match row { - Some(row) => Some(self.get_work_data(row)?), - None => None, - }; - - Ok(work) - } - - /// Retrieve all available information on a work from related tables. - fn get_work_data(&self, row: WorkRow) -> Result { - let mut instruments: Vec = Vec::new(); - - let instrumentations = instrumentations::table - .filter(instrumentations::work.eq(&row.id)) - .load::(&mut *self.connection.lock().unwrap())?; - - for instrumentation in instrumentations { - let id = instrumentation.instrument; - instruments.push( - self.get_instrument(&id)? - .ok_or(Error::MissingItem("instrument", id))?, - ); - } - - let mut parts: Vec = Vec::new(); - - let part_rows = work_parts::table - .filter(work_parts::work.eq(&row.id)) - .load::(&mut *self.connection.lock().unwrap())?; - - for part_row in part_rows { - parts.push(WorkPart { - title: part_row.title, - }); - } - - let person_id = row.composer; - let person = self - .get_person(&person_id)? - .ok_or(Error::MissingItem("person", person_id))?; - - Ok(Work { - id: row.id, - composer: person, - title: row.title, - instruments, - parts, - last_used: row.last_used.map(|t| Utc.timestamp_opt(t, 0).unwrap()), - last_played: row.last_played.map(|t| Utc.timestamp_opt(t, 0).unwrap()), - }) - } - - /// Delete an existing work. This will fail if there are still other tables that relate to - /// this work except for the things that are part of the information on the work it - pub fn delete_work(&self, id: &str) -> Result<()> { - info!("Deleting work {}", id); - diesel::delete(works::table.filter(works::id.eq(id))) - .execute(&mut *self.connection.lock().unwrap())?; - Ok(()) - } - - /// Get all existing works by a composer and related information from other tables. - pub fn get_works(&self, composer_id: &str) -> Result> { - let mut works: Vec = Vec::new(); - - let rows = works::table - .filter(works::composer.eq(composer_id)) - .load::(&mut *self.connection.lock().unwrap())?; - - for row in rows { - works.push(self.get_work_data(row)?); - } - - Ok(works) - } + Ok(()) +} + +/// Get an existing work. +pub fn get_work(connection: &mut SqliteConnection, id: &str) -> Result> { + let row = works::table + .filter(works::id.eq(id)) + .load::(connection)? + .first() + .cloned(); + + let work = match row { + Some(row) => Some(get_work_data(connection, row)?), + None => None, + }; + + Ok(work) +} + +/// Retrieve all available information on a work from related tables. +fn get_work_data(connection: &mut SqliteConnection, row: WorkRow) -> Result { + let mut instruments: Vec = Vec::new(); + + let instrumentations = instrumentations::table + .filter(instrumentations::work.eq(&row.id)) + .load::(connection)?; + + for instrumentation in instrumentations { + let id = instrumentation.instrument; + instruments + .push(get_instrument(connection, &id)?.ok_or(Error::MissingItem("instrument", id))?); + } + + let mut parts: Vec = Vec::new(); + + let part_rows = work_parts::table + .filter(work_parts::work.eq(&row.id)) + .load::(connection)?; + + for part_row in part_rows { + parts.push(WorkPart { + title: part_row.title, + }); + } + + let person_id = row.composer; + let person = + get_person(connection, &person_id)?.ok_or(Error::MissingItem("person", person_id))?; + + Ok(Work { + id: row.id, + composer: person, + title: row.title, + instruments, + parts, + last_used: row.last_used.map(|t| Utc.timestamp_opt(t, 0).unwrap()), + last_played: row.last_played.map(|t| Utc.timestamp_opt(t, 0).unwrap()), + }) +} + +/// Delete an existing work. This will fail if there are still other tables that relate to +/// this work except for the things that are part of the information on the work it +pub fn delete_work(connection: &mut SqliteConnection, id: &str) -> Result<()> { + info!("Deleting work {}", id); + diesel::delete(works::table.filter(works::id.eq(id))).execute(connection)?; + Ok(()) +} + +/// Get all existing works by a composer and related information from other tables. +pub fn get_works(connection: &mut SqliteConnection, composer_id: &str) -> Result> { + let mut works: Vec = Vec::new(); + + let rows = works::table + .filter(works::composer.eq(composer_id)) + .load::(connection)?; + + for row in rows { + works.push(get_work_data(connection, row)?); + } + + Ok(works) } diff --git a/crates/musicus/src/editors/ensemble.rs b/crates/musicus/src/editors/ensemble.rs index 8d45191..1f72dff 100644 --- a/crates/musicus/src/editors/ensemble.rs +++ b/crates/musicus/src/editors/ensemble.rs @@ -3,7 +3,7 @@ use crate::widgets::{Editor, Section, Widget}; use anyhow::Result; use gettextrs::gettext; use gtk::{builders::ListBoxBuilder, glib::clone, prelude::*}; -use musicus_backend::db::{generate_id, Ensemble}; +use musicus_backend::db::{generate_id, Ensemble, self}; use std::rc::Rc; /// A dialog for creating or editing a ensemble. @@ -88,7 +88,7 @@ impl EnsembleEditor { let ensemble = Ensemble::new(self.id.clone(), name.to_string()); - self.handle.backend.db().update_ensemble(ensemble.clone())?; + db::update_ensemble(&mut self.handle.backend.db().lock().unwrap(), ensemble.clone())?; self.handle.backend.library_changed(); Ok(ensemble) diff --git a/crates/musicus/src/editors/instrument.rs b/crates/musicus/src/editors/instrument.rs index 13e8536..daaea20 100644 --- a/crates/musicus/src/editors/instrument.rs +++ b/crates/musicus/src/editors/instrument.rs @@ -3,7 +3,7 @@ use crate::widgets::{Editor, Section, Widget}; use anyhow::Result; use gettextrs::gettext; use gtk::{builders::ListBoxBuilder, glib::clone, prelude::*}; -use musicus_backend::db::{generate_id, Instrument}; +use musicus_backend::db::{self, generate_id, Instrument}; use std::rc::Rc; /// A dialog for creating or editing a instrument. @@ -88,10 +88,11 @@ impl InstrumentEditor { let instrument = Instrument::new(self.id.clone(), name.to_string()); - self.handle - .backend - .db() - .update_instrument(instrument.clone())?; + db::update_instrument( + &mut self.handle.backend.db().lock().unwrap(), + instrument.clone(), + )?; + self.handle.backend.library_changed(); Ok(instrument) diff --git a/crates/musicus/src/editors/person.rs b/crates/musicus/src/editors/person.rs index 390b865..1d2f692 100644 --- a/crates/musicus/src/editors/person.rs +++ b/crates/musicus/src/editors/person.rs @@ -4,7 +4,7 @@ use anyhow::Result; use gettextrs::gettext; use glib::clone; use gtk::{builders::ListBoxBuilder, prelude::*}; -use musicus_backend::db::{generate_id, Person}; +use musicus_backend::db::{generate_id, Person, self}; use std::rc::Rc; /// A dialog for creating or editing a person. @@ -110,7 +110,11 @@ impl PersonEditor { last_name.to_string(), ); - self.handle.backend.db().update_person(person.clone())?; + db::update_person( + &mut self.handle.backend.db().lock().unwrap(), + person.clone(), + )?; + self.handle.backend.library_changed(); Ok(person) diff --git a/crates/musicus/src/editors/recording.rs b/crates/musicus/src/editors/recording.rs index 4de62e8..c9b8155 100644 --- a/crates/musicus/src/editors/recording.rs +++ b/crates/musicus/src/editors/recording.rs @@ -8,7 +8,7 @@ use anyhow::Result; use gettextrs::gettext; use glib::clone; use gtk_macros::get_widget; -use musicus_backend::db::{generate_id, Performance, Recording, Work}; +use musicus_backend::db::{self, generate_id, Performance, Recording, Work}; use std::cell::RefCell; use std::rc::Rc; @@ -188,10 +188,11 @@ impl RecordingEditor { self.performances.borrow().clone(), ); - self.handle - .backend - .db() - .update_recording(recording.clone())?; + db::update_recording( + &mut self.handle.backend.db().lock().unwrap(), + recording.clone(), + )?; + self.handle.backend.library_changed(); Ok(recording) diff --git a/crates/musicus/src/editors/work.rs b/crates/musicus/src/editors/work.rs index 5e5f9a3..1d0e9f4 100644 --- a/crates/musicus/src/editors/work.rs +++ b/crates/musicus/src/editors/work.rs @@ -8,7 +8,7 @@ use anyhow::Result; use gettextrs::gettext; use glib::clone; use gtk_macros::get_widget; -use musicus_backend::db::{generate_id, Instrument, Person, Work, WorkPart}; +use musicus_backend::db::{self, generate_id, Instrument, Person, Work, WorkPart}; use std::cell::RefCell; use std::rc::Rc; @@ -261,7 +261,7 @@ impl WorkEditor { self.parts.borrow().clone(), ); - self.handle.backend.db().update_work(work.clone())?; + db::update_work(&mut self.handle.backend.db().lock().unwrap(), work.clone())?; self.handle.backend.library_changed(); Ok(work) diff --git a/crates/musicus/src/import/import_screen.rs b/crates/musicus/src/import/import_screen.rs index c6e6b2c..3ae46e2 100644 --- a/crates/musicus/src/import/import_screen.rs +++ b/crates/musicus/src/import/import_screen.rs @@ -7,7 +7,7 @@ use adw::builders::ActionRowBuilder; use adw::prelude::*; use glib::clone; use gtk_macros::get_widget; -use musicus_backend::db::Medium; +use musicus_backend::db::{self, Medium}; use musicus_backend::import::ImportSession; use std::rc::Rc; use std::sync::Arc; @@ -29,7 +29,10 @@ impl ImportScreen { let this = self; spawn!(@clone this, async move { - let mediums = this.handle.backend.db().get_mediums_by_source_id(this.session.source_id()); + let mediums = db::get_mediums_by_source_id( + &mut this.handle.backend.db().lock().unwrap(), + this.session.source_id() + ); match mediums { Ok(mediums) => { @@ -110,7 +113,7 @@ impl Screen, ()> for ImportScreen { get_widget!(builder, adw::ActionRow, error_row); get_widget!(builder, gtk::ListBox, matching_list); get_widget!(builder, gtk::Button, select_button); - get_widget!(builder, gtk::Button, add_button); + get_widget!(builder, gtk::Button, add_medium_button); let this = Rc::new(Self { handle, @@ -139,7 +142,7 @@ impl Screen, ()> for ImportScreen { }); })); - add_button.connect_clicked(clone!(@weak this => move |_| { + add_medium_button.connect_clicked(clone!(@weak this => move |_| { spawn!(@clone this, async move { if let Some(medium) = push!(this.handle, MediumEditor, (Arc::clone(&this.session), None)).await { this.select_medium(medium); @@ -151,7 +154,7 @@ impl Screen, ()> for ImportScreen { this.load_matches(); - // Copy the tracks in the background, if neccessary. + // Copy the tracks in the background, if necessary. this.session.copy(); this diff --git a/crates/musicus/src/import/medium_preview.rs b/crates/musicus/src/import/medium_preview.rs index c39d3e5..88b479d 100644 --- a/crates/musicus/src/import/medium_preview.rs +++ b/crates/musicus/src/import/medium_preview.rs @@ -8,7 +8,7 @@ use glib::clone; use gtk::builders::{FrameBuilder, ListBoxBuilder}; use gtk::prelude::*; use gtk_macros::get_widget; -use musicus_backend::db::Medium; +use musicus_backend::db::{self, Medium}; use musicus_backend::import::{ImportSession, State}; use std::cell::RefCell; use std::path::PathBuf; @@ -226,7 +226,7 @@ impl MediumPreview { ); let directory = PathBuf::from(&directory_name); - std::fs::create_dir(&music_library_path.join(&directory))?; + std::fs::create_dir(music_library_path.join(&directory))?; // Copy the tracks to the music library. @@ -243,7 +243,7 @@ impl MediumPreview { track.path = track_path.to_str().unwrap().to_owned(); // Copy the corresponding audio file to the music library. - std::fs::copy(&import_track.path, &music_library_path.join(&track_path))?; + std::fs::copy(&import_track.path, music_library_path.join(&track_path))?; tracks.push(track); } @@ -257,7 +257,7 @@ impl MediumPreview { tracks, ); - self.handle.backend.db().update_medium(medium)?; + db::update_medium(&mut self.handle.backend.db().lock().unwrap(), medium)?; self.handle.backend.library_changed(); Ok(()) diff --git a/crates/musicus/src/screens/ensemble.rs b/crates/musicus/src/screens/ensemble.rs index c137c23..9a78944 100644 --- a/crates/musicus/src/screens/ensemble.rs +++ b/crates/musicus/src/screens/ensemble.rs @@ -7,7 +7,7 @@ use adw::builders::ActionRowBuilder; use adw::prelude::*; use gettextrs::gettext; use glib::clone; -use musicus_backend::db::{Ensemble, Medium, Recording}; +use musicus_backend::db::{self, Ensemble, Medium, Recording}; use std::cell::RefCell; use std::rc::Rc; @@ -60,7 +60,7 @@ impl Screen for EnsembleScreen { &gettext("Delete ensemble"), clone!(@weak this => move || { spawn!(@clone this, async move { - this.handle.backend.db().delete_ensemble(&this.ensemble.id).unwrap(); + db::delete_ensemble(&mut this.handle.backend.db().lock().unwrap(), &this.ensemble.id).unwrap(); this.handle.backend.library_changed(); }); }), @@ -131,19 +131,17 @@ impl Screen for EnsembleScreen { // Load the content. - let recordings = this - .handle - .backend - .db() - .get_recordings_for_ensemble(&this.ensemble.id) - .unwrap(); + let recordings = db::get_recordings_for_ensemble( + &mut this.handle.backend.db().lock().unwrap(), + &this.ensemble.id, + ) + .unwrap(); - let mediums = this - .handle - .backend - .db() - .get_mediums_for_ensemble(&this.ensemble.id) - .unwrap(); + let mediums = db::get_mediums_for_ensemble( + &mut this.handle.backend.db().lock().unwrap(), + &this.ensemble.id, + ) + .unwrap(); if !recordings.is_empty() { let length = recordings.len(); diff --git a/crates/musicus/src/screens/main.rs b/crates/musicus/src/screens/main.rs index d4c09d4..9c2c38f 100644 --- a/crates/musicus/src/screens/main.rs +++ b/crates/musicus/src/screens/main.rs @@ -9,7 +9,7 @@ use adw::prelude::*; use gettextrs::gettext; use glib::clone; use gtk_macros::get_widget; -use musicus_backend::db::PersonOrEnsemble; +use musicus_backend::db::{self, PersonOrEnsemble}; use std::cell::RefCell; use std::rc::Rc; @@ -142,8 +142,9 @@ impl Screen<(), ()> for MainScreen { ); play_button.connect_clicked(clone!(@weak this => move |_| { - if let Ok(recording) = this.handle.backend.db().random_recording() { - this.handle.backend.pl().add_items(this.handle.backend.db().get_tracks(&recording.id).unwrap()).unwrap(); + let recording = db::random_recording(&mut this.handle.backend.db().lock().unwrap()); + if let Ok(recording) = recording { + this.handle.backend.pl().add_items(db::get_tracks(&mut this.handle.backend.db().lock().unwrap(), &recording.id).unwrap()).unwrap(); } })); @@ -164,8 +165,8 @@ impl Screen<(), ()> for MainScreen { let mut poes = Vec::new(); - let persons = this.handle.backend.db().get_persons().unwrap(); - let ensembles = this.handle.backend.db().get_ensembles().unwrap(); + let persons = db::get_persons(&mut this.handle.backend.db().lock().unwrap(), ).unwrap(); + let ensembles = db::get_ensembles(&mut this.handle.backend.db().lock().unwrap(), ).unwrap(); for person in persons { poes.push(PersonOrEnsemble::Person(person)); diff --git a/crates/musicus/src/screens/person.rs b/crates/musicus/src/screens/person.rs index 7b7fe98..3ceb2f0 100644 --- a/crates/musicus/src/screens/person.rs +++ b/crates/musicus/src/screens/person.rs @@ -7,7 +7,7 @@ use adw::builders::ActionRowBuilder; use adw::prelude::*; use gettextrs::gettext; use glib::clone; -use musicus_backend::db::{Medium, Person, Recording, Work}; +use musicus_backend::db::{self, Medium, Person, Recording, Work}; use std::cell::RefCell; use std::rc::Rc; @@ -65,7 +65,7 @@ impl Screen for PersonScreen { &gettext("Delete person"), clone!(@weak this => move || { spawn!(@clone this, async move { - this.handle.backend.db().delete_person(&this.person.id).unwrap(); + db::delete_person(&mut this.handle.backend.db().lock().unwrap(), &this.person.id).unwrap(); this.handle.backend.library_changed(); }); }), @@ -165,21 +165,23 @@ impl Screen for PersonScreen { // Load the content. - let works = this.handle.backend.db().get_works(&this.person.id).unwrap(); + let works = db::get_works( + &mut this.handle.backend.db().lock().unwrap(), + &this.person.id, + ) + .unwrap(); - let recordings = this - .handle - .backend - .db() - .get_recordings_for_person(&this.person.id) - .unwrap(); + let recordings = db::get_recordings_for_person( + &mut this.handle.backend.db().lock().unwrap(), + &this.person.id, + ) + .unwrap(); - let mediums = this - .handle - .backend - .db() - .get_mediums_for_person(&this.person.id) - .unwrap(); + let mediums = db::get_mediums_for_person( + &mut this.handle.backend.db().lock().unwrap(), + &this.person.id, + ) + .unwrap(); if !works.is_empty() { let length = works.len(); diff --git a/crates/musicus/src/screens/recording.rs b/crates/musicus/src/screens/recording.rs index 5485c57..29666b0 100644 --- a/crates/musicus/src/screens/recording.rs +++ b/crates/musicus/src/screens/recording.rs @@ -6,7 +6,7 @@ use adw::builders::ActionRowBuilder; use adw::prelude::*; use gettextrs::gettext; use glib::clone; -use musicus_backend::db::{Recording, Track}; +use musicus_backend::db::{self, Recording, Track}; use std::cell::RefCell; use std::rc::Rc; @@ -64,7 +64,7 @@ impl Screen for RecordingScreen { &gettext("Delete recording"), clone!(@weak this => move || { spawn!(@clone this, async move { - this.handle.backend.db().delete_recording(&this.recording.id).unwrap(); + db::delete_recording(&mut this.handle.backend.db().lock().unwrap(), &this.recording.id).unwrap(); this.handle.backend.library_changed(); }); }), @@ -94,12 +94,11 @@ impl Screen for RecordingScreen { // Load the content. - let tracks = this - .handle - .backend - .db() - .get_tracks(&this.recording.id) - .unwrap(); + let tracks = db::get_tracks( + &mut this.handle.backend.db().lock().unwrap(), + &this.recording.id, + ) + .unwrap(); this.show_tracks(tracks); this.widget.ready(); diff --git a/crates/musicus/src/screens/work.rs b/crates/musicus/src/screens/work.rs index f13c352..02780f7 100644 --- a/crates/musicus/src/screens/work.rs +++ b/crates/musicus/src/screens/work.rs @@ -7,7 +7,7 @@ use adw::builders::ActionRowBuilder; use adw::prelude::*; use gettextrs::gettext; use glib::clone; -use musicus_backend::db::{Recording, Work}; +use musicus_backend::db::{self, Recording, Work}; use std::cell::RefCell; use std::rc::Rc; @@ -56,7 +56,7 @@ impl Screen for WorkScreen { &gettext("Delete work"), clone!(@weak this => move || { spawn!(@clone this, async move { - this.handle.backend.db().delete_work(&this.work.id).unwrap(); + db::delete_work(&mut this.handle.backend.db().lock().unwrap(), &this.work.id).unwrap(); this.handle.backend.library_changed(); }); }), @@ -98,12 +98,11 @@ impl Screen for WorkScreen { // Load the content. - let recordings = this - .handle - .backend - .db() - .get_recordings_for_work(&this.work.id) - .unwrap(); + let recordings = db::get_recordings_for_work( + &mut this.handle.backend.db().lock().unwrap(), + &this.work.id, + ) + .unwrap(); if !recordings.is_empty() { let length = recordings.len(); diff --git a/crates/musicus/src/selectors/ensemble.rs b/crates/musicus/src/selectors/ensemble.rs index 2e401b5..c73f527 100644 --- a/crates/musicus/src/selectors/ensemble.rs +++ b/crates/musicus/src/selectors/ensemble.rs @@ -7,7 +7,7 @@ use adw::prelude::*; use gettextrs::gettext; use glib::clone; use log::warn; -use musicus_backend::db::Ensemble; +use musicus_backend::db::{Ensemble, self}; use std::rc::Rc; /// A screen for selecting a ensemble. @@ -50,7 +50,7 @@ impl Screen<(), Ensemble> for EnsembleSelector { let ensemble = ensemble.to_owned(); row.connect_activated(clone!(@weak this => move |_| { - if let Err(err) = this.handle.backend.db().update_ensemble(ensemble.clone()) { + if let Err(err) = db::update_ensemble(&mut this.handle.backend.db().lock().unwrap(), ensemble.clone()) { warn!("Failed to update access time. {err}"); } @@ -64,7 +64,7 @@ impl Screen<(), Ensemble> for EnsembleSelector { .set_filter(|search, ensemble| ensemble.name.to_lowercase().contains(search)); this.selector - .set_items(this.handle.backend.db().get_recent_ensembles().unwrap()); + .set_items(db::get_recent_ensembles(&mut this.handle.backend.db().lock().unwrap(), ).unwrap()); this } diff --git a/crates/musicus/src/selectors/instrument.rs b/crates/musicus/src/selectors/instrument.rs index 26f1a5e..4843b26 100644 --- a/crates/musicus/src/selectors/instrument.rs +++ b/crates/musicus/src/selectors/instrument.rs @@ -7,7 +7,7 @@ use adw::prelude::*; use gettextrs::gettext; use glib::clone; use log::warn; -use musicus_backend::db::Instrument; +use musicus_backend::db::{Instrument, self}; use std::rc::Rc; /// A screen for selecting a instrument. @@ -50,7 +50,7 @@ impl Screen<(), Instrument> for InstrumentSelector { let instrument = instrument.to_owned(); row.connect_activated(clone!(@weak this => move |_| { - if let Err(err) = this.handle.backend.db().update_instrument(instrument.clone()) { + if let Err(err) = db::update_instrument(&mut this.handle.backend.db().lock().unwrap(), instrument.clone()) { warn!("Failed to update access time. {err}"); } @@ -64,7 +64,7 @@ impl Screen<(), Instrument> for InstrumentSelector { .set_filter(|search, instrument| instrument.name.to_lowercase().contains(search)); this.selector - .set_items(this.handle.backend.db().get_recent_instruments().unwrap()); + .set_items(db::get_recent_instruments(&mut this.handle.backend.db().lock().unwrap(), ).unwrap()); this } diff --git a/crates/musicus/src/selectors/medium.rs b/crates/musicus/src/selectors/medium.rs index 0bb4f1d..ee68e0b 100644 --- a/crates/musicus/src/selectors/medium.rs +++ b/crates/musicus/src/selectors/medium.rs @@ -6,7 +6,7 @@ use adw::prelude::*; use gettextrs::gettext; use glib::clone; use log::warn; -use musicus_backend::db::{Medium, PersonOrEnsemble}; +use musicus_backend::db::{self, Medium, PersonOrEnsemble}; use std::rc::Rc; /// A screen for selecting a medium. @@ -56,8 +56,10 @@ impl Screen<(), Medium> for MediumSelector { let mut poes = Vec::new(); - let persons = this.handle.backend.db().get_recent_persons().unwrap(); - let ensembles = this.handle.backend.db().get_recent_ensembles().unwrap(); + let persons = + db::get_recent_persons(&mut this.handle.backend.db().lock().unwrap()).unwrap(); + let ensembles = + db::get_recent_ensembles(&mut this.handle.backend.db().lock().unwrap()).unwrap(); for person in persons { poes.push(PersonOrEnsemble::Person(person)); @@ -111,7 +113,7 @@ impl Screen for MediumSelectorMediumScreen { let medium = medium.to_owned(); row.connect_activated(clone!(@weak this => move |_| { - if let Err(err) = this.handle.backend.db().update_medium(medium.clone()) { + if let Err(err) = db::update_medium(&mut this.handle.backend.db().lock().unwrap(), medium.clone()) { warn!("Failed to update access time. {err}"); } @@ -128,20 +130,20 @@ impl Screen for MediumSelectorMediumScreen { match this.poe.clone() { PersonOrEnsemble::Person(person) => { this.selector.set_items( - this.handle - .backend - .db() - .get_mediums_for_person(&person.id) - .unwrap(), + db::get_mediums_for_person( + &mut this.handle.backend.db().lock().unwrap(), + &person.id, + ) + .unwrap(), ); } PersonOrEnsemble::Ensemble(ensemble) => { this.selector.set_items( - this.handle - .backend - .db() - .get_mediums_for_ensemble(&ensemble.id) - .unwrap(), + db::get_mediums_for_ensemble( + &mut this.handle.backend.db().lock().unwrap(), + &ensemble.id, + ) + .unwrap(), ); } } diff --git a/crates/musicus/src/selectors/person.rs b/crates/musicus/src/selectors/person.rs index 001e864..ef9957d 100644 --- a/crates/musicus/src/selectors/person.rs +++ b/crates/musicus/src/selectors/person.rs @@ -7,7 +7,7 @@ use adw::prelude::*; use gettextrs::gettext; use glib::clone; use log::warn; -use musicus_backend::db::Person; +use musicus_backend::db::{Person, self}; use std::rc::Rc; /// A screen for selecting a person. @@ -50,7 +50,7 @@ impl Screen<(), Person> for PersonSelector { let person = person.to_owned(); row.connect_activated(clone!(@weak this => move |_| { - if let Err(err) = this.handle.backend.db().update_person(person.clone()) { + if let Err(err) = db::update_person(&mut this.handle.backend.db().lock().unwrap(), person.clone()) { warn!("Failed to update access time. {err}"); } @@ -64,7 +64,7 @@ impl Screen<(), Person> for PersonSelector { .set_filter(|search, person| person.name_fl().to_lowercase().contains(search)); this.selector - .set_items(this.handle.backend.db().get_recent_persons().unwrap()); + .set_items(db::get_recent_persons(&mut this.handle.backend.db().lock().unwrap(), ).unwrap()); this } diff --git a/crates/musicus/src/selectors/recording.rs b/crates/musicus/src/selectors/recording.rs index d904ee7..16e693a 100644 --- a/crates/musicus/src/selectors/recording.rs +++ b/crates/musicus/src/selectors/recording.rs @@ -7,7 +7,7 @@ use adw::prelude::*; use gettextrs::gettext; use glib::clone; use log::warn; -use musicus_backend::db::{Person, Recording, Work}; +use musicus_backend::db::{self, Person, Recording, Work}; use std::rc::Rc; /// A screen for selecting a recording. @@ -81,8 +81,9 @@ impl Screen<(), Recording> for RecordingSelector { this.selector .set_filter(|search, person| person.name_fl().to_lowercase().contains(search)); - this.selector - .set_items(this.handle.backend.db().get_recent_persons().unwrap()); + this.selector.set_items( + db::get_recent_persons(&mut this.handle.backend.db().lock().unwrap()).unwrap(), + ); this } @@ -144,8 +145,13 @@ impl Screen for RecordingSelectorWorkScreen { this.selector .set_filter(|search, work| work.title.to_lowercase().contains(search)); - this.selector - .set_items(this.handle.backend.db().get_works(&this.person.id).unwrap()); + this.selector.set_items( + db::get_works( + &mut this.handle.backend.db().lock().unwrap(), + &this.person.id, + ) + .unwrap(), + ); this } @@ -198,7 +204,7 @@ impl Screen for RecordingSelectorRecordingScreen { let recording = recording.to_owned(); row.connect_activated(clone!(@weak this => move |_| { - if let Err(err) = this.handle.backend.db().update_recording(recording.clone()) { + if let Err(err) = db::update_recording(&mut this.handle.backend.db().lock().unwrap(), recording.clone()) { warn!("Failed to update access time. {err}"); } @@ -213,11 +219,11 @@ impl Screen for RecordingSelectorRecordingScreen { }); this.selector.set_items( - this.handle - .backend - .db() - .get_recordings_for_work(&this.work.id) - .unwrap(), + db::get_recordings_for_work( + &mut this.handle.backend.db().lock().unwrap(), + &this.work.id, + ) + .unwrap(), ); this diff --git a/crates/musicus/src/selectors/work.rs b/crates/musicus/src/selectors/work.rs index e12f125..3fd9697 100644 --- a/crates/musicus/src/selectors/work.rs +++ b/crates/musicus/src/selectors/work.rs @@ -7,7 +7,7 @@ use adw::prelude::*; use gettextrs::gettext; use glib::clone; use log::warn; -use musicus_backend::db::{Person, Work}; +use musicus_backend::db::{Person, Work, self}; use std::rc::Rc; /// A screen for selecting a work. @@ -72,7 +72,7 @@ impl Screen<(), Work> for WorkSelector { .set_filter(|search, person| person.name_fl().to_lowercase().contains(search)); this.selector - .set_items(this.handle.backend.db().get_recent_persons().unwrap()); + .set_items(db::get_recent_persons(&mut this.handle.backend.db().lock().unwrap()).unwrap()); this } @@ -125,7 +125,7 @@ impl Screen for WorkSelectorWorkScreen { let work = work.to_owned(); row.connect_activated(clone!(@weak this => move |_| { - if let Err(err) = this.handle.backend.db().update_work(work.clone()) { + if let Err(err) = db::update_work(&mut this.handle.backend.db().lock().unwrap(), work.clone()) { warn!("Failed to update access time. {err}"); } @@ -139,7 +139,7 @@ impl Screen for WorkSelectorWorkScreen { .set_filter(|search, work| work.title.to_lowercase().contains(search)); this.selector - .set_items(this.handle.backend.db().get_works(&this.person.id).unwrap()); + .set_items(db::get_works(&mut this.handle.backend.db().lock().unwrap(), &this.person.id).unwrap()); this }