mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 19:57:25 +01:00
Use flat track list for mediums
This commit is contained in:
parent
e293972c0d
commit
4f617cb79a
11 changed files with 327 additions and 532 deletions
|
|
@ -10,6 +10,5 @@ DROP TABLE "ensembles";
|
|||
DROP TABLE "recordings";
|
||||
DROP TABLE "performances";
|
||||
DROP TABLE "mediums";
|
||||
DROP TABLE "track_sets";
|
||||
DROP TABLE "tracks";
|
||||
|
||||
|
|
|
|||
|
|
@ -60,17 +60,11 @@ CREATE TABLE "mediums" (
|
|||
"discid" TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE "track_sets" (
|
||||
CREATE TABLE "tracks" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"medium" TEXT NOT NULL REFERENCES "mediums"("id") ON DELETE CASCADE,
|
||||
"index" INTEGER NOT NULL,
|
||||
"recording" TEXT NOT NULL REFERENCES "recordings"("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "tracks" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"track_set" TEXT NOT NULL REFERENCES "track_sets"("id") ON DELETE CASCADE,
|
||||
"index" INTEGER NOT NULL,
|
||||
"recording" TEXT NOT NULL REFERENCES "recordings"("id"),
|
||||
"work_parts" TEXT NOT NULL,
|
||||
"path" TEXT NOT NULL
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use super::generate_id;
|
||||
use super::schema::{ensembles, mediums, performances, persons, recordings, track_sets, tracks};
|
||||
use super::schema::{ensembles, mediums, performances, persons, recordings, tracks};
|
||||
use super::{Database, Error, Recording, Result};
|
||||
use diesel::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -18,25 +18,17 @@ pub struct Medium {
|
|||
/// If applicable, the MusicBrainz DiscID.
|
||||
pub discid: Option<String>,
|
||||
|
||||
/// The tracks of the medium, grouped by recording.
|
||||
pub tracks: Vec<TrackSet>,
|
||||
}
|
||||
|
||||
/// A set of tracks of one recording within a medium.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TrackSet {
|
||||
/// The recording to which the tracks belong.
|
||||
pub recording: Recording,
|
||||
|
||||
/// The actual tracks.
|
||||
/// The tracks of the medium.
|
||||
pub tracks: Vec<Track>,
|
||||
}
|
||||
|
||||
/// A track within a recording on a medium.
|
||||
/// A track on a medium.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Track {
|
||||
/// The recording on this track.
|
||||
pub recording: Recording,
|
||||
|
||||
/// 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>,
|
||||
|
|
@ -56,23 +48,14 @@ struct MediumRow {
|
|||
pub discid: Option<String>,
|
||||
}
|
||||
|
||||
/// Table data for a [`TrackSet`].
|
||||
#[derive(Insertable, Queryable, Debug, Clone)]
|
||||
#[table_name = "track_sets"]
|
||||
struct TrackSetRow {
|
||||
pub id: String,
|
||||
pub medium: String,
|
||||
pub index: i32,
|
||||
pub recording: String,
|
||||
}
|
||||
|
||||
/// Table data for a [`Track`].
|
||||
#[derive(Insertable, Queryable, Debug, Clone)]
|
||||
#[table_name = "tracks"]
|
||||
struct TrackRow {
|
||||
pub id: String,
|
||||
pub track_set: String,
|
||||
pub medium: String,
|
||||
pub index: i32,
|
||||
pub recording: String,
|
||||
pub work_parts: String,
|
||||
pub path: String,
|
||||
}
|
||||
|
|
@ -85,7 +68,7 @@ impl Database {
|
|||
self.connection.transaction::<(), Error, _>(|| {
|
||||
let medium_id = &medium.id;
|
||||
|
||||
// This will also delete the track sets and tracks.
|
||||
// This will also delete the tracks.
|
||||
self.delete_medium(medium_id)?;
|
||||
|
||||
// Add the new medium.
|
||||
|
|
@ -100,49 +83,34 @@ impl Database {
|
|||
.values(medium_row)
|
||||
.execute(&self.connection)?;
|
||||
|
||||
for (index, track_set) in medium.tracks.iter().enumerate() {
|
||||
// Add associated items from the server, if they don't already
|
||||
// exist.
|
||||
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_set.recording.id)?.is_none() {
|
||||
self.update_recording(track_set.recording.clone())?;
|
||||
if self.get_recording(&track.recording.id)?.is_none() {
|
||||
self.update_recording(track.recording.clone())?;
|
||||
}
|
||||
|
||||
// Add the actual track set data.
|
||||
// Add the actual track data.
|
||||
|
||||
let track_set_id = generate_id();
|
||||
let work_parts = track
|
||||
.work_parts
|
||||
.iter()
|
||||
.map(|part_index| part_index.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(",");
|
||||
|
||||
let track_set_row = TrackSetRow {
|
||||
id: track_set_id.clone(),
|
||||
let track_row = TrackRow {
|
||||
id: generate_id(),
|
||||
medium: medium_id.to_owned(),
|
||||
index: index as i32,
|
||||
recording: track_set.recording.id.clone(),
|
||||
recording: track.recording.id.clone(),
|
||||
work_parts,
|
||||
path: track.path.clone(),
|
||||
};
|
||||
|
||||
diesel::insert_into(track_sets::table)
|
||||
.values(track_set_row)
|
||||
diesel::insert_into(tracks::table)
|
||||
.values(track_row)
|
||||
.execute(&self.connection)?;
|
||||
|
||||
for (index, track) in track_set.tracks.iter().enumerate() {
|
||||
let work_parts = track
|
||||
.work_parts
|
||||
.iter()
|
||||
.map(|part_index| part_index.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(",");
|
||||
|
||||
let track_row = TrackRow {
|
||||
id: generate_id(),
|
||||
track_set: track_set_id.clone(),
|
||||
index: index as i32,
|
||||
work_parts,
|
||||
path: track.path.clone(),
|
||||
};
|
||||
|
||||
diesel::insert_into(tracks::table)
|
||||
.values(track_row)
|
||||
.execute(&self.connection)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -172,8 +140,8 @@ impl Database {
|
|||
let mut mediums: Vec<Medium> = Vec::new();
|
||||
|
||||
let rows = mediums::table
|
||||
.inner_join(track_sets::table.on(track_sets::medium.eq(mediums::id)))
|
||||
.inner_join(recordings::table.on(recordings::id.eq(track_sets::recording)))
|
||||
.inner_join(tracks::table.on(tracks::medium.eq(mediums::id)))
|
||||
.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))
|
||||
|
|
@ -194,8 +162,8 @@ impl Database {
|
|||
let mut mediums: Vec<Medium> = Vec::new();
|
||||
|
||||
let rows = mediums::table
|
||||
.inner_join(track_sets::table.on(track_sets::medium.eq(mediums::id)))
|
||||
.inner_join(recordings::table.on(recordings::id.eq(track_sets::recording)))
|
||||
.inner_join(tracks::table.on(tracks::medium.eq(tracks::id)))
|
||||
.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))
|
||||
|
|
@ -218,93 +186,81 @@ impl Database {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Get all available track sets for a recording.
|
||||
pub fn get_track_sets(&self, recording_id: &str) -> Result<Vec<TrackSet>> {
|
||||
let mut track_sets: Vec<TrackSet> = Vec::new();
|
||||
/// 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();
|
||||
|
||||
let rows = track_sets::table
|
||||
.inner_join(recordings::table.on(recordings::id.eq(track_sets::recording)))
|
||||
let rows = tracks::table
|
||||
.inner_join(recordings::table.on(recordings::id.eq(tracks::recording)))
|
||||
.filter(recordings::id.eq(recording_id))
|
||||
.select(track_sets::table::all_columns())
|
||||
.load::<TrackSetRow>(&self.connection)?;
|
||||
.select(tracks::table::all_columns())
|
||||
.load::<TrackRow>(&self.connection)?;
|
||||
|
||||
for row in rows {
|
||||
let track_set = self.get_track_set_from_row(row)?;
|
||||
track_sets.push(track_set);
|
||||
let track = self.get_track_from_row(row)?;
|
||||
tracks.push(track);
|
||||
}
|
||||
|
||||
Ok(track_sets)
|
||||
Ok(tracks)
|
||||
}
|
||||
|
||||
/// Retrieve all available information on a medium from related tables.
|
||||
fn get_medium_data(&self, row: MediumRow) -> Result<Medium> {
|
||||
let track_set_rows = track_sets::table
|
||||
.filter(track_sets::medium.eq(&row.id))
|
||||
.order_by(track_sets::index)
|
||||
.load::<TrackSetRow>(&self.connection)?;
|
||||
|
||||
let mut track_sets = Vec::new();
|
||||
|
||||
for track_set_row in track_set_rows {
|
||||
let track_set = self.get_track_set_from_row(track_set_row)?;
|
||||
track_sets.push(track_set);
|
||||
}
|
||||
|
||||
let medium = Medium {
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
discid: row.discid,
|
||||
tracks: track_sets,
|
||||
};
|
||||
|
||||
Ok(medium)
|
||||
}
|
||||
|
||||
/// Convert a track set row from the database to an actual track set.
|
||||
fn get_track_set_from_row(&self, row: TrackSetRow) -> Result<TrackSet> {
|
||||
let recording_id = row.recording;
|
||||
|
||||
let recording = self
|
||||
.get_recording(&recording_id)?
|
||||
.ok_or(Error::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))
|
||||
.filter(tracks::medium.eq(&row.id))
|
||||
.order_by(tracks::index)
|
||||
.load::<TrackRow>(&self.connection)?;
|
||||
|
||||
let mut tracks = Vec::new();
|
||||
|
||||
for track_row in track_rows {
|
||||
let mut part_indices = Vec::new();
|
||||
|
||||
let work_parts = track_row
|
||||
.work_parts
|
||||
.split(',');
|
||||
|
||||
for part_index in work_parts {
|
||||
if !part_index.is_empty() {
|
||||
let index = str::parse(part_index)
|
||||
.or(Err(Error::Other(format!("Failed to parse part index from '{}'.", track_row.work_parts))))?;
|
||||
|
||||
part_indices.push(index);
|
||||
}
|
||||
}
|
||||
|
||||
let track = Track {
|
||||
work_parts: part_indices,
|
||||
path: track_row.path,
|
||||
};
|
||||
|
||||
let track = self.get_track_from_row(track_row)?;
|
||||
tracks.push(track);
|
||||
}
|
||||
|
||||
let track_set = TrackSet { recording, tracks };
|
||||
let medium = Medium {
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
discid: row.discid,
|
||||
tracks,
|
||||
};
|
||||
|
||||
Ok(track_set)
|
||||
Ok(medium)
|
||||
}
|
||||
|
||||
/// Convert a track row from the database to an actual track.
|
||||
fn get_track_from_row(&self, row: TrackRow) -> Result<Track> {
|
||||
let recording_id = row.recording;
|
||||
|
||||
let recording = self
|
||||
.get_recording(&recording_id)?
|
||||
.ok_or(Error::Other(format!(
|
||||
"Failed to get recording ({}) for track ({}).",
|
||||
recording_id,
|
||||
row.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)
|
||||
.or(Err(Error::Other(format!("Failed to parse part index from '{}'.", row.work_parts))))?;
|
||||
|
||||
part_indices.push(index);
|
||||
}
|
||||
}
|
||||
|
||||
let track = Track {
|
||||
recording,
|
||||
work_parts: part_indices,
|
||||
path: row.path,
|
||||
};
|
||||
|
||||
Ok(track)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,19 +55,11 @@ table! {
|
|||
}
|
||||
|
||||
table! {
|
||||
track_sets (id) {
|
||||
tracks (id) {
|
||||
id -> Text,
|
||||
medium -> Text,
|
||||
index -> Integer,
|
||||
recording -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
tracks (id) {
|
||||
id -> Text,
|
||||
track_set -> Text,
|
||||
index -> Integer,
|
||||
work_parts -> Text,
|
||||
path -> Text,
|
||||
}
|
||||
|
|
@ -106,9 +98,8 @@ joinable!(performances -> instruments (role));
|
|||
joinable!(performances -> persons (person));
|
||||
joinable!(performances -> recordings (recording));
|
||||
joinable!(recordings -> works (work));
|
||||
joinable!(track_sets -> mediums (medium));
|
||||
joinable!(track_sets -> recordings (recording));
|
||||
joinable!(tracks -> track_sets (track_set));
|
||||
joinable!(tracks -> mediums (medium));
|
||||
joinable!(tracks -> recordings (recording));
|
||||
joinable!(work_parts -> works (work));
|
||||
joinable!(work_sections -> works (work));
|
||||
joinable!(works -> persons (composer));
|
||||
|
|
@ -121,7 +112,6 @@ allow_tables_to_appear_in_same_query!(
|
|||
performances,
|
||||
persons,
|
||||
recordings,
|
||||
track_sets,
|
||||
tracks,
|
||||
work_parts,
|
||||
work_sections,
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ pub enum Action {
|
|||
GetMediumsForPerson(String, Sender<Result<Vec<Medium>>>),
|
||||
GetMediumsForEnsemble(String, Sender<Result<Vec<Medium>>>),
|
||||
DeleteMedium(String, Sender<Result<()>>),
|
||||
GetTrackSets(String, Sender<Result<Vec<TrackSet>>>),
|
||||
GetTracks(String, Sender<Result<Vec<Track>>>),
|
||||
Stop(Sender<()>),
|
||||
}
|
||||
|
||||
|
|
@ -141,8 +141,8 @@ impl DbThread {
|
|||
DeleteMedium(id, sender) => {
|
||||
sender.send(db.delete_medium(&id)).unwrap();
|
||||
}
|
||||
GetTrackSets(recording_id, sender) => {
|
||||
sender.send(db.get_track_sets(&recording_id)).unwrap();
|
||||
GetTracks(recording_id, sender) => {
|
||||
sender.send(db.get_tracks(&recording_id)).unwrap();
|
||||
}
|
||||
Stop(sender) => {
|
||||
sender.send(()).unwrap();
|
||||
|
|
@ -360,10 +360,10 @@ impl DbThread {
|
|||
receiver.await?
|
||||
}
|
||||
|
||||
/// Get all track sets for a recording.
|
||||
pub async fn get_track_sets(&self, recording_id: &str) -> Result<Vec<TrackSet>> {
|
||||
/// Get all tracks for a recording.
|
||||
pub async fn get_tracks(&self, recording_id: &str) -> Result<Vec<Track>> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
self.action_sender.send(GetTrackSets(recording_id.to_owned(), sender))?;
|
||||
self.action_sender.send(GetTracks(recording_id.to_owned(), sender))?;
|
||||
receiver.await?
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue