From 4f617cb79aad2cd5ac5ee23793ec8b3a8b5cdca8 Mon Sep 17 00:00:00 2001 From: Elias Projahn Date: Thu, 8 Apr 2021 00:08:31 +0200 Subject: [PATCH] Use flat track list for mediums --- backend/src/player.rs | 143 +++++------- .../2020-09-27-201047_initial_schema/down.sql | 1 - .../2020-09-27-201047_initial_schema/up.sql | 10 +- database/src/medium.rs | 216 +++++++----------- database/src/schema.rs | 16 +- database/src/thread.rs | 12 +- musicus/src/import/medium_editor.rs | 140 ++++++------ musicus/src/screens/medium.rs | 103 ++------- musicus/src/screens/player.rs | 104 +++++---- musicus/src/screens/recording.rs | 99 ++------ musicus/src/widgets/player_bar.rs | 15 +- 11 files changed, 327 insertions(+), 532 deletions(-) diff --git a/backend/src/player.rs b/backend/src/player.rs index 2c96522..f2388fd 100644 --- a/backend/src/player.rs +++ b/backend/src/player.rs @@ -1,5 +1,5 @@ use crate::{Error, Result}; -use musicus_database::TrackSet; +use musicus_database::Track; use glib::clone; use gstreamer_player::prelude::*; use std::cell::{Cell, RefCell}; @@ -10,21 +10,14 @@ use std::sync::Arc; #[cfg(target_os = "linux")] use mpris_player::{Metadata, MprisPlayer, PlaybackStatus}; -#[derive(Clone)] -pub struct PlaylistItem { - pub track_set: TrackSet, - pub indices: Vec, -} - pub struct Player { music_library_path: PathBuf, player: gstreamer_player::Player, - playlist: RefCell>, - current_item: Cell>, + playlist: RefCell>, current_track: Cell>, playing: Cell, - playlist_cbs: RefCell)>>>, - track_cbs: RefCell>>, + playlist_cbs: RefCell)>>>, + track_cbs: RefCell>>, duration_cbs: RefCell>>, playing_cbs: RefCell>>, position_cbs: RefCell>>, @@ -47,7 +40,6 @@ impl Player { music_library_path, player: player.clone(), playlist: RefCell::new(Vec::new()), - current_item: Cell::new(None), current_track: Cell::new(None), playing: Cell::new(false), playlist_cbs: RefCell::new(Vec::new()), @@ -144,11 +136,11 @@ impl Player { result } - pub fn add_playlist_cb) + 'static>(&self, cb: F) { + pub fn add_playlist_cb) + 'static>(&self, cb: F) { self.playlist_cbs.borrow_mut().push(Box::new(cb)); } - pub fn add_track_cb(&self, cb: F) { + pub fn add_track_cb(&self, cb: F) { self.track_cbs.borrow_mut().push(Box::new(cb)); } @@ -168,14 +160,10 @@ impl Player { self.raise_cb.replace(Some(Box::new(cb))); } - pub fn get_playlist(&self) -> Vec { + pub fn get_playlist(&self) -> Vec { self.playlist.borrow().clone() } - pub fn get_current_item(&self) -> Option { - self.current_item.get() - } - pub fn get_current_track(&self) -> Option { self.current_track.get() } @@ -188,41 +176,37 @@ impl Player { self.playing.get() } - pub fn add_item(&self, item: PlaylistItem) -> Result<()> { - if item.indices.is_empty() { - Err(Error::Other(String::from("Tried to add an empty playlist item!"))) - } else { - let was_empty = { - let mut playlist = self.playlist.borrow_mut(); - let was_empty = playlist.is_empty(); + pub fn add_item(&self, item: Track) -> Result<()> { + let was_empty = { + let mut playlist = self.playlist.borrow_mut(); + let was_empty = playlist.is_empty(); - playlist.push(item); + playlist.push(item); - was_empty - }; + was_empty + }; - for cb in &*self.playlist_cbs.borrow() { - cb(self.playlist.borrow().clone()); - } - - if was_empty { - self.set_track(0, 0)?; - self.player.play(); - self.playing.set(true); - - for cb in &*self.playing_cbs.borrow() { - cb(true); - } - - #[cfg(target_os = "linux")] - { - self.mpris.set_can_play(true); - self.mpris.set_playback_status(PlaybackStatus::Playing); - } - } - - Ok(()) + for cb in &*self.playlist_cbs.borrow() { + cb(self.playlist.borrow().clone()); } + + if was_empty { + self.set_track(0)?; + self.player.play(); + self.playing.set(true); + + for cb in &*self.playing_cbs.borrow() { + cb(true); + } + + #[cfg(target_os = "linux")] + { + self.mpris.set_can_play(true); + self.mpris.set_playback_status(PlaybackStatus::Playing); + } + } + + Ok(()) } pub fn play_pause(&self) { @@ -254,79 +238,56 @@ impl Player { } pub fn has_previous(&self) -> bool { - if let Some(current_item) = self.current_item.get() { - if let Some(current_track) = self.current_track.get() { - current_track > 0 || current_item > 0 - } else { - false - } + if let Some(current_track) = self.current_track.get() { + current_track > 0 } else { false } } 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 .current_track .get() .ok_or(Error::Other(String::from("Player tried to access non existant current track.")))?; - let playlist = self.playlist.borrow(); if current_track > 0 { current_track -= 1; - } else if current_item > 0 { - current_item -= 1; - current_track = playlist[current_item].indices.len() - 1; } else { 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 { - if let Some(current_item) = self.current_item.get() { - if let Some(current_track) = self.current_track.get() { - let playlist = self.playlist.borrow(); - let item = &playlist[current_item]; - - current_track + 1 < item.indices.len() || current_item + 1 < playlist.len() - } else { - false - } + if let Some(current_track) = self.current_track.get() { + let playlist = self.playlist.borrow(); + current_track + 1 < playlist.len() } else { false } } 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 .current_track .get() .ok_or(Error::Other(String::from("Player tried to access non existant current track.")))?; 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; - } else if current_item + 1 < playlist.len() { - current_item += 1; - current_track = 0; } 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<()> { - let item = &self.playlist.borrow()[current_item]; - let track = &item.track_set.tracks[current_track]; + pub fn set_track(&self, current_track: usize) -> Result<()> { + let track = &self.playlist.borrow()[current_track]; let path = self.music_library_path.join(track.path.clone()) .into_os_string().into_string().unwrap(); @@ -340,26 +301,25 @@ impl Player { self.player.play(); } - self.current_item.set(Some(current_item)); self.current_track.set(Some(current_track)); for cb in &*self.track_cbs.borrow() { - cb(current_item, current_track); + cb(current_track); } #[cfg(target_os = "linux")] { let mut parts = Vec::::new(); 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() { title = format!("{}: {}", title, parts.join(", ")); } - let subtitle = item.track_set.recording.get_performers(); + let subtitle = track.recording.get_performers(); let mut metadata = Metadata::new(); metadata.artist = Some(vec![title]); @@ -379,7 +339,7 @@ impl Player { } 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() { @@ -394,7 +354,6 @@ impl Player { pub fn clear(&self) { self.player.stop(); self.playing.set(false); - self.current_item.set(None); self.current_track.set(None); self.playlist.replace(Vec::new()); diff --git a/database/migrations/2020-09-27-201047_initial_schema/down.sql b/database/migrations/2020-09-27-201047_initial_schema/down.sql index 39e9f73..91fbc93 100644 --- a/database/migrations/2020-09-27-201047_initial_schema/down.sql +++ b/database/migrations/2020-09-27-201047_initial_schema/down.sql @@ -10,6 +10,5 @@ DROP TABLE "ensembles"; DROP TABLE "recordings"; DROP TABLE "performances"; DROP TABLE "mediums"; -DROP TABLE "track_sets"; DROP TABLE "tracks"; diff --git a/database/migrations/2020-09-27-201047_initial_schema/up.sql b/database/migrations/2020-09-27-201047_initial_schema/up.sql index 55f84f8..112e467 100644 --- a/database/migrations/2020-09-27-201047_initial_schema/up.sql +++ b/database/migrations/2020-09-27-201047_initial_schema/up.sql @@ -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 ); diff --git a/database/src/medium.rs b/database/src/medium.rs index d315378..0f0a896 100644 --- a/database/src/medium.rs +++ b/database/src/medium.rs @@ -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, - /// The tracks of the medium, grouped by recording. - pub tracks: Vec, -} - -/// 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, } -/// 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, @@ -56,23 +48,14 @@ struct MediumRow { pub discid: Option, } -/// 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::>() + .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::>() - .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 = 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 = 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> { - let mut track_sets: Vec = Vec::new(); + /// Get all available tracks for a recording. + pub fn get_tracks(&self, recording_id: &str) -> Result> { + let mut tracks: Vec = 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::(&self.connection)?; + .select(tracks::table::all_columns()) + .load::(&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 { - let track_set_rows = track_sets::table - .filter(track_sets::medium.eq(&row.id)) - .order_by(track_sets::index) - .load::(&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 { - 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::(&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 { + 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) } } diff --git a/database/src/schema.rs b/database/src/schema.rs index 0f478cb..bc046c4 100644 --- a/database/src/schema.rs +++ b/database/src/schema.rs @@ -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, diff --git a/database/src/thread.rs b/database/src/thread.rs index 42eb9fe..17f40f0 100644 --- a/database/src/thread.rs +++ b/database/src/thread.rs @@ -32,7 +32,7 @@ pub enum Action { GetMediumsForPerson(String, Sender>>), GetMediumsForEnsemble(String, Sender>>), DeleteMedium(String, Sender>), - GetTrackSets(String, Sender>>), + GetTracks(String, Sender>>), 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> { + /// Get all tracks for a recording. + pub async fn get_tracks(&self, recording_id: &str) -> Result> { 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? } diff --git a/musicus/src/import/medium_editor.rs b/musicus/src/import/medium_editor.rs index ab43ac7..fcd7cb1 100644 --- a/musicus/src/import/medium_editor.rs +++ b/musicus/src/import/medium_editor.rs @@ -7,7 +7,7 @@ use glib::prelude::*; use gtk::prelude::*; use gtk_macros::get_widget; 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 std::cell::RefCell; use std::rc::Rc; @@ -26,7 +26,7 @@ pub struct MediumEditor { status_page: libadwaita::StatusPage, disc_status_page: libadwaita::StatusPage, track_set_list: Rc, - track_sets: RefCell>, + // track_sets: RefCell>, } impl Screen, ()> for MediumEditor { @@ -65,7 +65,7 @@ impl Screen, ()> for MediumEditor { status_page, disc_status_page, track_set_list: list, - track_sets: RefCell::new(Vec::new()), + // track_sets: RefCell::new(Vec::new()), }); // Connect signals and callbacks @@ -89,43 +89,43 @@ impl Screen, ()> for MediumEditor { add_button.connect_clicked(clone!(@weak this => move |_| { spawn!(@clone this, async move { - if let Some(track_set) = push!(this.handle, TrackSetEditor, Arc::clone(&this.session)).await { - let length = { - let mut track_sets = this.track_sets.borrow_mut(); - track_sets.push(track_set); - track_sets.len() - }; + // if let Some(track_set) = push!(this.handle, TrackSetEditor, Arc::clone(&this.session)).await { + // let length = { + // let mut track_sets = this.track_sets.borrow_mut(); + // track_sets.push(track_set); + // 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| { - let track_set = &this.track_sets.borrow()[index]; + // this.track_set_list.set_make_widget_cb(clone!(@weak this => move |index| { + // let track_set = &this.track_sets.borrow()[index]; - let title = track_set.recording.work.get_title(); - let subtitle = track_set.recording.get_performers(); + // let title = track_set.recording.work.get_title(); + // let subtitle = track_set.recording.get_performers(); - let edit_image = gtk::Image::from_icon_name(Some("document-edit-symbolic")); - let edit_button = gtk::Button::new(); - edit_button.set_has_frame(false); - edit_button.set_valign(gtk::Align::Center); - edit_button.set_child(Some(&edit_image)); + // let edit_image = gtk::Image::from_icon_name(Some("document-edit-symbolic")); + // let edit_button = gtk::Button::new(); + // edit_button.set_has_frame(false); + // edit_button.set_valign(gtk::Align::Center); + // edit_button.set_child(Some(&edit_image)); - let row = libadwaita::ActionRow::new(); - row.set_activatable(true); - row.set_title(Some(&title)); - row.set_subtitle(Some(&subtitle)); - row.add_suffix(&edit_button); - row.set_activatable_widget(Some(&edit_button)); + // let row = libadwaita::ActionRow::new(); + // row.set_activatable(true); + // row.set_title(Some(&title)); + // row.set_subtitle(Some(&subtitle)); + // row.add_suffix(&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. - })); + // })); - row.upcast() - })); + // row.upcast() + // })); try_again_button.connect_clicked(clone!(@weak this => move |_| { this.widget.set_visible_child_name("content"); @@ -155,68 +155,68 @@ impl Screen, ()> for MediumEditor { impl MediumEditor { /// Save the medium and possibly upload it to the server. 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. - let mut path = self.handle.backend.get_music_library_path().unwrap().clone(); - path.push(&name); - std::fs::create_dir(&path)?; + // let mut path = self.handle.backend.get_music_library_path().unwrap().clone(); + // path.push(&name); + // std::fs::create_dir(&path)?; // Convert the track set data to real track sets. - let mut track_sets = Vec::new(); - let import_tracks = self.session.tracks(); + // let mut track_sets = Vec::new(); + // let import_tracks = self.session.tracks(); - for track_set_data in &*self.track_sets.borrow() { - let mut tracks = Vec::new(); + // for track_set_data in &*self.track_sets.borrow() { + // 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. - let import_track = &import_tracks[track_data.track_source]; + // let import_track = &import_tracks[track_data.track_source]; - let mut track_path = path.clone(); - track_path.push(import_track.path.file_name().unwrap()); + // let mut track_path = path.clone(); + // 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. - let track = Track { - work_parts: track_data.work_parts.clone(), - path: track_path.to_str().unwrap().to_owned(), - }; + // let track = Track { + // work_parts: track_data.work_parts.clone(), + // path: track_path.to_str().unwrap().to_owned(), + // }; - tracks.push(track); - } + // tracks.push(track); + // } - let track_set = TrackSet { - recording: track_set_data.recording.clone(), - tracks, - }; + // let track_set = TrackSet { + // recording: track_set_data.recording.clone(), + // tracks, + // }; - track_sets.push(track_set); - } + // track_sets.push(track_set); + // } - let medium = Medium { - id: generate_id(), - name: self.name_entry.get_text().to_string(), - discid: Some(self.session.source_id().to_owned()), - tracks: track_sets, - }; + // let medium = Medium { + // id: generate_id(), + // name: self.name_entry.get_text().to_string(), + // discid: Some(self.session.source_id().to_owned()), + // tracks: track_sets, + // }; - let upload = self.publish_switch.get_active(); - if upload { - self.handle.backend.cl().post_medium(&medium).await?; - } + // let upload = self.publish_switch.get_active(); + // if upload { + // self.handle.backend.cl().post_medium(&medium).await?; + // } - self.handle.backend - .db() - .update_medium(medium.clone()) - .await?; + // self.handle.backend + // .db() + // .update_medium(medium.clone()) + // .await?; - self.handle.backend.library_changed(); + // self.handle.backend.library_changed(); Ok(()) } diff --git a/musicus/src/screens/medium.rs b/musicus/src/screens/medium.rs index 7b3db52..6b55e3e 100644 --- a/musicus/src/screens/medium.rs +++ b/musicus/src/screens/medium.rs @@ -5,53 +5,21 @@ use gettextrs::gettext; use glib::clone; use gtk::prelude::*; use libadwaita::prelude::*; -use musicus_backend::PlaylistItem; use musicus_backend::db::Medium; 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. pub struct MediumScreen { handle: NavigationHandle<()>, medium: Medium, widget: widgets::Screen, list: Rc, - items: Vec, } impl Screen for MediumScreen { /// Create a new medium screen for the specified medium and load the /// contents asynchronously. fn new(medium: Medium, handle: NavigationHandle<()>) -> Rc { - 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(); widget.set_title(&medium.name); @@ -65,7 +33,6 @@ impl Screen for MediumScreen { medium, widget, list, - items, }); this.widget.set_back_cb(clone!(@weak this => move || { @@ -82,63 +49,35 @@ impl Screen for MediumScreen { })); section.add_action("media-playback-start-symbolic", clone!(@weak this => move || { - for track_set in &this.medium.tracks { - let indices = (0..track_set.tracks.len()).collect(); - - let playlist_item = PlaylistItem { - track_set: track_set.clone(), - indices, - }; - - this.handle.backend.pl().add_item(playlist_item).unwrap(); + for track in &this.medium.tracks { + this.handle.backend.pl().add_item(track.clone()).unwrap(); } })); this.list.set_make_widget_cb(clone!(@weak this => move |index| { - match this.items[index] { - ListItem::Header(index) => { - let track_set = &this.medium.tracks[index]; - let recording = &track_set.recording; + let track = &this.medium.tracks[index]; - 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::::new(); - for part in &track.work_parts { - parts.push(track_set.recording.work.parts[*part].title.clone()); - } - - let title = if parts.is_empty() { - gettext("Unknown") - } else { - parts.join(", ") - }; - - let row = libadwaita::ActionRow::new(); - row.set_selectable(false); - row.set_activatable(false); - row.set_title(Some(&title)); - row.set_margin_start(12); - - row.upcast() - } - ListItem::Separator => { - let separator = gtk::Separator::new(gtk::Orientation::Horizontal); - separator.upcast() - } + let mut parts = Vec::::new(); + for part in &track.work_parts { + parts.push(track.recording.work.parts[*part].title.clone()); } + + let title = if parts.is_empty() { + gettext("Unknown") + } else { + parts.join(", ") + }; + + let row = libadwaita::ActionRow::new(); + row.set_selectable(false); + row.set_activatable(false); + row.set_title(Some(&title)); + row.set_margin_start(12); + + row.upcast() })); - this.list.update(this.items.len()); + this.list.update(this.medium.tracks.len()); this } diff --git a/musicus/src/screens/player.rs b/musicus/src/screens/player.rs index 3fc93f9..b414b82 100644 --- a/musicus/src/screens/player.rs +++ b/musicus/src/screens/player.rs @@ -5,21 +5,25 @@ use glib::clone; use gtk::prelude::*; use gtk_macros::get_widget; use libadwaita::prelude::*; -use musicus_backend::PlaylistItem; +use musicus_backend::db::Track; use std::cell::{Cell, RefCell}; use std::rc::Rc; /// Elements for visually representing the playlist. enum ListItem { - /// A header shown on top of a track set. This contains an index - /// referencing the playlist item containing this track set. - Header(usize), + /// A playable track. + Track { + /// Index within the playlist. + index: usize, - /// A playable track. This contains an index to the playlist item, an - /// index to the track and whether it is the currently played one. - Track(usize, usize, bool), + /// Whether this is the first track of the recording. + first: bool, - /// A separator shown between track sets. + /// Whether this is the currently played track. + playing: bool, + }, + + /// A separator shown between recordings. Separator, } @@ -37,10 +41,9 @@ pub struct PlayerScreen { play_image: gtk::Image, pause_image: gtk::Image, list: Rc, - playlist: RefCell>, + playlist: RefCell>, items: RefCell>, seeking: Cell, - current_item: Cell, current_track: Cell, } @@ -87,7 +90,6 @@ impl Screen<(), ()> for PlayerScreen { items: RefCell::new(Vec::new()), playlist: RefCell::new(Vec::new()), seeking: Cell::new(false), - current_item: Cell::new(0), current_track: Cell::new(0), }); @@ -102,28 +104,26 @@ impl Screen<(), ()> for PlayerScreen { 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.next_button.set_sensitive(this.handle.backend.pl().has_next()); - let item = &this.playlist.borrow()[current_item]; - let track = &item.track_set.tracks[current_track]; + let track = &this.playlist.borrow()[current_track]; let mut parts = Vec::::new(); 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() { title = format!("{}: {}", title, parts.join(", ")); } 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.current_item.set(current_item); this.current_track.set(current_track); this.show_playlist(); @@ -205,29 +205,17 @@ impl Screen<(), ()> for PlayerScreen { this.list.set_make_widget_cb(clone!(@weak this => move |index| { let widget = match this.items.borrow()[index] { - ListItem::Header(item_index) => { - let playlist_item = &this.playlist.borrow()[item_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]; + ListItem::Track {index, first, playing} => { + let track = &this.playlist.borrow()[index]; let mut parts = Vec::::new(); 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") } else { parts.join(", ") @@ -238,8 +226,18 @@ impl Screen<(), ()> for PlayerScreen { row.set_activatable(true); 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 |_| { - this.handle.backend.pl().set_track(item_index, track_index).unwrap(); + this.handle.backend.pl().set_track(index).unwrap(); })); let icon = if playing { @@ -272,25 +270,35 @@ impl PlayerScreen { /// Update the user interface according to the playlist. fn show_playlist(&self) { let playlist = self.playlist.borrow(); - let current_item = self.current_item.get(); let current_track = self.current_track.get(); let mut first = true; let mut items = Vec::new(); - for (item_index, playlist_item) in playlist.iter().enumerate() { - if !first { - items.push(ListItem::Separator); + 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 { + items.push(ListItem::Separator); + } else { + first = false; + } + + true } else { - first = false; - } + false + }; - items.push(ListItem::Header(item_index)); + let item = ListItem::Track { + index, + first: first_track, + playing: index == current_track, + }; - for (index, _) in playlist_item.indices.iter().enumerate() { - let playing = current_item == item_index && current_track == index; - items.push(ListItem::Track(item_index, index, playing)); - } + items.push(item); } let length = items.len(); diff --git a/musicus/src/screens/recording.rs b/musicus/src/screens/recording.rs index 8bf1345..44892ec 100644 --- a/musicus/src/screens/recording.rs +++ b/musicus/src/screens/recording.rs @@ -6,29 +6,17 @@ use gettextrs::gettext; use glib::clone; use gtk::prelude::*; use libadwaita::prelude::*; -use musicus_backend::PlaylistItem; -use musicus_backend::db::{Recording, TrackSet}; +use musicus_backend::db::{Recording, Track}; use std::cell::RefCell; 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. pub struct RecordingScreen { handle: NavigationHandle<()>, recording: Recording, widget: widgets::Screen, list: Rc, - track_sets: RefCell>, - items: RefCell>, + tracks: RefCell>, } impl Screen for RecordingScreen { @@ -48,22 +36,12 @@ impl Screen for RecordingScreen { recording, widget, list, - track_sets: RefCell::new(Vec::new()), - items: RefCell::new(Vec::new()), + tracks: RefCell::new(Vec::new()), }); section.add_action("media-playback-start-symbolic", clone!(@weak this => move || { - if let Some(player) = this.handle.backend.get_player() { - if let Some(track_set) = this.track_sets.borrow().get(0).cloned() { - let indices = (0..track_set.tracks.len()).collect(); - - let playlist_item = PlaylistItem { - track_set, - indices, - }; - - player.add_item(playlist_item).unwrap(); - } + for track in &*this.tracks.borrow() { + this.handle.backend.pl().add_item(track.clone()).unwrap(); } })); @@ -86,47 +64,36 @@ impl Screen for RecordingScreen { })); this.list.set_make_widget_cb(clone!(@weak this => move |index| { - let widget = match this.items.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 track = &this.tracks.borrow()[index]; - let mut title_parts = Vec::::new(); - for part in &track.work_parts { - title_parts.push(this.recording.work.parts[*part].title.clone()); - } + let mut title_parts = Vec::::new(); + for part in &track.work_parts { + title_parts.push(this.recording.work.parts[*part].title.clone()); + } - let title = if title_parts.is_empty() { - gettext("Unknown") - } else { - title_parts.join(", ") - }; - - let row = libadwaita::ActionRow::new(); - row.set_title(Some(&title)); - - row.upcast() - } - ListItem::Separator => { - let separator = gtk::Separator::new(gtk::Orientation::Horizontal); - separator.upcast() - } + let title = if title_parts.is_empty() { + gettext("Unknown") + } else { + title_parts.join(", ") }; - widget + let row = libadwaita::ActionRow::new(); + row.set_title(Some(&title)); + + row.upcast() })); // Load the content asynchronously. spawn!(@clone this, async move { - let track_sets = this.handle + let tracks = this.handle .backend .db() - .get_track_sets(&this.recording.id) + .get_tracks(&this.recording.id) .await .unwrap(); - this.show_track_sets(track_sets); + this.show_tracks(tracks); this.widget.ready(); }); @@ -135,26 +102,10 @@ impl Screen for RecordingScreen { } impl RecordingScreen { - /// Update the track sets variable as well as the user interface. - fn show_track_sets(&self, track_sets: Vec) { - let mut first = true; - let mut items = Vec::new(); - - 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); + /// Update the tracks variable as well as the user interface. + fn show_tracks(&self, tracks: Vec) { + let length = tracks.len(); + self.tracks.replace(tracks); self.list.update(length); } } diff --git a/musicus/src/widgets/player_bar.rs b/musicus/src/widgets/player_bar.rs index c2764ca..93077fa 100644 --- a/musicus/src/widgets/player_bar.rs +++ b/musicus/src/widgets/player_bar.rs @@ -1,7 +1,7 @@ use glib::clone; use gtk::prelude::*; use gtk_macros::get_widget; -use musicus_backend::{Player, PlaylistItem}; +use musicus_backend::Player; use std::cell::RefCell; use std::rc::Rc; @@ -83,7 +83,7 @@ impl PlayerBar { self.player.replace(player.clone()); if let Some(player) = player { - let playlist = Rc::new(RefCell::new(Vec::::new())); + let playlist = Rc::new(RefCell::new(Vec::new())); player.add_playlist_cb(clone!( @strong player, @@ -107,25 +107,24 @@ impl PlayerBar { @strong self.title_label as title_label, @strong self.subtitle_label as subtitle_label, @strong self.position_label as position_label - => move |current_item, current_track| { + => move |current_track| { previous_button.set_sensitive(player.has_previous()); next_button.set_sensitive(player.has_next()); - let item = &playlist.borrow()[current_item]; - let track = &item.track_set.tracks[current_track]; + let track = &playlist.borrow()[current_track]; let mut parts = Vec::::new(); 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() { title = format!("{}: {}", title, parts.join(", ")); } 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"); } ));