musicus/database/src/medium.rs

293 lines
9.2 KiB
Rust
Raw Normal View History

2020-12-20 11:47:27 +01:00
use super::generate_id;
2021-04-08 00:08:31 +02:00
use super::schema::{ensembles, mediums, performances, persons, recordings, tracks};
2021-02-04 21:47:22 +01:00
use super::{Database, Error, Recording, Result};
2020-12-20 11:47:27 +01:00
use diesel::prelude::*;
2021-04-25 23:12:19 +02:00
use log::info;
2020-12-20 11:47:27 +01:00
/// 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).
2022-01-23 14:33:35 +01:00
#[derive(PartialEq, Eq, Hash, Debug, Clone)]
2020-12-20 11:47:27 +01:00
pub struct Medium {
2021-01-13 16:10:48 +01:00
/// An unique ID for the medium.
2020-12-20 11:47:27 +01:00
pub id: String,
2021-01-13 16:10:48 +01:00
/// The human identifier for the medium.
2020-12-20 11:47:27 +01:00
pub name: String,
2021-01-13 16:10:48 +01:00
/// If applicable, the MusicBrainz DiscID.
2020-12-20 11:47:27 +01:00
pub discid: Option<String>,
2021-01-13 16:10:48 +01:00
2021-04-08 00:08:31 +02:00
/// The tracks of the medium.
2020-12-20 11:47:27 +01:00
pub tracks: Vec<Track>,
}
2021-04-08 00:08:31 +02:00
/// A track on a medium.
2022-01-23 14:33:35 +01:00
#[derive(PartialEq, Eq, Hash, Debug, Clone)]
2020-12-20 11:47:27 +01:00
pub struct Track {
2021-04-08 00:08:31 +02:00
/// The recording on this track.
pub recording: Recording,
2021-01-13 16:10:48 +01:00
/// The work parts that are played on this track. They are indices to the
/// work parts of the work that is associated with the recording.
pub work_parts: Vec<usize>,
2021-01-15 22:27:43 +01:00
2021-04-15 18:19:17 +02:00
/// The index of the track within its source. This is used to associate
/// the metadata with the audio data from the source when importing.
pub source_index: usize,
2022-01-23 14:33:35 +01:00
/// The path to the audio file containing this track.
2021-01-15 22:27:43 +01:00
pub path: String,
2020-12-20 11:47:27 +01:00
}
/// Table data for a [`Medium`].
#[derive(Insertable, Queryable, Debug, Clone)]
#[table_name = "mediums"]
struct MediumRow {
pub id: String,
pub name: String,
pub discid: Option<String>,
}
/// Table data for a [`Track`].
#[derive(Insertable, Queryable, QueryableByName, Debug, Clone)]
2020-12-20 11:47:27 +01:00
#[table_name = "tracks"]
struct TrackRow {
pub id: String,
2021-04-08 00:08:31 +02:00
pub medium: String,
2020-12-20 11:47:27 +01:00
pub index: i32,
2021-04-08 00:08:31 +02:00
pub recording: String,
2020-12-20 11:47:27 +01:00
pub work_parts: String,
2021-04-15 18:19:17 +02:00
pub source_index: i32,
2021-01-15 22:27:43 +01:00
pub path: String,
2020-12-20 11:47:27 +01:00
}
impl Database {
/// Update an existing medium or insert a new one.
2021-02-04 21:47:22 +01:00
pub fn update_medium(&self, medium: Medium) -> Result<()> {
2021-04-25 23:12:19 +02:00
info!("Updating medium {:?}", medium);
2020-12-20 11:47:27 +01:00
self.defer_foreign_keys()?;
2021-02-04 21:47:22 +01:00
self.connection.transaction::<(), Error, _>(|| {
2020-12-20 11:47:27 +01:00
let medium_id = &medium.id;
2021-04-08 00:08:31 +02:00
// This will also delete the tracks.
2020-12-20 11:47:27 +01:00
self.delete_medium(medium_id)?;
2021-01-15 22:27:43 +01:00
// Add the new medium.
let medium_row = MediumRow {
id: medium_id.to_owned(),
name: medium.name.clone(),
discid: medium.discid.clone(),
};
diesel::insert_into(mediums::table)
.values(medium_row)
.execute(&self.connection)?;
2021-04-08 00:08:31 +02:00
for (index, track) in medium.tracks.iter().enumerate() {
// Add associated items from the server, if they don't already exist.
2021-01-15 22:27:43 +01:00
2021-04-08 00:08:31 +02:00
if self.get_recording(&track.recording.id)?.is_none() {
self.update_recording(track.recording.clone())?;
2021-01-15 22:27:43 +01:00
}
2021-04-08 00:08:31 +02:00
// Add the actual track data.
2021-01-15 22:27:43 +01:00
2021-04-08 00:08:31 +02:00
let work_parts = track
.work_parts
.iter()
.map(|part_index| part_index.to_string())
.collect::<Vec<String>>()
.join(",");
2020-12-20 11:47:27 +01:00
2021-04-08 00:08:31 +02:00
let track_row = TrackRow {
id: generate_id(),
2020-12-20 11:47:27 +01:00
medium: medium_id.to_owned(),
index: index as i32,
2021-04-08 00:08:31 +02:00
recording: track.recording.id.clone(),
work_parts,
2021-04-15 18:19:17 +02:00
source_index: track.source_index as i32,
2021-04-08 00:08:31 +02:00
path: track.path.clone(),
2020-12-20 11:47:27 +01:00
};
2021-04-08 00:08:31 +02:00
diesel::insert_into(tracks::table)
.values(track_row)
2020-12-20 11:47:27 +01:00
.execute(&self.connection)?;
}
Ok(())
})?;
Ok(())
}
/// Get an existing medium.
2021-02-04 21:47:22 +01:00
pub fn get_medium(&self, id: &str) -> Result<Option<Medium>> {
2020-12-20 11:47:27 +01:00
let row = mediums::table
.filter(mediums::id.eq(id))
.load::<MediumRow>(&self.connection)?
.into_iter()
.next();
let medium = match row {
Some(row) => Some(self.get_medium_data(row)?),
None => None,
};
Ok(medium)
2021-02-06 23:02:38 +01:00
}
2021-03-23 09:53:16 +01:00
/// Get mediums that have a specific source ID.
pub fn get_mediums_by_source_id(&self, source_id: &str) -> Result<Vec<Medium>> {
let mut mediums: Vec<Medium> = Vec::new();
let rows = mediums::table
.filter(mediums::discid.nullable().eq(source_id))
.load::<MediumRow>(&self.connection)?;
for row in rows {
let medium = self.get_medium_data(row)?;
mediums.push(medium);
}
Ok(mediums)
}
2021-02-06 23:02:38 +01:00
/// Get mediums on which this person is performing.
pub fn get_mediums_for_person(&self, person_id: &str) -> Result<Vec<Medium>> {
let mut mediums: Vec<Medium> = Vec::new();
let rows = mediums::table
2021-04-08 00:08:31 +02:00
.inner_join(tracks::table.on(tracks::medium.eq(mediums::id)))
.inner_join(recordings::table.on(recordings::id.eq(tracks::recording)))
2021-02-06 23:02:38 +01:00
.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::<MediumRow>(&self.connection)?;
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<Vec<Medium>> {
let mut mediums: Vec<Medium> = Vec::new();
let rows = mediums::table
2021-04-08 00:08:31 +02:00
.inner_join(tracks::table.on(tracks::medium.eq(tracks::id)))
.inner_join(recordings::table.on(recordings::id.eq(tracks::recording)))
2021-02-06 23:02:38 +01:00
.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::<MediumRow>(&self.connection)?;
for row in rows {
let medium = self.get_medium_data(row)?;
mediums.push(medium);
}
Ok(mediums)
2020-12-20 11:47:27 +01:00
}
/// Delete a medium and all of its tracks. This will fail, if the music
/// library contains audio files referencing any of those tracks.
2021-02-04 21:47:22 +01:00
pub fn delete_medium(&self, id: &str) -> Result<()> {
2021-04-25 23:12:19 +02:00
info!("Deleting medium {}", id);
2020-12-20 11:47:27 +01:00
diesel::delete(mediums::table.filter(mediums::id.eq(id))).execute(&self.connection)?;
Ok(())
}
2021-04-08 00:08:31 +02:00
/// Get all available tracks for a recording.
pub fn get_tracks(&self, recording_id: &str) -> Result<Vec<Track>> {
let mut tracks: Vec<Track> = Vec::new();
2021-01-15 22:27:43 +01:00
2021-04-08 00:08:31 +02:00
let rows = tracks::table
.inner_join(recordings::table.on(recordings::id.eq(tracks::recording)))
2021-01-15 22:27:43 +01:00
.filter(recordings::id.eq(recording_id))
2021-04-08 00:08:31 +02:00
.select(tracks::table::all_columns())
.load::<TrackRow>(&self.connection)?;
2021-01-15 22:27:43 +01:00
for row in rows {
2021-04-08 00:08:31 +02:00
let track = self.get_track_from_row(row)?;
tracks.push(track);
2021-01-15 22:27:43 +01:00
}
2021-04-08 00:08:31 +02:00
Ok(tracks)
2021-01-15 22:27:43 +01:00
}
/// Get a random track from the database.
pub fn random_track(&self) -> Result<Track> {
let row = diesel::sql_query("SELECT * FROM tracks ORDER BY RANDOM() LIMIT 1")
.load::<TrackRow>(&self.connection)?
.into_iter()
.next()
.ok_or(Error::Other("Failed to generate random track"))?;
self.get_track_from_row(row)
}
2020-12-20 11:47:27 +01:00
/// Retrieve all available information on a medium from related tables.
2021-02-04 21:47:22 +01:00
fn get_medium_data(&self, row: MediumRow) -> Result<Medium> {
2021-04-08 00:08:31 +02:00
let track_rows = tracks::table
.filter(tracks::medium.eq(&row.id))
.order_by(tracks::index)
.load::<TrackRow>(&self.connection)?;
2020-12-20 11:47:27 +01:00
2021-04-08 00:08:31 +02:00
let mut tracks = Vec::new();
2020-12-20 11:47:27 +01:00
2021-04-08 00:08:31 +02:00
for track_row in track_rows {
let track = self.get_track_from_row(track_row)?;
tracks.push(track);
2020-12-20 11:47:27 +01:00
}
let medium = Medium {
id: row.id,
name: row.name,
discid: row.discid,
2021-04-08 00:08:31 +02:00
tracks,
2020-12-20 11:47:27 +01:00
};
Ok(medium)
}
2021-01-16 15:08:12 +01:00
2021-04-08 00:08:31 +02:00
/// Convert a track row from the database to an actual track.
fn get_track_from_row(&self, row: TrackRow) -> Result<Track> {
2021-01-16 15:08:12 +01:00
let recording_id = row.recording;
let recording = self
.get_recording(&recording_id)?
2021-04-25 22:48:25 +02:00
.ok_or(Error::MissingItem("recording", recording_id))?;
2021-01-16 15:08:12 +01:00
2021-04-08 00:08:31 +02:00
let mut part_indices = Vec::new();
2021-01-16 15:08:12 +01:00
2021-04-25 22:48:25 +02:00
let work_parts = row.work_parts.split(',');
2021-04-08 00:08:31 +02:00
for part_index in work_parts {
if !part_index.is_empty() {
2021-04-25 23:22:55 +02:00
let index = str::parse(part_index)
.map_err(|_| Error::ParsingError("part index", String::from(part_index)))?;
2021-04-08 00:08:31 +02:00
part_indices.push(index);
}
2021-01-16 15:08:12 +01:00
}
2021-04-08 00:08:31 +02:00
let track = Track {
recording,
work_parts: part_indices,
2021-04-15 18:19:17 +02:00
source_index: row.source_index as usize,
2021-04-08 00:08:31 +02:00
path: row.path,
};
2021-01-16 15:08:12 +01:00
2021-04-08 00:08:31 +02:00
Ok(track)
2021-01-16 15:08:12 +01:00
}
2020-12-20 11:47:27 +01:00
}