Use flat track list for mediums

This commit is contained in:
Elias Projahn 2021-04-08 00:08:31 +02:00
parent e293972c0d
commit 4f617cb79a
11 changed files with 327 additions and 532 deletions

View file

@ -1,5 +1,5 @@
use crate::{Error, Result}; use crate::{Error, Result};
use musicus_database::TrackSet; use musicus_database::Track;
use glib::clone; use glib::clone;
use gstreamer_player::prelude::*; use gstreamer_player::prelude::*;
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
@ -10,21 +10,14 @@ use std::sync::Arc;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use mpris_player::{Metadata, MprisPlayer, PlaybackStatus}; use mpris_player::{Metadata, MprisPlayer, PlaybackStatus};
#[derive(Clone)]
pub struct PlaylistItem {
pub track_set: TrackSet,
pub indices: Vec<usize>,
}
pub struct Player { pub struct Player {
music_library_path: PathBuf, music_library_path: PathBuf,
player: gstreamer_player::Player, player: gstreamer_player::Player,
playlist: RefCell<Vec<PlaylistItem>>, playlist: RefCell<Vec<Track>>,
current_item: Cell<Option<usize>>,
current_track: Cell<Option<usize>>, current_track: Cell<Option<usize>>,
playing: Cell<bool>, playing: Cell<bool>,
playlist_cbs: RefCell<Vec<Box<dyn Fn(Vec<PlaylistItem>)>>>, playlist_cbs: RefCell<Vec<Box<dyn Fn(Vec<Track>)>>>,
track_cbs: RefCell<Vec<Box<dyn Fn(usize, usize)>>>, track_cbs: RefCell<Vec<Box<dyn Fn(usize)>>>,
duration_cbs: RefCell<Vec<Box<dyn Fn(u64)>>>, duration_cbs: RefCell<Vec<Box<dyn Fn(u64)>>>,
playing_cbs: RefCell<Vec<Box<dyn Fn(bool)>>>, playing_cbs: RefCell<Vec<Box<dyn Fn(bool)>>>,
position_cbs: RefCell<Vec<Box<dyn Fn(u64)>>>, position_cbs: RefCell<Vec<Box<dyn Fn(u64)>>>,
@ -47,7 +40,6 @@ impl Player {
music_library_path, music_library_path,
player: player.clone(), player: player.clone(),
playlist: RefCell::new(Vec::new()), playlist: RefCell::new(Vec::new()),
current_item: Cell::new(None),
current_track: Cell::new(None), current_track: Cell::new(None),
playing: Cell::new(false), playing: Cell::new(false),
playlist_cbs: RefCell::new(Vec::new()), playlist_cbs: RefCell::new(Vec::new()),
@ -144,11 +136,11 @@ impl Player {
result result
} }
pub fn add_playlist_cb<F: Fn(Vec<PlaylistItem>) + 'static>(&self, cb: F) { pub fn add_playlist_cb<F: Fn(Vec<Track>) + 'static>(&self, cb: F) {
self.playlist_cbs.borrow_mut().push(Box::new(cb)); self.playlist_cbs.borrow_mut().push(Box::new(cb));
} }
pub fn add_track_cb<F: Fn(usize, usize) + 'static>(&self, cb: F) { pub fn add_track_cb<F: Fn(usize) + 'static>(&self, cb: F) {
self.track_cbs.borrow_mut().push(Box::new(cb)); self.track_cbs.borrow_mut().push(Box::new(cb));
} }
@ -168,14 +160,10 @@ impl Player {
self.raise_cb.replace(Some(Box::new(cb))); self.raise_cb.replace(Some(Box::new(cb)));
} }
pub fn get_playlist(&self) -> Vec<PlaylistItem> { pub fn get_playlist(&self) -> Vec<Track> {
self.playlist.borrow().clone() self.playlist.borrow().clone()
} }
pub fn get_current_item(&self) -> Option<usize> {
self.current_item.get()
}
pub fn get_current_track(&self) -> Option<usize> { pub fn get_current_track(&self) -> Option<usize> {
self.current_track.get() self.current_track.get()
} }
@ -188,10 +176,7 @@ impl Player {
self.playing.get() self.playing.get()
} }
pub fn add_item(&self, item: PlaylistItem) -> Result<()> { pub fn add_item(&self, item: Track) -> Result<()> {
if item.indices.is_empty() {
Err(Error::Other(String::from("Tried to add an empty playlist item!")))
} else {
let was_empty = { let was_empty = {
let mut playlist = self.playlist.borrow_mut(); let mut playlist = self.playlist.borrow_mut();
let was_empty = playlist.is_empty(); let was_empty = playlist.is_empty();
@ -206,7 +191,7 @@ impl Player {
} }
if was_empty { if was_empty {
self.set_track(0, 0)?; self.set_track(0)?;
self.player.play(); self.player.play();
self.playing.set(true); self.playing.set(true);
@ -223,7 +208,6 @@ impl Player {
Ok(()) Ok(())
} }
}
pub fn play_pause(&self) { pub fn play_pause(&self) {
if self.is_playing() { if self.is_playing() {
@ -254,79 +238,56 @@ impl Player {
} }
pub fn has_previous(&self) -> bool { pub fn has_previous(&self) -> bool {
if let Some(current_item) = self.current_item.get() {
if let Some(current_track) = self.current_track.get() { if let Some(current_track) = self.current_track.get() {
current_track > 0 || current_item > 0 current_track > 0
} else {
false
}
} else { } else {
false false
} }
} }
pub fn previous(&self) -> Result<()> { pub fn previous(&self) -> Result<()> {
let mut current_item = self.current_item.get()
.ok_or(Error::Other(String::from("Player tried to access non existant current item.")))?;
let mut current_track = self let mut current_track = self
.current_track .current_track
.get() .get()
.ok_or(Error::Other(String::from("Player tried to access non existant current track.")))?; .ok_or(Error::Other(String::from("Player tried to access non existant current track.")))?;
let playlist = self.playlist.borrow();
if current_track > 0 { if current_track > 0 {
current_track -= 1; current_track -= 1;
} else if current_item > 0 {
current_item -= 1;
current_track = playlist[current_item].indices.len() - 1;
} else { } else {
return Err(Error::Other(String::from("No existing previous track."))); return Err(Error::Other(String::from("No existing previous track.")));
} }
self.set_track(current_item, current_track) self.set_track(current_track)
} }
pub fn has_next(&self) -> bool { pub fn has_next(&self) -> bool {
if let Some(current_item) = self.current_item.get() {
if let Some(current_track) = self.current_track.get() { if let Some(current_track) = self.current_track.get() {
let playlist = self.playlist.borrow(); let playlist = self.playlist.borrow();
let item = &playlist[current_item]; current_track + 1 < playlist.len()
current_track + 1 < item.indices.len() || current_item + 1 < playlist.len()
} else {
false
}
} else { } else {
false false
} }
} }
pub fn next(&self) -> Result<()> { pub fn next(&self) -> Result<()> {
let mut current_item = self.current_item.get()
.ok_or(Error::Other(String::from("Player tried to access non existant current item.")))?;
let mut current_track = self let mut current_track = self
.current_track .current_track
.get() .get()
.ok_or(Error::Other(String::from("Player tried to access non existant current track.")))?; .ok_or(Error::Other(String::from("Player tried to access non existant current track.")))?;
let playlist = self.playlist.borrow(); let playlist = self.playlist.borrow();
let item = &playlist[current_item];
if current_track + 1 < item.indices.len() { if current_track + 1 < playlist.len() {
current_track += 1; current_track += 1;
} else if current_item + 1 < playlist.len() {
current_item += 1;
current_track = 0;
} else { } else {
return Err(Error::Other(String::from("No existing previous track."))); return Err(Error::Other(String::from("No existing next track.")));
} }
self.set_track(current_item, current_track) self.set_track(current_track)
} }
pub fn set_track(&self, current_item: usize, current_track: usize) -> Result<()> { pub fn set_track(&self, current_track: usize) -> Result<()> {
let item = &self.playlist.borrow()[current_item]; let track = &self.playlist.borrow()[current_track];
let track = &item.track_set.tracks[current_track];
let path = self.music_library_path.join(track.path.clone()) let path = self.music_library_path.join(track.path.clone())
.into_os_string().into_string().unwrap(); .into_os_string().into_string().unwrap();
@ -340,26 +301,25 @@ impl Player {
self.player.play(); self.player.play();
} }
self.current_item.set(Some(current_item));
self.current_track.set(Some(current_track)); self.current_track.set(Some(current_track));
for cb in &*self.track_cbs.borrow() { for cb in &*self.track_cbs.borrow() {
cb(current_item, current_track); cb(current_track);
} }
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
let mut parts = Vec::<String>::new(); let mut parts = Vec::<String>::new();
for part in &track.work_parts { for part in &track.work_parts {
parts.push(item.track_set.recording.work.parts[*part].title.clone()); parts.push(track.recording.work.parts[*part].title.clone());
} }
let mut title = item.track_set.recording.work.get_title(); let mut title = track.recording.work.get_title();
if !parts.is_empty() { if !parts.is_empty() {
title = format!("{}: {}", title, parts.join(", ")); title = format!("{}: {}", title, parts.join(", "));
} }
let subtitle = item.track_set.recording.get_performers(); let subtitle = track.recording.get_performers();
let mut metadata = Metadata::new(); let mut metadata = Metadata::new();
metadata.artist = Some(vec![title]); metadata.artist = Some(vec![title]);
@ -379,7 +339,7 @@ impl Player {
} }
for cb in &*self.track_cbs.borrow() { for cb in &*self.track_cbs.borrow() {
cb(self.current_item.get().unwrap(), self.current_track.get().unwrap()); cb(self.current_track.get().unwrap());
} }
for cb in &*self.duration_cbs.borrow() { for cb in &*self.duration_cbs.borrow() {
@ -394,7 +354,6 @@ impl Player {
pub fn clear(&self) { pub fn clear(&self) {
self.player.stop(); self.player.stop();
self.playing.set(false); self.playing.set(false);
self.current_item.set(None);
self.current_track.set(None); self.current_track.set(None);
self.playlist.replace(Vec::new()); self.playlist.replace(Vec::new());

View file

@ -10,6 +10,5 @@ DROP TABLE "ensembles";
DROP TABLE "recordings"; DROP TABLE "recordings";
DROP TABLE "performances"; DROP TABLE "performances";
DROP TABLE "mediums"; DROP TABLE "mediums";
DROP TABLE "track_sets";
DROP TABLE "tracks"; DROP TABLE "tracks";

View file

@ -60,17 +60,11 @@ CREATE TABLE "mediums" (
"discid" TEXT "discid" TEXT
); );
CREATE TABLE "track_sets" ( CREATE TABLE "tracks" (
"id" TEXT NOT NULL PRIMARY KEY, "id" TEXT NOT NULL PRIMARY KEY,
"medium" TEXT NOT NULL REFERENCES "mediums"("id") ON DELETE CASCADE, "medium" TEXT NOT NULL REFERENCES "mediums"("id") ON DELETE CASCADE,
"index" INTEGER NOT NULL, "index" INTEGER NOT NULL,
"recording" TEXT NOT NULL REFERENCES "recordings"("id") "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,
"work_parts" TEXT NOT NULL, "work_parts" TEXT NOT NULL,
"path" TEXT NOT NULL "path" TEXT NOT NULL
); );

View file

@ -1,5 +1,5 @@
use super::generate_id; 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 super::{Database, Error, Recording, Result};
use diesel::prelude::*; use diesel::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -18,25 +18,17 @@ pub struct Medium {
/// If applicable, the MusicBrainz DiscID. /// If applicable, the MusicBrainz DiscID.
pub discid: Option<String>, pub discid: Option<String>,
/// The tracks of the medium, grouped by recording. /// The tracks of the medium.
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.
pub tracks: Vec<Track>, pub tracks: Vec<Track>,
} }
/// A track within a recording on a medium. /// A track on a medium.
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Track { 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 /// 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. /// work parts of the work that is associated with the recording.
pub work_parts: Vec<usize>, pub work_parts: Vec<usize>,
@ -56,23 +48,14 @@ struct MediumRow {
pub discid: Option<String>, 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`]. /// Table data for a [`Track`].
#[derive(Insertable, Queryable, Debug, Clone)] #[derive(Insertable, Queryable, Debug, Clone)]
#[table_name = "tracks"] #[table_name = "tracks"]
struct TrackRow { struct TrackRow {
pub id: String, pub id: String,
pub track_set: String, pub medium: String,
pub index: i32, pub index: i32,
pub recording: String,
pub work_parts: String, pub work_parts: String,
pub path: String, pub path: String,
} }
@ -85,7 +68,7 @@ impl Database {
self.connection.transaction::<(), Error, _>(|| { self.connection.transaction::<(), Error, _>(|| {
let medium_id = &medium.id; 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)?; self.delete_medium(medium_id)?;
// Add the new medium. // Add the new medium.
@ -100,30 +83,15 @@ impl Database {
.values(medium_row) .values(medium_row)
.execute(&self.connection)?; .execute(&self.connection)?;
for (index, track_set) in medium.tracks.iter().enumerate() { for (index, track) in medium.tracks.iter().enumerate() {
// Add associated items from the server, if they don't already // Add associated items from the server, if they don't already exist.
// exist.
if self.get_recording(&track_set.recording.id)?.is_none() { if self.get_recording(&track.recording.id)?.is_none() {
self.update_recording(track_set.recording.clone())?; self.update_recording(track.recording.clone())?;
} }
// Add the actual track set data. // Add the actual track data.
let track_set_id = generate_id();
let track_set_row = TrackSetRow {
id: track_set_id.clone(),
medium: medium_id.to_owned(),
index: index as i32,
recording: track_set.recording.id.clone(),
};
diesel::insert_into(track_sets::table)
.values(track_set_row)
.execute(&self.connection)?;
for (index, track) in track_set.tracks.iter().enumerate() {
let work_parts = track let work_parts = track
.work_parts .work_parts
.iter() .iter()
@ -133,8 +101,9 @@ impl Database {
let track_row = TrackRow { let track_row = TrackRow {
id: generate_id(), id: generate_id(),
track_set: track_set_id.clone(), medium: medium_id.to_owned(),
index: index as i32, index: index as i32,
recording: track.recording.id.clone(),
work_parts, work_parts,
path: track.path.clone(), path: track.path.clone(),
}; };
@ -143,7 +112,6 @@ impl Database {
.values(track_row) .values(track_row)
.execute(&self.connection)?; .execute(&self.connection)?;
} }
}
Ok(()) Ok(())
})?; })?;
@ -172,8 +140,8 @@ impl Database {
let mut mediums: Vec<Medium> = Vec::new(); let mut mediums: Vec<Medium> = Vec::new();
let rows = mediums::table let rows = mediums::table
.inner_join(track_sets::table.on(track_sets::medium.eq(mediums::id))) .inner_join(tracks::table.on(tracks::medium.eq(mediums::id)))
.inner_join(recordings::table.on(recordings::id.eq(track_sets::recording))) .inner_join(recordings::table.on(recordings::id.eq(tracks::recording)))
.inner_join(performances::table.on(performances::recording.eq(recordings::id))) .inner_join(performances::table.on(performances::recording.eq(recordings::id)))
.inner_join(persons::table.on(persons::id.nullable().eq(performances::person))) .inner_join(persons::table.on(persons::id.nullable().eq(performances::person)))
.filter(persons::id.eq(person_id)) .filter(persons::id.eq(person_id))
@ -194,8 +162,8 @@ impl Database {
let mut mediums: Vec<Medium> = Vec::new(); let mut mediums: Vec<Medium> = Vec::new();
let rows = mediums::table let rows = mediums::table
.inner_join(track_sets::table.on(track_sets::medium.eq(mediums::id))) .inner_join(tracks::table.on(tracks::medium.eq(tracks::id)))
.inner_join(recordings::table.on(recordings::id.eq(track_sets::recording))) .inner_join(recordings::table.on(recordings::id.eq(tracks::recording)))
.inner_join(performances::table.on(performances::recording.eq(recordings::id))) .inner_join(performances::table.on(performances::recording.eq(recordings::id)))
.inner_join(ensembles::table.on(ensembles::id.nullable().eq(performances::ensemble))) .inner_join(ensembles::table.on(ensembles::id.nullable().eq(performances::ensemble)))
.filter(ensembles::id.eq(ensemble_id)) .filter(ensembles::id.eq(ensemble_id))
@ -218,93 +186,81 @@ impl Database {
Ok(()) Ok(())
} }
/// Get all available track sets for a recording. /// Get all available tracks for a recording.
pub fn get_track_sets(&self, recording_id: &str) -> Result<Vec<TrackSet>> { pub fn get_tracks(&self, recording_id: &str) -> Result<Vec<Track>> {
let mut track_sets: Vec<TrackSet> = Vec::new(); let mut tracks: Vec<Track> = Vec::new();
let rows = track_sets::table let rows = tracks::table
.inner_join(recordings::table.on(recordings::id.eq(track_sets::recording))) .inner_join(recordings::table.on(recordings::id.eq(tracks::recording)))
.filter(recordings::id.eq(recording_id)) .filter(recordings::id.eq(recording_id))
.select(track_sets::table::all_columns()) .select(tracks::table::all_columns())
.load::<TrackSetRow>(&self.connection)?; .load::<TrackRow>(&self.connection)?;
for row in rows { for row in rows {
let track_set = self.get_track_set_from_row(row)?; let track = self.get_track_from_row(row)?;
track_sets.push(track_set); tracks.push(track);
} }
Ok(track_sets) Ok(tracks)
} }
/// Retrieve all available information on a medium from related tables. /// 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) -> 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 let track_rows = tracks::table
.filter(tracks::track_set.eq(row.id)) .filter(tracks::medium.eq(&row.id))
.order_by(tracks::index) .order_by(tracks::index)
.load::<TrackRow>(&self.connection)?; .load::<TrackRow>(&self.connection)?;
let mut tracks = Vec::new(); let mut tracks = Vec::new();
for track_row in track_rows { 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,
};
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 mut part_indices = Vec::new();
let work_parts = track_row let work_parts = row
.work_parts .work_parts
.split(','); .split(',');
for part_index in work_parts { for part_index in work_parts {
if !part_index.is_empty() { if !part_index.is_empty() {
let index = str::parse(part_index) let index = str::parse(part_index)
.or(Err(Error::Other(format!("Failed to parse part index from '{}'.", track_row.work_parts))))?; .or(Err(Error::Other(format!("Failed to parse part index from '{}'.", row.work_parts))))?;
part_indices.push(index); part_indices.push(index);
} }
} }
let track = Track { let track = Track {
recording,
work_parts: part_indices, work_parts: part_indices,
path: track_row.path, path: row.path,
}; };
tracks.push(track); Ok(track)
}
let track_set = TrackSet { recording, tracks };
Ok(track_set)
} }
} }

View file

@ -55,19 +55,11 @@ table! {
} }
table! { table! {
track_sets (id) { tracks (id) {
id -> Text, id -> Text,
medium -> Text, medium -> Text,
index -> Integer, index -> Integer,
recording -> Text, recording -> Text,
}
}
table! {
tracks (id) {
id -> Text,
track_set -> Text,
index -> Integer,
work_parts -> Text, work_parts -> Text,
path -> Text, path -> Text,
} }
@ -106,9 +98,8 @@ joinable!(performances -> instruments (role));
joinable!(performances -> persons (person)); joinable!(performances -> persons (person));
joinable!(performances -> recordings (recording)); joinable!(performances -> recordings (recording));
joinable!(recordings -> works (work)); joinable!(recordings -> works (work));
joinable!(track_sets -> mediums (medium)); joinable!(tracks -> mediums (medium));
joinable!(track_sets -> recordings (recording)); joinable!(tracks -> recordings (recording));
joinable!(tracks -> track_sets (track_set));
joinable!(work_parts -> works (work)); joinable!(work_parts -> works (work));
joinable!(work_sections -> works (work)); joinable!(work_sections -> works (work));
joinable!(works -> persons (composer)); joinable!(works -> persons (composer));
@ -121,7 +112,6 @@ allow_tables_to_appear_in_same_query!(
performances, performances,
persons, persons,
recordings, recordings,
track_sets,
tracks, tracks,
work_parts, work_parts,
work_sections, work_sections,

View file

@ -32,7 +32,7 @@ pub enum Action {
GetMediumsForPerson(String, Sender<Result<Vec<Medium>>>), GetMediumsForPerson(String, Sender<Result<Vec<Medium>>>),
GetMediumsForEnsemble(String, Sender<Result<Vec<Medium>>>), GetMediumsForEnsemble(String, Sender<Result<Vec<Medium>>>),
DeleteMedium(String, Sender<Result<()>>), DeleteMedium(String, Sender<Result<()>>),
GetTrackSets(String, Sender<Result<Vec<TrackSet>>>), GetTracks(String, Sender<Result<Vec<Track>>>),
Stop(Sender<()>), Stop(Sender<()>),
} }
@ -141,8 +141,8 @@ impl DbThread {
DeleteMedium(id, sender) => { DeleteMedium(id, sender) => {
sender.send(db.delete_medium(&id)).unwrap(); sender.send(db.delete_medium(&id)).unwrap();
} }
GetTrackSets(recording_id, sender) => { GetTracks(recording_id, sender) => {
sender.send(db.get_track_sets(&recording_id)).unwrap(); sender.send(db.get_tracks(&recording_id)).unwrap();
} }
Stop(sender) => { Stop(sender) => {
sender.send(()).unwrap(); sender.send(()).unwrap();
@ -360,10 +360,10 @@ impl DbThread {
receiver.await? receiver.await?
} }
/// Get all track sets for a recording. /// Get all tracks for a recording.
pub async fn get_track_sets(&self, recording_id: &str) -> Result<Vec<TrackSet>> { pub async fn get_tracks(&self, recording_id: &str) -> Result<Vec<Track>> {
let (sender, receiver) = oneshot::channel(); 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? receiver.await?
} }

View file

@ -7,7 +7,7 @@ use glib::prelude::*;
use gtk::prelude::*; use gtk::prelude::*;
use gtk_macros::get_widget; use gtk_macros::get_widget;
use libadwaita::prelude::*; use libadwaita::prelude::*;
use musicus_backend::db::{generate_id, Medium, Track, TrackSet}; use musicus_backend::db::{generate_id, Medium};
use musicus_backend::import::ImportSession; use musicus_backend::import::ImportSession;
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
@ -26,7 +26,7 @@ pub struct MediumEditor {
status_page: libadwaita::StatusPage, status_page: libadwaita::StatusPage,
disc_status_page: libadwaita::StatusPage, disc_status_page: libadwaita::StatusPage,
track_set_list: Rc<List>, track_set_list: Rc<List>,
track_sets: RefCell<Vec<TrackSetData>>, // track_sets: RefCell<Vec<TrackSetData>>,
} }
impl Screen<Arc<ImportSession>, ()> for MediumEditor { impl Screen<Arc<ImportSession>, ()> for MediumEditor {
@ -65,7 +65,7 @@ impl Screen<Arc<ImportSession>, ()> for MediumEditor {
status_page, status_page,
disc_status_page, disc_status_page,
track_set_list: list, track_set_list: list,
track_sets: RefCell::new(Vec::new()), // track_sets: RefCell::new(Vec::new()),
}); });
// Connect signals and callbacks // Connect signals and callbacks
@ -89,43 +89,43 @@ impl Screen<Arc<ImportSession>, ()> for MediumEditor {
add_button.connect_clicked(clone!(@weak this => move |_| { add_button.connect_clicked(clone!(@weak this => move |_| {
spawn!(@clone this, async move { spawn!(@clone this, async move {
if let Some(track_set) = push!(this.handle, TrackSetEditor, Arc::clone(&this.session)).await { // if let Some(track_set) = push!(this.handle, TrackSetEditor, Arc::clone(&this.session)).await {
let length = { // let length = {
let mut track_sets = this.track_sets.borrow_mut(); // let mut track_sets = this.track_sets.borrow_mut();
track_sets.push(track_set); // track_sets.push(track_set);
track_sets.len() // track_sets.len()
}; // };
this.track_set_list.update(length); // this.track_set_list.update(length);
} // }
}); });
})); }));
this.track_set_list.set_make_widget_cb(clone!(@weak this => move |index| { // this.track_set_list.set_make_widget_cb(clone!(@weak this => move |index| {
let track_set = &this.track_sets.borrow()[index]; // let track_set = &this.track_sets.borrow()[index];
let title = track_set.recording.work.get_title(); // let title = track_set.recording.work.get_title();
let subtitle = track_set.recording.get_performers(); // let subtitle = track_set.recording.get_performers();
let edit_image = gtk::Image::from_icon_name(Some("document-edit-symbolic")); // let edit_image = gtk::Image::from_icon_name(Some("document-edit-symbolic"));
let edit_button = gtk::Button::new(); // let edit_button = gtk::Button::new();
edit_button.set_has_frame(false); // edit_button.set_has_frame(false);
edit_button.set_valign(gtk::Align::Center); // edit_button.set_valign(gtk::Align::Center);
edit_button.set_child(Some(&edit_image)); // edit_button.set_child(Some(&edit_image));
let row = libadwaita::ActionRow::new(); // let row = libadwaita::ActionRow::new();
row.set_activatable(true); // row.set_activatable(true);
row.set_title(Some(&title)); // row.set_title(Some(&title));
row.set_subtitle(Some(&subtitle)); // row.set_subtitle(Some(&subtitle));
row.add_suffix(&edit_button); // row.add_suffix(&edit_button);
row.set_activatable_widget(Some(&edit_button)); // row.set_activatable_widget(Some(&edit_button));
edit_button.connect_clicked(clone!(@weak this => move |_| { // edit_button.connect_clicked(clone!(@weak this => move |_| {
// TODO: Implement editing. // TODO: Implement editing.
})); // }));
row.upcast() // row.upcast()
})); // }));
try_again_button.connect_clicked(clone!(@weak this => move |_| { try_again_button.connect_clicked(clone!(@weak this => move |_| {
this.widget.set_visible_child_name("content"); this.widget.set_visible_child_name("content");
@ -155,68 +155,68 @@ impl Screen<Arc<ImportSession>, ()> for MediumEditor {
impl MediumEditor { impl MediumEditor {
/// Save the medium and possibly upload it to the server. /// Save the medium and possibly upload it to the server.
async fn save(&self) -> Result<()> { async fn save(&self) -> Result<()> {
let name = self.name_entry.get_text().to_string(); // let name = self.name_entry.get_text().to_string();
// Create a new directory in the music library path for the imported medium. // Create a new directory in the music library path for the imported medium.
let mut path = self.handle.backend.get_music_library_path().unwrap().clone(); // let mut path = self.handle.backend.get_music_library_path().unwrap().clone();
path.push(&name); // path.push(&name);
std::fs::create_dir(&path)?; // std::fs::create_dir(&path)?;
// Convert the track set data to real track sets. // Convert the track set data to real track sets.
let mut track_sets = Vec::new(); // let mut track_sets = Vec::new();
let import_tracks = self.session.tracks(); // let import_tracks = self.session.tracks();
for track_set_data in &*self.track_sets.borrow() { // for track_set_data in &*self.track_sets.borrow() {
let mut tracks = Vec::new(); // let mut tracks = Vec::new();
for track_data in &track_set_data.tracks { // for track_data in &track_set_data.tracks {
// Copy the corresponding audio file to the music library. // Copy the corresponding audio file to the music library.
let import_track = &import_tracks[track_data.track_source]; // let import_track = &import_tracks[track_data.track_source];
let mut track_path = path.clone(); // let mut track_path = path.clone();
track_path.push(import_track.path.file_name().unwrap()); // track_path.push(import_track.path.file_name().unwrap());
std::fs::copy(&import_track.path, &track_path)?; // std::fs::copy(&import_track.path, &track_path)?;
// Create the real track. // Create the real track.
let track = Track { // let track = Track {
work_parts: track_data.work_parts.clone(), // work_parts: track_data.work_parts.clone(),
path: track_path.to_str().unwrap().to_owned(), // path: track_path.to_str().unwrap().to_owned(),
}; // };
tracks.push(track); // tracks.push(track);
} // }
let track_set = TrackSet { // let track_set = TrackSet {
recording: track_set_data.recording.clone(), // recording: track_set_data.recording.clone(),
tracks, // tracks,
}; // };
track_sets.push(track_set); // track_sets.push(track_set);
} // }
let medium = Medium { // let medium = Medium {
id: generate_id(), // id: generate_id(),
name: self.name_entry.get_text().to_string(), // name: self.name_entry.get_text().to_string(),
discid: Some(self.session.source_id().to_owned()), // discid: Some(self.session.source_id().to_owned()),
tracks: track_sets, // tracks: track_sets,
}; // };
let upload = self.publish_switch.get_active(); // let upload = self.publish_switch.get_active();
if upload { // if upload {
self.handle.backend.cl().post_medium(&medium).await?; // self.handle.backend.cl().post_medium(&medium).await?;
} // }
self.handle.backend // self.handle.backend
.db() // .db()
.update_medium(medium.clone()) // .update_medium(medium.clone())
.await?; // .await?;
self.handle.backend.library_changed(); // self.handle.backend.library_changed();
Ok(()) Ok(())
} }

View file

@ -5,53 +5,21 @@ use gettextrs::gettext;
use glib::clone; use glib::clone;
use gtk::prelude::*; use gtk::prelude::*;
use libadwaita::prelude::*; use libadwaita::prelude::*;
use musicus_backend::PlaylistItem;
use musicus_backend::db::Medium; use musicus_backend::db::Medium;
use std::rc::Rc; use std::rc::Rc;
/// Elements for visually representing the contents of the medium.
enum ListItem {
/// A header shown on top of a track set. The value is the index of the corresponding track set
/// within the medium.
Header(usize),
/// A track. The indices are from the track set and the track.
Track(usize, usize),
/// A separator shown between track sets.
Separator,
}
/// A screen for showing the contents of a medium. /// A screen for showing the contents of a medium.
pub struct MediumScreen { pub struct MediumScreen {
handle: NavigationHandle<()>, handle: NavigationHandle<()>,
medium: Medium, medium: Medium,
widget: widgets::Screen, widget: widgets::Screen,
list: Rc<List>, list: Rc<List>,
items: Vec<ListItem>,
} }
impl Screen<Medium, ()> for MediumScreen { impl Screen<Medium, ()> for MediumScreen {
/// Create a new medium screen for the specified medium and load the /// Create a new medium screen for the specified medium and load the
/// contents asynchronously. /// contents asynchronously.
fn new(medium: Medium, handle: NavigationHandle<()>) -> Rc<Self> { fn new(medium: Medium, handle: NavigationHandle<()>) -> Rc<Self> {
let mut items = Vec::new();
let mut first = true;
for (track_set_index, track_set) in medium.tracks.iter().enumerate() {
if !first {
items.push(ListItem::Separator);
} else {
first = false;
}
items.push(ListItem::Header(track_set_index));
for (track_index, _) in track_set.tracks.iter().enumerate() {
items.push(ListItem::Track(track_set_index, track_index));
}
}
let widget = widgets::Screen::new(); let widget = widgets::Screen::new();
widget.set_title(&medium.name); widget.set_title(&medium.name);
@ -65,7 +33,6 @@ impl Screen<Medium, ()> for MediumScreen {
medium, medium,
widget, widget,
list, list,
items,
}); });
this.widget.set_back_cb(clone!(@weak this => move || { this.widget.set_back_cb(clone!(@weak this => move || {
@ -82,39 +49,17 @@ impl Screen<Medium, ()> for MediumScreen {
})); }));
section.add_action("media-playback-start-symbolic", clone!(@weak this => move || { section.add_action("media-playback-start-symbolic", clone!(@weak this => move || {
for track_set in &this.medium.tracks { for track in &this.medium.tracks {
let indices = (0..track_set.tracks.len()).collect(); this.handle.backend.pl().add_item(track.clone()).unwrap();
let playlist_item = PlaylistItem {
track_set: track_set.clone(),
indices,
};
this.handle.backend.pl().add_item(playlist_item).unwrap();
} }
})); }));
this.list.set_make_widget_cb(clone!(@weak this => move |index| { this.list.set_make_widget_cb(clone!(@weak this => move |index| {
match this.items[index] { let track = &this.medium.tracks[index];
ListItem::Header(index) => {
let track_set = &this.medium.tracks[index];
let recording = &track_set.recording;
let row = libadwaita::ActionRow::new();
row.set_activatable(false);
row.set_selectable(false);
row.set_title(Some(&recording.work.get_title()));
row.set_subtitle(Some(&recording.get_performers()));
row.upcast()
}
ListItem::Track(track_set_index, track_index) => {
let track_set = &this.medium.tracks[track_set_index];
let track = &track_set.tracks[track_index];
let mut parts = Vec::<String>::new(); let mut parts = Vec::<String>::new();
for part in &track.work_parts { for part in &track.work_parts {
parts.push(track_set.recording.work.parts[*part].title.clone()); parts.push(track.recording.work.parts[*part].title.clone());
} }
let title = if parts.is_empty() { let title = if parts.is_empty() {
@ -130,15 +75,9 @@ impl Screen<Medium, ()> for MediumScreen {
row.set_margin_start(12); row.set_margin_start(12);
row.upcast() row.upcast()
}
ListItem::Separator => {
let separator = gtk::Separator::new(gtk::Orientation::Horizontal);
separator.upcast()
}
}
})); }));
this.list.update(this.items.len()); this.list.update(this.medium.tracks.len());
this this
} }

View file

@ -5,21 +5,25 @@ use glib::clone;
use gtk::prelude::*; use gtk::prelude::*;
use gtk_macros::get_widget; use gtk_macros::get_widget;
use libadwaita::prelude::*; use libadwaita::prelude::*;
use musicus_backend::PlaylistItem; use musicus_backend::db::Track;
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::rc::Rc; use std::rc::Rc;
/// Elements for visually representing the playlist. /// Elements for visually representing the playlist.
enum ListItem { enum ListItem {
/// A header shown on top of a track set. This contains an index /// A playable track.
/// referencing the playlist item containing this track set. Track {
Header(usize), /// Index within the playlist.
index: usize,
/// A playable track. This contains an index to the playlist item, an /// Whether this is the first track of the recording.
/// index to the track and whether it is the currently played one. first: bool,
Track(usize, usize, bool),
/// A separator shown between track sets. /// Whether this is the currently played track.
playing: bool,
},
/// A separator shown between recordings.
Separator, Separator,
} }
@ -37,10 +41,9 @@ pub struct PlayerScreen {
play_image: gtk::Image, play_image: gtk::Image,
pause_image: gtk::Image, pause_image: gtk::Image,
list: Rc<List>, list: Rc<List>,
playlist: RefCell<Vec<PlaylistItem>>, playlist: RefCell<Vec<Track>>,
items: RefCell<Vec<ListItem>>, items: RefCell<Vec<ListItem>>,
seeking: Cell<bool>, seeking: Cell<bool>,
current_item: Cell<usize>,
current_track: Cell<usize>, current_track: Cell<usize>,
} }
@ -87,7 +90,6 @@ impl Screen<(), ()> for PlayerScreen {
items: RefCell::new(Vec::new()), items: RefCell::new(Vec::new()),
playlist: RefCell::new(Vec::new()), playlist: RefCell::new(Vec::new()),
seeking: Cell::new(false), seeking: Cell::new(false),
current_item: Cell::new(0),
current_track: Cell::new(0), current_track: Cell::new(0),
}); });
@ -102,28 +104,26 @@ impl Screen<(), ()> for PlayerScreen {
this.show_playlist(); this.show_playlist();
})); }));
player.add_track_cb(clone!(@weak this, @weak player => @default-return (), move |current_item, current_track| { player.add_track_cb(clone!(@weak this, @weak player => @default-return (), move |current_track| {
this.previous_button.set_sensitive(this.handle.backend.pl().has_previous()); this.previous_button.set_sensitive(this.handle.backend.pl().has_previous());
this.next_button.set_sensitive(this.handle.backend.pl().has_next()); this.next_button.set_sensitive(this.handle.backend.pl().has_next());
let item = &this.playlist.borrow()[current_item]; let track = &this.playlist.borrow()[current_track];
let track = &item.track_set.tracks[current_track];
let mut parts = Vec::<String>::new(); let mut parts = Vec::<String>::new();
for part in &track.work_parts { for part in &track.work_parts {
parts.push(item.track_set.recording.work.parts[*part].title.clone()); parts.push(track.recording.work.parts[*part].title.clone());
} }
let mut title = item.track_set.recording.work.get_title(); let mut title = track.recording.work.get_title();
if !parts.is_empty() { if !parts.is_empty() {
title = format!("{}: {}", title, parts.join(", ")); title = format!("{}: {}", title, parts.join(", "));
} }
this.title_label.set_text(&title); this.title_label.set_text(&title);
this.subtitle_label.set_text(&item.track_set.recording.get_performers()); this.subtitle_label.set_text(&track.recording.get_performers());
this.position_label.set_text("0:00"); this.position_label.set_text("0:00");
this.current_item.set(current_item);
this.current_track.set(current_track); this.current_track.set(current_track);
this.show_playlist(); this.show_playlist();
@ -205,29 +205,17 @@ impl Screen<(), ()> for PlayerScreen {
this.list.set_make_widget_cb(clone!(@weak this => move |index| { this.list.set_make_widget_cb(clone!(@weak this => move |index| {
let widget = match this.items.borrow()[index] { let widget = match this.items.borrow()[index] {
ListItem::Header(item_index) => { ListItem::Track {index, first, playing} => {
let playlist_item = &this.playlist.borrow()[item_index]; let track = &this.playlist.borrow()[index];
let recording = &playlist_item.track_set.recording;
let row = libadwaita::ActionRow::new();
row.set_activatable(false);
row.set_selectable(false);
row.set_title(Some(&recording.work.get_title()));
row.set_subtitle(Some(&recording.get_performers()));
row.upcast()
}
ListItem::Track(item_index, track_index, playing) => {
let playlist_item = &this.playlist.borrow()[item_index];
let index = playlist_item.indices[track_index];
let track = &playlist_item.track_set.tracks[index];
let mut parts = Vec::<String>::new(); let mut parts = Vec::<String>::new();
for part in &track.work_parts { for part in &track.work_parts {
parts.push(playlist_item.track_set.recording.work.parts[*part].title.clone()); parts.push(track.recording.work.parts[*part].title.clone());
} }
let title = if parts.is_empty() { let title = if first {
track.recording.work.get_title()
} else if parts.is_empty() {
gettext("Unknown") gettext("Unknown")
} else { } else {
parts.join(", ") parts.join(", ")
@ -238,8 +226,18 @@ impl Screen<(), ()> for PlayerScreen {
row.set_activatable(true); row.set_activatable(true);
row.set_title(Some(&title)); row.set_title(Some(&title));
if first {
let subtitle = if !parts.is_empty() {
format!("{}\n{}", track.recording.get_performers(), parts.join(", "))
} else {
track.recording.get_performers()
};
row.set_subtitle(Some(&subtitle));
}
row.connect_activated(clone!(@weak this => move |_| { row.connect_activated(clone!(@weak this => move |_| {
this.handle.backend.pl().set_track(item_index, track_index).unwrap(); this.handle.backend.pl().set_track(index).unwrap();
})); }));
let icon = if playing { let icon = if playing {
@ -272,25 +270,35 @@ impl PlayerScreen {
/// Update the user interface according to the playlist. /// Update the user interface according to the playlist.
fn show_playlist(&self) { fn show_playlist(&self) {
let playlist = self.playlist.borrow(); let playlist = self.playlist.borrow();
let current_item = self.current_item.get();
let current_track = self.current_track.get(); let current_track = self.current_track.get();
let mut first = true; let mut first = true;
let mut items = Vec::new(); let mut items = Vec::new();
for (item_index, playlist_item) in playlist.iter().enumerate() { let mut last_recording_id = "";
for (index, track) in playlist.iter().enumerate() {
let first_track = if track.recording.id != last_recording_id {
last_recording_id = &track.recording.id;
if !first { if !first {
items.push(ListItem::Separator); items.push(ListItem::Separator);
} else { } else {
first = false; first = false;
} }
items.push(ListItem::Header(item_index)); true
} else {
false
};
for (index, _) in playlist_item.indices.iter().enumerate() { let item = ListItem::Track {
let playing = current_item == item_index && current_track == index; index,
items.push(ListItem::Track(item_index, index, playing)); first: first_track,
} playing: index == current_track,
};
items.push(item);
} }
let length = items.len(); let length = items.len();

View file

@ -6,29 +6,17 @@ use gettextrs::gettext;
use glib::clone; use glib::clone;
use gtk::prelude::*; use gtk::prelude::*;
use libadwaita::prelude::*; use libadwaita::prelude::*;
use musicus_backend::PlaylistItem; use musicus_backend::db::{Recording, Track};
use musicus_backend::db::{Recording, TrackSet};
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
/// Representation of one entry within the track list.
enum ListItem {
/// A track row. This hold an index to the track set and an index to the
/// track within the track set.
Track(usize, usize),
/// A separator intended for use between track sets.
Separator,
}
/// A screen for showing a recording. /// A screen for showing a recording.
pub struct RecordingScreen { pub struct RecordingScreen {
handle: NavigationHandle<()>, handle: NavigationHandle<()>,
recording: Recording, recording: Recording,
widget: widgets::Screen, widget: widgets::Screen,
list: Rc<List>, list: Rc<List>,
track_sets: RefCell<Vec<TrackSet>>, tracks: RefCell<Vec<Track>>,
items: RefCell<Vec<ListItem>>,
} }
impl Screen<Recording, ()> for RecordingScreen { impl Screen<Recording, ()> for RecordingScreen {
@ -48,22 +36,12 @@ impl Screen<Recording, ()> for RecordingScreen {
recording, recording,
widget, widget,
list, list,
track_sets: RefCell::new(Vec::new()), tracks: RefCell::new(Vec::new()),
items: RefCell::new(Vec::new()),
}); });
section.add_action("media-playback-start-symbolic", clone!(@weak this => move || { section.add_action("media-playback-start-symbolic", clone!(@weak this => move || {
if let Some(player) = this.handle.backend.get_player() { for track in &*this.tracks.borrow() {
if let Some(track_set) = this.track_sets.borrow().get(0).cloned() { this.handle.backend.pl().add_item(track.clone()).unwrap();
let indices = (0..track_set.tracks.len()).collect();
let playlist_item = PlaylistItem {
track_set,
indices,
};
player.add_item(playlist_item).unwrap();
}
} }
})); }));
@ -86,10 +64,7 @@ impl Screen<Recording, ()> for RecordingScreen {
})); }));
this.list.set_make_widget_cb(clone!(@weak this => move |index| { this.list.set_make_widget_cb(clone!(@weak this => move |index| {
let widget = match this.items.borrow()[index] { let track = &this.tracks.borrow()[index];
ListItem::Track(track_set_index, track_index) => {
let track_set = &this.track_sets.borrow()[track_set_index];
let track = &track_set.tracks[track_index];
let mut title_parts = Vec::<String>::new(); let mut title_parts = Vec::<String>::new();
for part in &track.work_parts { for part in &track.work_parts {
@ -106,27 +81,19 @@ impl Screen<Recording, ()> for RecordingScreen {
row.set_title(Some(&title)); row.set_title(Some(&title));
row.upcast() row.upcast()
}
ListItem::Separator => {
let separator = gtk::Separator::new(gtk::Orientation::Horizontal);
separator.upcast()
}
};
widget
})); }));
// Load the content asynchronously. // Load the content asynchronously.
spawn!(@clone this, async move { spawn!(@clone this, async move {
let track_sets = this.handle let tracks = this.handle
.backend .backend
.db() .db()
.get_track_sets(&this.recording.id) .get_tracks(&this.recording.id)
.await .await
.unwrap(); .unwrap();
this.show_track_sets(track_sets); this.show_tracks(tracks);
this.widget.ready(); this.widget.ready();
}); });
@ -135,26 +102,10 @@ impl Screen<Recording, ()> for RecordingScreen {
} }
impl RecordingScreen { impl RecordingScreen {
/// Update the track sets variable as well as the user interface. /// Update the tracks variable as well as the user interface.
fn show_track_sets(&self, track_sets: Vec<TrackSet>) { fn show_tracks(&self, tracks: Vec<Track>) {
let mut first = true; let length = tracks.len();
let mut items = Vec::new(); self.tracks.replace(tracks);
for (track_set_index, track_set) in track_sets.iter().enumerate() {
if !first {
items.push(ListItem::Separator);
} else {
first = false;
}
for (track_index, _) in track_set.tracks.iter().enumerate() {
items.push(ListItem::Track(track_set_index, track_index));
}
}
let length = items.len();
self.items.replace(items);
self.track_sets.replace(track_sets);
self.list.update(length); self.list.update(length);
} }
} }

View file

@ -1,7 +1,7 @@
use glib::clone; use glib::clone;
use gtk::prelude::*; use gtk::prelude::*;
use gtk_macros::get_widget; use gtk_macros::get_widget;
use musicus_backend::{Player, PlaylistItem}; use musicus_backend::Player;
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
@ -83,7 +83,7 @@ impl PlayerBar {
self.player.replace(player.clone()); self.player.replace(player.clone());
if let Some(player) = player { if let Some(player) = player {
let playlist = Rc::new(RefCell::new(Vec::<PlaylistItem>::new())); let playlist = Rc::new(RefCell::new(Vec::new()));
player.add_playlist_cb(clone!( player.add_playlist_cb(clone!(
@strong player, @strong player,
@ -107,25 +107,24 @@ impl PlayerBar {
@strong self.title_label as title_label, @strong self.title_label as title_label,
@strong self.subtitle_label as subtitle_label, @strong self.subtitle_label as subtitle_label,
@strong self.position_label as position_label @strong self.position_label as position_label
=> move |current_item, current_track| { => move |current_track| {
previous_button.set_sensitive(player.has_previous()); previous_button.set_sensitive(player.has_previous());
next_button.set_sensitive(player.has_next()); next_button.set_sensitive(player.has_next());
let item = &playlist.borrow()[current_item]; let track = &playlist.borrow()[current_track];
let track = &item.track_set.tracks[current_track];
let mut parts = Vec::<String>::new(); let mut parts = Vec::<String>::new();
for part in &track.work_parts { for part in &track.work_parts {
parts.push(item.track_set.recording.work.parts[*part].title.clone()); parts.push(track.recording.work.parts[*part].title.clone());
} }
let mut title = item.track_set.recording.work.get_title(); let mut title = track.recording.work.get_title();
if !parts.is_empty() { if !parts.is_empty() {
title = format!("{}: {}", title, parts.join(", ")); title = format!("{}: {}", title, parts.join(", "));
} }
title_label.set_text(&title); title_label.set_text(&title);
subtitle_label.set_text(&item.track_set.recording.get_performers()); subtitle_label.set_text(&track.recording.get_performers());
position_label.set_text("0:00"); position_label.set_text("0:00");
} }
)); ));