From 606ee563e945254395ead8d615ec56cc4cdbe31b Mon Sep 17 00:00:00 2001 From: Elias Projahn Date: Tue, 23 Mar 2021 09:53:16 +0100 Subject: [PATCH] Initial version of new import screen --- database/src/medium.rs | 16 +++ database/src/thread.rs | 11 ++ musicus/Cargo.toml | 1 + musicus/res/musicus.gresource.xml | 2 + musicus/res/ui/import_screen.ui | 177 ++++++++++++++++++++++++++ musicus/src/import/import_screen.rs | 147 +++++++++++++++++++++ musicus/src/import/mod.rs | 2 + musicus/src/import/source_selector.rs | 7 +- 8 files changed, 361 insertions(+), 2 deletions(-) create mode 100644 musicus/res/ui/import_screen.ui create mode 100644 musicus/src/import/import_screen.rs diff --git a/database/src/medium.rs b/database/src/medium.rs index d315378..7eea061 100644 --- a/database/src/medium.rs +++ b/database/src/medium.rs @@ -167,6 +167,22 @@ impl Database { Ok(medium) } + /// Get mediums that have a specific source ID. + pub fn get_mediums_by_source_id(&self, source_id: &str) -> Result> { + let mut mediums: Vec = Vec::new(); + + let rows = mediums::table + .filter(mediums::discid.nullable().eq(source_id)) + .load::(&self.connection)?; + + for row in rows { + let medium = self.get_medium_data(row)?; + mediums.push(medium); + } + + Ok(mediums) + } + /// Get mediums on which this person is performing. pub fn get_mediums_for_person(&self, person_id: &str) -> Result> { let mut mediums: Vec = Vec::new(); diff --git a/database/src/thread.rs b/database/src/thread.rs index 42eb9fe..e4bb7e6 100644 --- a/database/src/thread.rs +++ b/database/src/thread.rs @@ -29,6 +29,7 @@ pub enum Action { RecordingExists(String, Sender>), UpdateMedium(Medium, Sender>), GetMedium(String, Sender>>), + GetMediumsBySourceId(String, Sender>>), GetMediumsForPerson(String, Sender>>), GetMediumsForEnsemble(String, Sender>>), DeleteMedium(String, Sender>), @@ -132,6 +133,9 @@ impl DbThread { GetMedium(id, sender) => { sender.send(db.get_medium(&id)).unwrap(); } + GetMediumsBySourceId(id, sender) => { + sender.send(db.get_mediums_by_source_id(&id)).unwrap(); + } GetMediumsForPerson(id, sender) => { sender.send(db.get_mediums_for_person(&id)).unwrap(); } @@ -346,6 +350,13 @@ impl DbThread { receiver.await? } + /// Get all mediums with the specified source ID. + pub async fn get_mediums_by_source_id(&self, id: &str) -> Result> { + let (sender, receiver) = oneshot::channel(); + self.action_sender.send(GetMediumsBySourceId(id.to_owned(), sender))?; + receiver.await? + } + /// Get all mediums on which a person performs. pub async fn get_mediums_for_person(&self, id: &str) -> Result> { let (sender, receiver) = oneshot::channel(); diff --git a/musicus/Cargo.toml b/musicus/Cargo.toml index 13f3a14..7b3a722 100644 --- a/musicus/Cargo.toml +++ b/musicus/Cargo.toml @@ -11,6 +11,7 @@ futures-channel = "0.3.5" gettext-rs = { version = "0.5.0", features = ["gettext-system"] } gstreamer = "0.16.4" gtk-macros = "0.2.0" +log = "0.4.14" musicus_backend = { version = "0.1.0", path = "../backend" } once_cell = "1.5.2" rand = "0.7.3" diff --git a/musicus/res/musicus.gresource.xml b/musicus/res/musicus.gresource.xml index 1918ed5..a57529d 100644 --- a/musicus/res/musicus.gresource.xml +++ b/musicus/res/musicus.gresource.xml @@ -2,9 +2,11 @@ ui/editor.ui + ui/import_screen.ui ui/login_dialog.ui ui/main_screen.ui ui/medium_editor.ui + ui/medium_preview.ui ui/performance_editor.ui ui/player_bar.ui ui/player_screen.ui diff --git a/musicus/res/ui/import_screen.ui b/musicus/res/ui/import_screen.ui new file mode 100644 index 0000000..8310f5b --- /dev/null +++ b/musicus/res/ui/import_screen.ui @@ -0,0 +1,177 @@ + + + + + + vertical + + + false + false + + + Import music + + + + + go-previous-symbolic + + + + + + + True + + + + + 6 + 6 + 6 + vertical + + + start + 12 + 6 + Matching metadata + + + + + + + + + + crossfade + false + true + + + loading + + + none + + + Loading… + + + True + + + + + + + + + + + error + + + none + + + Error while searching for matching metadata + True + try_again_button + + + view-refresh-symbolic + center + + + + + + + + + + + empty + + + none + + + No matching metadata found + + + + + + + + + content + + + none + + + + + + + + + + + start + 12 + 6 + Manually add metadata + + + + + + + + + + none + + + Select existing medium + True + select_button + + + Select + center + + + + + + + Add a new medium + True + add_button + + + Add + center + + + + + + + + + + + + + + + + diff --git a/musicus/src/import/import_screen.rs b/musicus/src/import/import_screen.rs new file mode 100644 index 0000000..66c61c0 --- /dev/null +++ b/musicus/src/import/import_screen.rs @@ -0,0 +1,147 @@ +use super::medium_editor::MediumEditor; +use super::medium_preview::MediumPreview; +use crate::navigator::{NavigationHandle, Screen}; +use crate::widgets::Widget; +use glib::clone; +use gtk::prelude::*; +use gtk_macros::get_widget; +use libadwaita::prelude::*; +use log::debug; +use musicus_backend::db::Medium; +use musicus_backend::import::ImportSession; +use std::rc::Rc; +use std::sync::Arc; + +/// A dialog for selecting metadata when importing music. +pub struct ImportScreen { + handle: NavigationHandle<()>, + session: Arc, + widget: gtk::Box, + matching_stack: gtk::Stack, + error_row: libadwaita::ActionRow, + matching_list: gtk::ListBox, +} + +impl ImportScreen { + /// Find matching mediums on the server. + fn load_matches(self: &Rc) { + self.matching_stack.set_visible_child_name("loading"); + + let this = self; + spawn!(@clone this, async move { + match this.handle.backend.db().get_mediums_by_source_id(this.session.source_id()).await { + Ok(mediums) => { + if !mediums.is_empty() { + this.show_matches(mediums); + this.matching_stack.set_visible_child_name("content"); + } else { + this.matching_stack.set_visible_child_name("empty"); + } + } + Err(err) => { + this.error_row.set_subtitle(Some(&err.to_string())); + this.matching_stack.set_visible_child_name("error"); + } + } + }); + } + + /// Populate the list of matches + fn show_matches(self: &Rc, mediums: Vec) { + if let Some(mut child) = self.matching_list.get_first_child() { + loop { + let next_child = child.get_next_sibling(); + self.matching_list.remove(&child); + + match next_child { + Some(next_child) => child = next_child, + None => break, + } + } + } + + let this = self; + + for medium in mediums { + let row = libadwaita::ActionRowBuilder::new() + .title(&medium.name) + .subtitle(&format!("{} Recordings", medium.tracks.len())) + .activatable(true) + .build(); + + row.connect_activated(clone!(@weak this => move |_| { + debug!("Medium selected: {}", medium.name); + })); + + this.matching_list.append(&row); + } + } + + /// Select a medium from somewhere and present a preview. + fn select_medium(self: &Rc, medium: Medium) { + let this = self; + + spawn!(@clone this, async move { + push!(this.handle, MediumPreview, (this.session.clone(), medium)).await; + }); + } +} + +impl Screen, ()> for ImportScreen { + /// Create a new import screen. + fn new(session: Arc, handle: NavigationHandle<()>) -> Rc { + // Create UI + + let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/import_screen.ui"); + + get_widget!(builder, gtk::Box, widget); + get_widget!(builder, gtk::Button, back_button); + get_widget!(builder, gtk::Stack, matching_stack); + get_widget!(builder, gtk::Button, try_again_button); + get_widget!(builder, libadwaita::ActionRow, error_row); + get_widget!(builder, gtk::ListBox, matching_list); + get_widget!(builder, gtk::Button, select_button); + get_widget!(builder, gtk::Button, add_button); + + let this = Rc::new(Self { + handle, + session, + widget, + matching_stack, + error_row, + matching_list, + }); + + // Connect signals and callbacks + + back_button.connect_clicked(clone!(@weak this => move |_| { + this.handle.pop(None); + })); + + try_again_button.connect_clicked(clone!(@weak this => move |_| { + this.load_matches(); + })); + + select_button.connect_clicked(clone!(@weak this => move |_| { + debug!("TODO: Show medium selector."); + })); + + add_button.connect_clicked(clone!(@weak this => move |_| { + spawn!(@clone this, async move { + push!(this.handle, MediumEditor, Arc::clone(&this.session)).await; + }); + })); + + // Initialize the view + + this.load_matches(); + + this + } +} + +impl Widget for ImportScreen { + fn get_widget(&self) -> gtk::Widget { + self.widget.clone().upcast() + } +} diff --git a/musicus/src/import/mod.rs b/musicus/src/import/mod.rs index f6588c4..780eb5a 100644 --- a/musicus/src/import/mod.rs +++ b/musicus/src/import/mod.rs @@ -1,4 +1,6 @@ +mod import_screen; mod medium_editor; +mod medium_preview; mod source_selector; mod track_editor; mod track_selector; diff --git a/musicus/src/import/source_selector.rs b/musicus/src/import/source_selector.rs index e0f405e..289294b 100644 --- a/musicus/src/import/source_selector.rs +++ b/musicus/src/import/source_selector.rs @@ -1,3 +1,4 @@ +use super::import_screen::ImportScreen; use super::medium_editor::MediumEditor; use crate::navigator::{NavigationHandle, Screen}; use crate::widgets::Widget; @@ -65,8 +66,10 @@ impl Screen<(), ()> for SourceSelector { spawn!(@clone this, async move { match ImportSession::folder(PathBuf::from(path)).await { Ok(session) => { - push!(this.handle, MediumEditor, session).await; - this.handle.pop(Some(())); + // push!(this.handle, MediumEditor, session).await; + // this.handle.pop(Some(())); + + push!(this.handle, ImportScreen, session).await; } Err(err) => { this.status_page.set_description(Some(&err.to_string()));