Add custom error type to database module

This commit is contained in:
Elias Projahn 2021-02-04 17:43:36 +01:00
parent 5a41d5008f
commit d7fb996183
11 changed files with 232 additions and 116 deletions

View file

@ -1,6 +1,5 @@
use super::schema::ensembles;
use super::Database;
use anyhow::Result;
use super::{Database, DatabaseResult};
use diesel::prelude::*;
use serde::{Deserialize, Serialize};
@ -14,7 +13,7 @@ pub struct Ensemble {
impl Database {
/// Update an existing ensemble or insert a new one.
pub fn update_ensemble(&self, ensemble: Ensemble) -> Result<()> {
pub fn update_ensemble(&self, ensemble: Ensemble) -> DatabaseResult<()> {
self.defer_foreign_keys()?;
self.connection.transaction(|| {
@ -27,7 +26,7 @@ impl Database {
}
/// Get an existing ensemble.
pub fn get_ensemble(&self, id: &str) -> Result<Option<Ensemble>> {
pub fn get_ensemble(&self, id: &str) -> DatabaseResult<Option<Ensemble>> {
let ensemble = ensembles::table
.filter(ensembles::id.eq(id))
.load::<Ensemble>(&self.connection)?
@ -38,14 +37,14 @@ impl Database {
}
/// Delete an existing ensemble.
pub fn delete_ensemble(&self, id: &str) -> Result<()> {
pub fn delete_ensemble(&self, id: &str) -> DatabaseResult<()> {
diesel::delete(ensembles::table.filter(ensembles::id.eq(id))).execute(&self.connection)?;
Ok(())
}
/// Get all existing ensembles.
pub fn get_ensembles(&self) -> Result<Vec<Ensemble>> {
pub fn get_ensembles(&self) -> DatabaseResult<Vec<Ensemble>> {
let ensembles = ensembles::table.load::<Ensemble>(&self.connection)?;
Ok(ensembles)

View file

@ -0,0 +1,26 @@
use thiserror::Error;
/// Error that happens within the database module.
#[derive(Error, Debug)]
pub enum DatabaseError {
#[error(transparent)]
ConnectionError(#[from] diesel::result::ConnectionError),
#[error(transparent)]
MigrationsError(#[from] diesel_migrations::RunMigrationsError),
#[error(transparent)]
QueryError(#[from] diesel::result::Error),
#[error(transparent)]
SendError(#[from] std::sync::mpsc::SendError<super::thread::Action>),
#[error(transparent)]
ReceiveError(#[from] futures_channel::oneshot::Canceled),
#[error("Database error: {0}")]
Other(String),
}
/// Return type for database methods.
pub type DatabaseResult<T> = Result<T, DatabaseError>;

View file

@ -1,6 +1,5 @@
use super::schema::instruments;
use super::Database;
use anyhow::Result;
use super::{Database, DatabaseResult};
use diesel::prelude::*;
use serde::{Deserialize, Serialize};
@ -14,7 +13,7 @@ pub struct Instrument {
impl Database {
/// Update an existing instrument or insert a new one.
pub fn update_instrument(&self, instrument: Instrument) -> Result<()> {
pub fn update_instrument(&self, instrument: Instrument) -> DatabaseResult<()> {
self.defer_foreign_keys()?;
self.connection.transaction(|| {
@ -27,7 +26,7 @@ impl Database {
}
/// Get an existing instrument.
pub fn get_instrument(&self, id: &str) -> Result<Option<Instrument>> {
pub fn get_instrument(&self, id: &str) -> DatabaseResult<Option<Instrument>> {
let instrument = instruments::table
.filter(instruments::id.eq(id))
.load::<Instrument>(&self.connection)?
@ -38,7 +37,7 @@ impl Database {
}
/// Delete an existing instrument.
pub fn delete_instrument(&self, id: &str) -> Result<()> {
pub fn delete_instrument(&self, id: &str) -> DatabaseResult<()> {
diesel::delete(instruments::table.filter(instruments::id.eq(id)))
.execute(&self.connection)?;
@ -46,7 +45,7 @@ impl Database {
}
/// Get all existing instruments.
pub fn get_instruments(&self) -> Result<Vec<Instrument>> {
pub fn get_instruments(&self) -> DatabaseResult<Vec<Instrument>> {
let instruments = instruments::table.load::<Instrument>(&self.connection)?;
Ok(instruments)

View file

@ -1,7 +1,6 @@
use super::generate_id;
use super::schema::{mediums, recordings, track_sets, tracks};
use super::{Database, Recording};
use anyhow::{anyhow, Error, Result};
use super::{Database, DatabaseError, Recording, DatabaseResult};
use diesel::prelude::*;
use serde::{Deserialize, Serialize};
@ -80,10 +79,10 @@ struct TrackRow {
impl Database {
/// Update an existing medium or insert a new one.
pub fn update_medium(&self, medium: Medium) -> Result<()> {
pub fn update_medium(&self, medium: Medium) -> DatabaseResult<()> {
self.defer_foreign_keys()?;
self.connection.transaction::<(), Error, _>(|| {
self.connection.transaction::<(), DatabaseError, _>(|| {
let medium_id = &medium.id;
// This will also delete the track sets and tracks.
@ -153,7 +152,7 @@ impl Database {
}
/// Get an existing medium.
pub fn get_medium(&self, id: &str) -> Result<Option<Medium>> {
pub fn get_medium(&self, id: &str) -> DatabaseResult<Option<Medium>> {
let row = mediums::table
.filter(mediums::id.eq(id))
.load::<MediumRow>(&self.connection)?
@ -170,13 +169,13 @@ impl Database {
/// 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<()> {
pub fn delete_medium(&self, id: &str) -> DatabaseResult<()> {
diesel::delete(mediums::table.filter(mediums::id.eq(id))).execute(&self.connection)?;
Ok(())
}
/// Get all available track sets for a recording.
pub fn get_track_sets(&self, recording_id: &str) -> Result<Vec<TrackSet>> {
pub fn get_track_sets(&self, recording_id: &str) -> DatabaseResult<Vec<TrackSet>> {
let mut track_sets: Vec<TrackSet> = Vec::new();
let rows = track_sets::table
@ -194,7 +193,7 @@ impl Database {
}
/// Retrieve all available information on a medium from related tables.
fn get_medium_data(&self, row: MediumRow) -> Result<Medium> {
fn get_medium_data(&self, row: MediumRow) -> DatabaseResult<Medium> {
let track_set_rows = track_sets::table
.filter(track_sets::medium.eq(&row.id))
.order_by(track_sets::index)
@ -218,12 +217,16 @@ impl Database {
}
/// Convert a track set row from the database to an actual track set.
fn get_track_set_from_row(&self, row: TrackSetRow) -> Result<TrackSet> {
fn get_track_set_from_row(&self, row: TrackSetRow) -> DatabaseResult<TrackSet> {
let recording_id = row.recording;
let recording = self
.get_recording(&recording_id)?
.ok_or_else(|| anyhow!("No recording with ID: {}", recording_id))?;
.ok_or(DatabaseError::Other(format!(
"Failed to get recording ({}) for track set ({}).",
recording_id,
row.id,
)))?;
let track_rows = tracks::table
.filter(tracks::track_set.eq(row.id))
@ -236,8 +239,14 @@ impl Database {
let work_parts = track_row
.work_parts
.split(',')
.map(|part_index| Ok(str::parse(part_index)?))
.collect::<Result<Vec<usize>>>()?;
.map(|part_index| {
str::parse(part_index)
.or(Err(DatabaseError::Other(format!(
"Failed to parse part index from '{}'.",
track_row.work_parts,
)))?)
})
.collect::<DatabaseResult<Vec<usize>>>()?;
let track = Track {
work_parts,

View file

@ -1,9 +1,11 @@
use anyhow::Result;
use diesel::prelude::*;
pub mod ensembles;
pub use ensembles::*;
pub mod error;
pub use error::*;
pub mod instruments;
pub use instruments::*;
@ -42,7 +44,7 @@ pub struct Database {
impl Database {
/// Create a new database interface and run migrations if necessary.
pub fn new(file_name: &str) -> Result<Database> {
pub fn new(file_name: &str) -> DatabaseResult<Database> {
let connection = SqliteConnection::establish(file_name)?;
diesel::sql_query("PRAGMA foreign_keys = ON").execute(&connection)?;
@ -52,7 +54,7 @@ impl Database {
}
/// Defer all foreign keys for the next transaction.
fn defer_foreign_keys(&self) -> Result<()> {
fn defer_foreign_keys(&self) -> DatabaseResult<()> {
diesel::sql_query("PRAGMA defer_foreign_keys = ON").execute(&self.connection)?;
Ok(())
}

View file

@ -1,6 +1,5 @@
use super::schema::persons;
use super::Database;
use anyhow::Result;
use super::{Database, DatabaseResult};
use diesel::prelude::*;
use serde::{Deserialize, Serialize};
@ -27,7 +26,7 @@ impl Person {
impl Database {
/// Update an existing person or insert a new one.
pub fn update_person(&self, person: Person) -> Result<()> {
pub fn update_person(&self, person: Person) -> DatabaseResult<()> {
self.defer_foreign_keys()?;
self.connection.transaction(|| {
@ -40,7 +39,7 @@ impl Database {
}
/// Get an existing person.
pub fn get_person(&self, id: &str) -> Result<Option<Person>> {
pub fn get_person(&self, id: &str) -> DatabaseResult<Option<Person>> {
let person = persons::table
.filter(persons::id.eq(id))
.load::<Person>(&self.connection)?
@ -51,14 +50,14 @@ impl Database {
}
/// Delete an existing person.
pub fn delete_person(&self, id: &str) -> Result<()> {
pub fn delete_person(&self, id: &str) -> DatabaseResult<()> {
diesel::delete(persons::table.filter(persons::id.eq(id))).execute(&self.connection)?;
Ok(())
}
/// Get all existing persons.
pub fn get_persons(&self) -> Result<Vec<Person>> {
pub fn get_persons(&self) -> DatabaseResult<Vec<Person>> {
let persons = persons::table.load::<Person>(&self.connection)?;
Ok(persons)

View file

@ -1,7 +1,6 @@
use super::generate_id;
use super::schema::{ensembles, performances, persons, recordings};
use super::{Database, Ensemble, Instrument, Person, Work};
use anyhow::{anyhow, Error, Result};
use super::{Database, Ensemble, DatabaseError, Instrument, Person, DatabaseResult, Work};
use diesel::prelude::*;
use serde::{Deserialize, Serialize};
@ -120,9 +119,9 @@ impl Recording {
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<()> {
pub fn update_recording(&self, recording: Recording) -> DatabaseResult<()> {
self.defer_foreign_keys()?;
self.connection.transaction::<(), Error, _>(|| {
self.connection.transaction::<(), DatabaseError, _>(|| {
let recording_id = &recording.id;
self.delete_recording(recording_id)?;
@ -180,7 +179,7 @@ impl Database {
}
/// Check whether the database contains a recording.
pub fn recording_exists(&self, id: &str) -> Result<bool> {
pub fn recording_exists(&self, id: &str) -> DatabaseResult<bool> {
let exists = recordings::table
.filter(recordings::id.eq(id))
.load::<RecordingRow>(&self.connection)?
@ -191,7 +190,7 @@ impl Database {
}
/// Get an existing recording.
pub fn get_recording(&self, id: &str) -> Result<Option<Recording>> {
pub fn get_recording(&self, id: &str) -> DatabaseResult<Option<Recording>> {
let row = recordings::table
.filter(recordings::id.eq(id))
.load::<RecordingRow>(&self.connection)?
@ -207,7 +206,7 @@ impl Database {
}
/// Retrieve all available information on a recording from related tables.
fn get_recording_data(&self, row: RecordingRow) -> Result<Recording> {
fn get_recording_data(&self, row: RecordingRow) -> DatabaseResult<Recording> {
let mut performance_descriptions: Vec<Performance> = Vec::new();
let performance_rows = performances::table
@ -219,21 +218,33 @@ impl Database {
person: match row.person {
Some(id) => Some(
self.get_person(&id)?
.ok_or(anyhow!("No person with ID: {}", id))?,
.ok_or(DatabaseError::Other(format!(
"Failed to get person ({}) for recording ({}).",
id,
row.id,
)))?
),
None => None,
},
ensemble: match row.ensemble {
Some(id) => Some(
self.get_ensemble(&id)?
.ok_or(anyhow!("No ensemble with ID: {}", id))?,
.ok_or(DatabaseError::Other(format!(
"Failed to get ensemble ({}) for recording ({}).",
id,
row.id,
)))?
),
None => None,
},
role: match row.role {
Some(id) => Some(
self.get_instrument(&id)?
.ok_or(anyhow!("No instrument with ID: {}", id))?,
.ok_or(DatabaseError::Other(format!(
"Failed to get instrument ({}) for recording ({}).",
id,
row.id,
)))?
),
None => None,
},
@ -243,7 +254,11 @@ impl Database {
let work_id = &row.work;
let work = self
.get_work(work_id)?
.ok_or(anyhow!("Work doesn't exist: {}", work_id))?;
.ok_or(DatabaseError::Other(format!(
"Failed to get work ({}) for recording ({}).",
work_id,
row.id,
)))?;
let recording_description = Recording {
id: row.id,
@ -256,7 +271,7 @@ impl Database {
}
/// Get all available information on all recordings where a person is performing.
pub fn get_recordings_for_person(&self, person_id: &str) -> Result<Vec<Recording>> {
pub fn get_recordings_for_person(&self, person_id: &str) -> DatabaseResult<Vec<Recording>> {
let mut recordings: Vec<Recording> = Vec::new();
let rows = recordings::table
@ -274,7 +289,7 @@ impl Database {
}
/// Get all available information on all recordings where an ensemble is performing.
pub fn get_recordings_for_ensemble(&self, ensemble_id: &str) -> Result<Vec<Recording>> {
pub fn get_recordings_for_ensemble(&self, ensemble_id: &str) -> DatabaseResult<Vec<Recording>> {
let mut recordings: Vec<Recording> = Vec::new();
let rows = recordings::table
@ -292,7 +307,7 @@ impl Database {
}
/// Get allavailable information on all recordings of a work.
pub fn get_recordings_for_work(&self, work_id: &str) -> Result<Vec<Recording>> {
pub fn get_recordings_for_work(&self, work_id: &str) -> DatabaseResult<Vec<Recording>> {
let mut recordings: Vec<Recording> = Vec::new();
let rows = recordings::table
@ -308,7 +323,7 @@ impl Database {
/// 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<()> {
pub fn delete_recording(&self, id: &str) -> DatabaseResult<()> {
diesel::delete(recordings::table.filter(recordings::id.eq(id)))
.execute(&self.connection)?;
Ok(())

View file

@ -1,37 +1,36 @@
use super::*;
use anyhow::Result;
use futures_channel::oneshot;
use futures_channel::oneshot::Sender;
use std::sync::mpsc;
use std::thread;
/// An action the database thread can perform.
enum Action {
UpdatePerson(Person, Sender<Result<()>>),
GetPerson(String, Sender<Result<Option<Person>>>),
DeletePerson(String, Sender<Result<()>>),
GetPersons(Sender<Result<Vec<Person>>>),
UpdateInstrument(Instrument, Sender<Result<()>>),
GetInstrument(String, Sender<Result<Option<Instrument>>>),
DeleteInstrument(String, Sender<Result<()>>),
GetInstruments(Sender<Result<Vec<Instrument>>>),
UpdateWork(Work, Sender<Result<()>>),
DeleteWork(String, Sender<Result<()>>),
GetWorks(String, Sender<Result<Vec<Work>>>),
UpdateEnsemble(Ensemble, Sender<Result<()>>),
GetEnsemble(String, Sender<Result<Option<Ensemble>>>),
DeleteEnsemble(String, Sender<Result<()>>),
GetEnsembles(Sender<Result<Vec<Ensemble>>>),
UpdateRecording(Recording, Sender<Result<()>>),
DeleteRecording(String, Sender<Result<()>>),
GetRecordingsForPerson(String, Sender<Result<Vec<Recording>>>),
GetRecordingsForEnsemble(String, Sender<Result<Vec<Recording>>>),
GetRecordingsForWork(String, Sender<Result<Vec<Recording>>>),
RecordingExists(String, Sender<Result<bool>>),
UpdateMedium(Medium, Sender<Result<()>>),
GetMedium(String, Sender<Result<Option<Medium>>>),
DeleteMedium(String, Sender<Result<()>>),
GetTrackSets(String, Sender<Result<Vec<TrackSet>>>),
pub enum Action {
UpdatePerson(Person, Sender<DatabaseResult<()>>),
GetPerson(String, Sender<DatabaseResult<Option<Person>>>),
DeletePerson(String, Sender<DatabaseResult<()>>),
GetPersons(Sender<DatabaseResult<Vec<Person>>>),
UpdateInstrument(Instrument, Sender<DatabaseResult<()>>),
GetInstrument(String, Sender<DatabaseResult<Option<Instrument>>>),
DeleteInstrument(String, Sender<DatabaseResult<()>>),
GetInstruments(Sender<DatabaseResult<Vec<Instrument>>>),
UpdateWork(Work, Sender<DatabaseResult<()>>),
DeleteWork(String, Sender<DatabaseResult<()>>),
GetWorks(String, Sender<DatabaseResult<Vec<Work>>>),
UpdateEnsemble(Ensemble, Sender<DatabaseResult<()>>),
GetEnsemble(String, Sender<DatabaseResult<Option<Ensemble>>>),
DeleteEnsemble(String, Sender<DatabaseResult<()>>),
GetEnsembles(Sender<DatabaseResult<Vec<Ensemble>>>),
UpdateRecording(Recording, Sender<DatabaseResult<()>>),
DeleteRecording(String, Sender<DatabaseResult<()>>),
GetRecordingsForPerson(String, Sender<DatabaseResult<Vec<Recording>>>),
GetRecordingsForEnsemble(String, Sender<DatabaseResult<Vec<Recording>>>),
GetRecordingsForWork(String, Sender<DatabaseResult<Vec<Recording>>>),
RecordingExists(String, Sender<DatabaseResult<bool>>),
UpdateMedium(Medium, Sender<DatabaseResult<()>>),
GetMedium(String, Sender<DatabaseResult<Option<Medium>>>),
DeleteMedium(String, Sender<DatabaseResult<()>>),
GetTrackSets(String, Sender<DatabaseResult<Vec<TrackSet>>>),
Stop(Sender<()>),
}
@ -44,7 +43,7 @@ pub struct DbThread {
impl DbThread {
/// Create a new database connection in a background thread.
pub async fn new(path: String) -> Result<Self> {
pub async fn new(path: String) -> DatabaseResult<Self> {
let (action_sender, action_receiver) = mpsc::channel();
let (ready_sender, ready_receiver) = oneshot::channel();
@ -150,14 +149,14 @@ impl DbThread {
}
/// Update an existing person or insert a new one.
pub async fn update_person(&self, person: Person) -> Result<()> {
pub async fn update_person(&self, person: Person) -> DatabaseResult<()> {
let (sender, receiver) = oneshot::channel();
self.action_sender.send(UpdatePerson(person, sender))?;
receiver.await?
}
/// Get an existing person.
pub async fn get_person(&self, id: &str) -> Result<Option<Person>> {
pub async fn get_person(&self, id: &str) -> DatabaseResult<Option<Person>> {
let (sender, receiver) = oneshot::channel();
self.action_sender.send(GetPerson(id.to_string(), sender))?;
receiver.await?
@ -165,7 +164,7 @@ impl DbThread {
/// Delete an existing person. This will fail, if there are still other items referencing
/// this person.
pub async fn delete_person(&self, id: &str) -> Result<()> {
pub async fn delete_person(&self, id: &str) -> DatabaseResult<()> {
let (sender, receiver) = oneshot::channel();
self.action_sender
.send(DeletePerson(id.to_string(), sender))?;
@ -173,14 +172,14 @@ impl DbThread {
}
/// Get all existing persons.
pub async fn get_persons(&self) -> Result<Vec<Person>> {
pub async fn get_persons(&self) -> DatabaseResult<Vec<Person>> {
let (sender, receiver) = oneshot::channel();
self.action_sender.send(GetPersons(sender))?;
receiver.await?
}
/// Update an existing instrument or insert a new one.
pub async fn update_instrument(&self, instrument: Instrument) -> Result<()> {
pub async fn update_instrument(&self, instrument: Instrument) -> DatabaseResult<()> {
let (sender, receiver) = oneshot::channel();
self.action_sender
.send(UpdateInstrument(instrument, sender))?;
@ -188,7 +187,7 @@ impl DbThread {
}
/// Get an existing instrument.
pub async fn get_instrument(&self, id: &str) -> Result<Option<Instrument>> {
pub async fn get_instrument(&self, id: &str) -> DatabaseResult<Option<Instrument>> {
let (sender, receiver) = oneshot::channel();
self.action_sender
.send(GetInstrument(id.to_string(), sender))?;
@ -197,7 +196,7 @@ impl DbThread {
/// Delete an existing instrument. This will fail, if there are still other items referencing
/// this instrument.
pub async fn delete_instrument(&self, id: &str) -> Result<()> {
pub async fn delete_instrument(&self, id: &str) -> DatabaseResult<()> {
let (sender, receiver) = oneshot::channel();
self.action_sender
.send(DeleteInstrument(id.to_string(), sender))?;
@ -205,14 +204,14 @@ impl DbThread {
}
/// Get all existing instruments.
pub async fn get_instruments(&self) -> Result<Vec<Instrument>> {
pub async fn get_instruments(&self) -> DatabaseResult<Vec<Instrument>> {
let (sender, receiver) = oneshot::channel();
self.action_sender.send(GetInstruments(sender))?;
receiver.await?
}
/// Update an existing work or insert a new one.
pub async fn update_work(&self, work: Work) -> Result<()> {
pub async fn update_work(&self, work: Work) -> DatabaseResult<()> {
let (sender, receiver) = oneshot::channel();
self.action_sender.send(UpdateWork(work, sender))?;
receiver.await?
@ -220,7 +219,7 @@ impl DbThread {
/// Delete an existing work. This will fail, if there are still other items referencing
/// this work.
pub async fn delete_work(&self, id: &str) -> Result<()> {
pub async fn delete_work(&self, id: &str) -> DatabaseResult<()> {
let (sender, receiver) = oneshot::channel();
self.action_sender
.send(DeleteWork(id.to_string(), sender))?;
@ -228,7 +227,7 @@ impl DbThread {
}
/// Get information on all existing works by a composer.
pub async fn get_works(&self, person_id: &str) -> Result<Vec<Work>> {
pub async fn get_works(&self, person_id: &str) -> DatabaseResult<Vec<Work>> {
let (sender, receiver) = oneshot::channel();
self.action_sender
.send(GetWorks(person_id.to_string(), sender))?;
@ -236,14 +235,14 @@ impl DbThread {
}
/// Update an existing ensemble or insert a new one.
pub async fn update_ensemble(&self, ensemble: Ensemble) -> Result<()> {
pub async fn update_ensemble(&self, ensemble: Ensemble) -> DatabaseResult<()> {
let (sender, receiver) = oneshot::channel();
self.action_sender.send(UpdateEnsemble(ensemble, sender))?;
receiver.await?
}
/// Get an existing ensemble.
pub async fn get_ensemble(&self, id: &str) -> Result<Option<Ensemble>> {
pub async fn get_ensemble(&self, id: &str) -> DatabaseResult<Option<Ensemble>> {
let (sender, receiver) = oneshot::channel();
self.action_sender
.send(GetEnsemble(id.to_string(), sender))?;
@ -252,7 +251,7 @@ impl DbThread {
/// Delete an existing ensemble. This will fail, if there are still other items referencing
/// this ensemble.
pub async fn delete_ensemble(&self, id: &str) -> Result<()> {
pub async fn delete_ensemble(&self, id: &str) -> DatabaseResult<()> {
let (sender, receiver) = oneshot::channel();
self.action_sender
.send(DeleteEnsemble(id.to_string(), sender))?;
@ -260,14 +259,14 @@ impl DbThread {
}
/// Get all existing ensembles.
pub async fn get_ensembles(&self) -> Result<Vec<Ensemble>> {
pub async fn get_ensembles(&self) -> DatabaseResult<Vec<Ensemble>> {
let (sender, receiver) = oneshot::channel();
self.action_sender.send(GetEnsembles(sender))?;
receiver.await?
}
/// Update an existing recording or insert a new one.
pub async fn update_recording(&self, recording: Recording) -> Result<()> {
pub async fn update_recording(&self, recording: Recording) -> DatabaseResult<()> {
let (sender, receiver) = oneshot::channel();
self.action_sender
.send(UpdateRecording(recording, sender))?;
@ -275,7 +274,7 @@ impl DbThread {
}
/// Delete an existing recording.
pub async fn delete_recording(&self, id: &str) -> Result<()> {
pub async fn delete_recording(&self, id: &str) -> DatabaseResult<()> {
let (sender, receiver) = oneshot::channel();
self.action_sender
.send(DeleteRecording(id.to_string(), sender))?;
@ -283,7 +282,7 @@ impl DbThread {
}
/// Get information on all recordings in which a person performs.
pub async fn get_recordings_for_person(&self, person_id: &str) -> Result<Vec<Recording>> {
pub async fn get_recordings_for_person(&self, person_id: &str) -> DatabaseResult<Vec<Recording>> {
let (sender, receiver) = oneshot::channel();
self.action_sender
.send(GetRecordingsForPerson(person_id.to_string(), sender))?;
@ -291,7 +290,7 @@ impl DbThread {
}
/// Get information on all recordings in which an ensemble performs.
pub async fn get_recordings_for_ensemble(&self, ensemble_id: &str) -> Result<Vec<Recording>> {
pub async fn get_recordings_for_ensemble(&self, ensemble_id: &str) -> DatabaseResult<Vec<Recording>> {
let (sender, receiver) = oneshot::channel();
self.action_sender
.send(GetRecordingsForEnsemble(ensemble_id.to_string(), sender))?;
@ -299,7 +298,7 @@ impl DbThread {
}
/// Get information on all recordings of a work.
pub async fn get_recordings_for_work(&self, work_id: &str) -> Result<Vec<Recording>> {
pub async fn get_recordings_for_work(&self, work_id: &str) -> DatabaseResult<Vec<Recording>> {
let (sender, receiver) = oneshot::channel();
self.action_sender
.send(GetRecordingsForWork(work_id.to_string(), sender))?;
@ -307,7 +306,7 @@ impl DbThread {
}
/// Check whether a recording exists within the database.
pub async fn recording_exists(&self, id: &str) -> Result<bool> {
pub async fn recording_exists(&self, id: &str) -> DatabaseResult<bool> {
let (sender, receiver) = oneshot::channel();
self.action_sender
.send(RecordingExists(id.to_string(), sender))?;
@ -315,7 +314,7 @@ impl DbThread {
}
/// Update an existing medium or insert a new one.
pub async fn update_medium(&self, medium: Medium) -> Result<()> {
pub async fn update_medium(&self, medium: Medium) -> DatabaseResult<()> {
let (sender, receiver) = oneshot::channel();
self.action_sender.send(UpdateMedium(medium, sender))?;
receiver.await?
@ -323,7 +322,7 @@ impl DbThread {
/// Delete an existing medium. This will fail, if there are still other
/// items referencing this medium.
pub async fn delete_medium(&self, id: &str) -> Result<()> {
pub async fn delete_medium(&self, id: &str) -> DatabaseResult<()> {
let (sender, receiver) = oneshot::channel();
self.action_sender
@ -333,21 +332,21 @@ impl DbThread {
}
/// Get an existing medium.
pub async fn get_medium(&self, id: &str) -> Result<Option<Medium>> {
pub async fn get_medium(&self, id: &str) -> DatabaseResult<Option<Medium>> {
let (sender, receiver) = oneshot::channel();
self.action_sender.send(GetMedium(id.to_owned(), sender))?;
receiver.await?
}
/// Get all track sets for a recording.
pub async fn get_track_sets(&self, recording_id: &str) -> Result<Vec<TrackSet>> {
pub async fn get_track_sets(&self, recording_id: &str) -> DatabaseResult<Vec<TrackSet>> {
let (sender, receiver) = oneshot::channel();
self.action_sender.send(GetTrackSets(recording_id.to_owned(), sender))?;
receiver.await?
}
/// Stop the database thread. Any future access to the database will fail.
pub async fn stop(&self) -> Result<()> {
pub async fn stop(&self) -> DatabaseResult<()> {
let (sender, receiver) = oneshot::channel();
self.action_sender.send(Stop(sender))?;
Ok(receiver.await?)

View file

@ -1,7 +1,6 @@
use super::generate_id;
use super::schema::{instrumentations, work_parts, work_sections, works};
use super::{Database, Instrument, Person};
use anyhow::{anyhow, Error, Result};
use super::{Database, DatabaseError, Instrument, Person, DatabaseResult};
use diesel::prelude::*;
use diesel::{Insertable, Queryable};
use serde::{Deserialize, Serialize};
@ -106,10 +105,10 @@ 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<()> {
pub fn update_work(&self, work: Work) -> DatabaseResult<()> {
self.defer_foreign_keys()?;
self.connection.transaction::<(), Error, _>(|| {
self.connection.transaction::<(), DatabaseError, _>(|| {
let work_id = &work.id;
self.delete_work(work_id)?;
@ -163,7 +162,7 @@ impl Database {
let row = WorkPartRow {
id: rand::random(),
work: work_id.to_string(),
part_index: index.try_into()?,
part_index: index as i64,
title: part.title,
composer: part.composer.map(|person| person.id),
};
@ -178,7 +177,7 @@ impl Database {
id: rand::random(),
work: work_id.to_string(),
title: section.title,
before_index: section.before_index.try_into()?,
before_index: section.before_index as i64,
};
diesel::insert_into(work_sections::table)
@ -195,7 +194,7 @@ impl Database {
}
/// Get an existing work.
pub fn get_work(&self, id: &str) -> Result<Option<Work>> {
pub fn get_work(&self, id: &str) -> DatabaseResult<Option<Work>> {
let row = works::table
.filter(works::id.eq(id))
.load::<WorkRow>(&self.connection)?
@ -211,7 +210,7 @@ impl Database {
}
/// Retrieve all available information on a work from related tables.
fn get_work_data(&self, row: WorkRow) -> Result<Work> {
fn get_work_data(&self, row: WorkRow) -> DatabaseResult<Work> {
let mut instruments: Vec<Instrument> = Vec::new();
let instrumentations = instrumentations::table
@ -222,7 +221,11 @@ impl Database {
let id = &instrumentation.instrument;
instruments.push(
self.get_instrument(id)?
.ok_or(anyhow!("No instrument with ID: {}", id))?,
.ok_or(DatabaseError::Other(format!(
"Failed to get instrument ({}) for work ({}).",
id,
row.id,
)))?
);
}
@ -238,7 +241,11 @@ impl Database {
composer: match part_row.composer {
Some(composer) => Some(
self.get_person(&composer)?
.ok_or(anyhow!("No person with ID: {}", composer))?,
.ok_or(DatabaseError::Other(format!(
"Failed to get person ({}) for work ({}).",
composer,
row.id,
)))?
),
None => None,
},
@ -254,14 +261,18 @@ impl Database {
for section_row in section_rows {
sections.push(WorkSection {
title: section_row.title,
before_index: section_row.before_index.try_into()?,
before_index: section_row.before_index as usize,
});
}
let person_id = &row.composer;
let person = self
.get_person(person_id)?
.ok_or(anyhow!("Person doesn't exist: {}", person_id))?;
.ok_or(DatabaseError::Other(format!(
"Failed to get person ({}) for work ({}).",
person_id,
row.id,
)))?;
Ok(Work {
id: row.id,
@ -275,13 +286,13 @@ impl Database {
/// 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<()> {
pub fn delete_work(&self, id: &str) -> DatabaseResult<()> {
diesel::delete(works::table.filter(works::id.eq(id))).execute(&self.connection)?;
Ok(())
}
/// Get all existing works by a composer and related information from other tables.
pub fn get_works(&self, composer_id: &str) -> Result<Vec<Work>> {
pub fn get_works(&self, composer_id: &str) -> DatabaseResult<Vec<Work>> {
let mut works: Vec<Work> = Vec::new();
let rows = works::table

55
src/backend/error.rs Normal file
View file

@ -0,0 +1,55 @@
use isahc::http::StatusCode;
/// An error that can happen within the backend.
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("The users login credentials were wrong.")]
LoginFailed,
#[error("The user has to be logged in to perform this action.")]
Unauthorized,
#[error("The user is not allowed to perform this action.")]
Forbidden,
#[error("The server returned an unexpected status code: {0}.")]
UnexpectedResponse(StatusCode),
#[error("A networking error happened.")]
NetworkError(#[from] isahc::Error),
#[error("A networking error happened.")]
HttpError(#[from] isahc::http::Error),
#[error(transparent)]
DatabaseError(#[from] crate::backend::DatabaseError),
#[error("An IO error happened.")]
IoError(#[from] std::io::Error),
#[error("An error happened using the SecretService.")]
SecretServiceError(#[from] secret_service::Error),
#[error("An error happened while serializing or deserializing.")]
SerdeError(#[from] serde_json::Error),
#[error("An error happened in GLib.")]
GlibError(#[from] glib::BoolError),
#[error("A channel was canceled.")]
ChannelError(#[from] futures_channel::oneshot::Canceled),
#[error("Error decoding to UTF8.")]
Utf8Error(#[from] std::str::Utf8Error),
#[error("An error happened: {0}")]
Other(&'static str),
// TODO: Remove this once anyhow has been dropped as a dependency.
#[error("An unkown error happened.")]
Unknown(#[from] anyhow::Error),
}
pub type Result<T> = std::result::Result<T, Error>;

View file

@ -42,6 +42,7 @@ sources = files(
'backend/client/register.rs',
'backend/client/works.rs',
'backend/database/ensembles.rs',
'backend/database/error.rs',
'backend/database/instruments.rs',
'backend/database/medium.rs',
'backend/database/mod.rs',
@ -50,6 +51,7 @@ sources = files(
'backend/database/schema.rs',
'backend/database/thread.rs',
'backend/database/works.rs',
'backend/error.rs',
'backend/library.rs',
'backend/mod.rs',
'backend/player.rs',