Merge branch 'medium-tracks' into new-import-screen

This commit is contained in:
Elias Projahn 2021-04-08 00:15:19 +02:00
commit 3f5f751992
11 changed files with 327 additions and 539 deletions

View file

@ -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<usize>,
}
pub struct Player {
music_library_path: PathBuf,
player: gstreamer_player::Player,
playlist: RefCell<Vec<PlaylistItem>>,
current_item: Cell<Option<usize>>,
playlist: RefCell<Vec<Track>>,
current_track: Cell<Option<usize>>,
playing: Cell<bool>,
playlist_cbs: RefCell<Vec<Box<dyn Fn(Vec<PlaylistItem>)>>>,
track_cbs: RefCell<Vec<Box<dyn Fn(usize, usize)>>>,
playlist_cbs: RefCell<Vec<Box<dyn Fn(Vec<Track>)>>>,
track_cbs: RefCell<Vec<Box<dyn Fn(usize)>>>,
duration_cbs: RefCell<Vec<Box<dyn Fn(u64)>>>,
playing_cbs: RefCell<Vec<Box<dyn Fn(bool)>>>,
position_cbs: RefCell<Vec<Box<dyn Fn(u64)>>>,
@ -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<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));
}
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));
}
@ -168,14 +160,10 @@ impl Player {
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()
}
pub fn get_current_item(&self) -> Option<usize> {
self.current_item.get()
}
pub fn get_current_track(&self) -> Option<usize> {
self.current_track.get()
}
@ -188,10 +176,7 @@ 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 {
pub fn add_item(&self, item: Track) -> Result<()> {
let was_empty = {
let mut playlist = self.playlist.borrow_mut();
let was_empty = playlist.is_empty();
@ -206,7 +191,7 @@ impl Player {
}
if was_empty {
self.set_track(0, 0)?;
self.set_track(0)?;
self.player.play();
self.playing.set(true);
@ -223,7 +208,6 @@ impl Player {
Ok(())
}
}
pub fn play_pause(&self) {
if self.is_playing() {
@ -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
}
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
}
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::<String>::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());

View file

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

View file

@ -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
);

View file

@ -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,32 +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>,
}
impl Medium {
/// Get an iterator that iterates through all tracks within this medium in order.
pub fn track_iter<'a>(&'a self) -> TrackIter<'a> {
TrackIter::new(self)
}
}
/// 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>,
@ -63,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,
}
@ -135,7 +111,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.
@ -150,30 +126,15 @@ 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 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
.work_parts
.iter()
@ -183,8 +144,9 @@ impl Database {
let track_row = TrackRow {
id: generate_id(),
track_set: track_set_id.clone(),
medium: medium_id.to_owned(),
index: index as i32,
recording: track.recording.id.clone(),
work_parts,
path: track.path.clone(),
};
@ -193,7 +155,6 @@ impl Database {
.values(track_row)
.execute(&self.connection)?;
}
}
Ok(())
})?;
@ -238,8 +199,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))
@ -260,8 +221,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))
@ -284,93 +245,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 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 work_parts = track_row
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 '{}'.", track_row.work_parts))))?;
.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: track_row.path,
path: row.path,
};
tracks.push(track);
}
let track_set = TrackSet { recording, tracks };
Ok(track_set)
Ok(track)
}
}

View file

@ -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,

View file

@ -33,7 +33,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<()>),
}
@ -145,8 +145,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();
@ -371,10 +371,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?
}

View file

@ -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;
@ -24,7 +24,7 @@ pub struct MediumEditor {
status_page: libadwaita::StatusPage,
disc_status_page: libadwaita::StatusPage,
track_set_list: Rc<List>,
track_sets: RefCell<Vec<TrackSetData>>,
// track_sets: RefCell<Vec<TrackSetData>>,
}
impl Screen<Arc<ImportSession>, Medium> for MediumEditor {
@ -59,7 +59,7 @@ impl Screen<Arc<ImportSession>, Medium> 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
@ -83,43 +83,43 @@ impl Screen<Arc<ImportSession>, Medium> 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");
@ -136,68 +136,68 @@ impl Screen<Arc<ImportSession>, Medium> for MediumEditor {
impl MediumEditor {
/// Save the medium and possibly upload it to the server.
async fn save(&self) -> Result<Medium> {
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(medium)
}

View file

@ -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<List>,
items: Vec<ListItem>,
}
impl Screen<Medium, ()> for MediumScreen {
/// Create a new medium screen for the specified medium and load the
/// contents asynchronously.
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();
widget.set_title(&medium.name);
@ -65,7 +33,6 @@ impl Screen<Medium, ()> for MediumScreen {
medium,
widget,
list,
items,
});
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 || {
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 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 track = &this.medium.tracks[index];
let mut parts = Vec::<String>::new();
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() {
@ -130,15 +75,9 @@ impl Screen<Medium, ()> for MediumScreen {
row.set_margin_start(12);
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
}

View file

@ -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<List>,
playlist: RefCell<Vec<PlaylistItem>>,
playlist: RefCell<Vec<Track>>,
items: RefCell<Vec<ListItem>>,
seeking: Cell<bool>,
current_item: Cell<usize>,
current_track: Cell<usize>,
}
@ -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::<String>::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::<String>::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() {
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;
}
items.push(ListItem::Header(item_index));
true
} else {
false
};
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));
}
let item = ListItem::Track {
index,
first: first_track,
playing: index == current_track,
};
items.push(item);
}
let length = items.len();

View file

@ -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<List>,
track_sets: RefCell<Vec<TrackSet>>,
items: RefCell<Vec<ListItem>>,
tracks: RefCell<Vec<Track>>,
}
impl Screen<Recording, ()> for RecordingScreen {
@ -48,22 +36,12 @@ impl Screen<Recording, ()> 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,10 +64,7 @@ impl Screen<Recording, ()> 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::<String>::new();
for part in &track.work_parts {
@ -106,27 +81,19 @@ impl Screen<Recording, ()> for RecordingScreen {
row.set_title(Some(&title));
row.upcast()
}
ListItem::Separator => {
let separator = gtk::Separator::new(gtk::Orientation::Horizontal);
separator.upcast()
}
};
widget
}));
// 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<Recording, ()> for RecordingScreen {
}
impl RecordingScreen {
/// Update the track sets variable as well as the user interface.
fn show_track_sets(&self, track_sets: Vec<TrackSet>) {
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<Track>) {
let length = tracks.len();
self.tracks.replace(tracks);
self.list.update(length);
}
}

View file

@ -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::<PlaylistItem>::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::<String>::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");
}
));