Add folder source to import

This commit is contained in:
Elias Projahn 2021-01-29 17:51:44 +01:00
parent 030eccf253
commit 357a4a4429
8 changed files with 156 additions and 7 deletions

View file

@ -3,6 +3,7 @@ use anyhow::{anyhow, bail, Result};
use async_trait::async_trait; use async_trait::async_trait;
use discid::DiscId; use discid::DiscId;
use futures_channel::oneshot; use futures_channel::oneshot;
use gettextrs::gettext;
use gstreamer::prelude::*; use gstreamer::prelude::*;
use gstreamer::{Element, ElementFactory, Pipeline}; use gstreamer::{Element, ElementFactory, Pipeline};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
@ -45,6 +46,8 @@ impl DiscSource {
let tmp_dir = Self::create_tmp_dir()?; let tmp_dir = Self::create_tmp_dir()?;
for number in first_track..=last_track { for number in first_track..=last_track {
let name = gettext!("Track {}", number);
let file_name = format!("track_{:02}.flac", number); let file_name = format!("track_{:02}.flac", number);
let mut path = tmp_dir.clone(); let mut path = tmp_dir.clone();
@ -52,6 +55,7 @@ impl DiscSource {
let track = SourceTrack { let track = SourceTrack {
number, number,
name,
path, path,
}; };

View file

@ -0,0 +1,90 @@
use super::source::{Source, SourceTrack};
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use futures_channel::oneshot;
use once_cell::sync::OnceCell;
use std::path::{Path, PathBuf};
use std::thread;
/// A folder outside of the music library that contains tracks to import.
#[derive(Clone, Debug)]
pub struct FolderSource {
/// The path to the folder.
path: PathBuf,
/// The tracks within the folder.
tracks: OnceCell<Vec<SourceTrack>>,
}
impl FolderSource {
/// Create a new folder source.
pub fn new(path: PathBuf) -> Self {
Self {
path,
tracks: OnceCell::new(),
}
}
/// Load the contents of the folder as tracks.
fn load_priv(path: &Path) -> Result<Vec<SourceTrack>> {
let mut tracks = Vec::new();
let mut number = 1;
for entry in std::fs::read_dir(path)? {
let entry = entry?;
if entry.file_type()?.is_file() {
let name = entry
.file_name()
.into_string()
.or_else(|_| Err(anyhow!("Failed to convert OsString to String!")))?;
let path = entry.path();
let track = SourceTrack {
number,
name,
path,
};
tracks.push(track);
number += 1;
}
}
Ok(tracks)
}
}
#[async_trait]
impl Source for FolderSource {
async fn load(&self) -> Result<()> {
let (sender, receiver) = oneshot::channel();
let path = self.path.clone();
thread::spawn(move || {
let result = Self::load_priv(&path);
sender.send(result).unwrap();
});
let tracks = receiver.await??;
self.tracks.set(tracks);
Ok(())
}
fn tracks(&self) -> Option<&[SourceTrack]> {
match self.tracks.get() {
Some(tracks) => Some(tracks.as_slice()),
None => None,
}
}
fn discid(&self) -> Option<String> {
None
}
async fn copy(&self) -> Result<()> {
Ok(())
}
}

View file

@ -1,4 +1,5 @@
mod disc_source; mod disc_source;
mod folder_source;
mod medium_editor; mod medium_editor;
mod source; mod source;
mod source_selector; mod source_selector;

View file

@ -27,6 +27,10 @@ pub struct SourceTrack {
/// the track numbers start. /// the track numbers start.
pub number: u32, pub number: u32,
/// A human readable identifier for the track. This will be used to
/// present the track for selection.
pub name: String,
/// The path to the file where the corresponding audio file is. This file /// The path to the file where the corresponding audio file is. This file
/// is only required to exist, once the source's copy method has finished. /// is only required to exist, once the source's copy method has finished.
/// This will not be the actual file within the user's music library, but /// This will not be the actual file within the user's music library, but

View file

@ -1,12 +1,15 @@
use super::medium_editor::MediumEditor; use super::medium_editor::MediumEditor;
use super::disc_source::DiscSource; use super::disc_source::DiscSource;
use super::folder_source::FolderSource;
use super::source::Source; use super::source::Source;
use crate::backend::Backend; use crate::backend::Backend;
use crate::widgets::{Navigator, NavigatorScreen}; use crate::widgets::{Navigator, NavigatorScreen};
use gettextrs::gettext;
use glib::clone; use glib::clone;
use gtk::prelude::*; use gtk::prelude::*;
use gtk_macros::get_widget; use gtk_macros::get_widget;
use std::cell::RefCell; use std::cell::RefCell;
use std::path::PathBuf;
use std::rc::Rc; use std::rc::Rc;
/// A dialog for starting to import music. /// A dialog for starting to import music.
@ -49,6 +52,55 @@ impl SourceSelector {
} }
})); }));
folder_button.connect_clicked(clone!(@strong this => move |_| {
let window = this.navigator.borrow().clone().unwrap().window.clone();
let dialog = gtk::FileChooserDialog::new(
Some(&gettext("Select folder")),
Some(&window),
gtk::FileChooserAction::SelectFolder,
&[
(&gettext("Cancel"), gtk::ResponseType::Cancel),
(&gettext("Select"), gtk::ResponseType::Accept),
]);
dialog.connect_response(clone!(@strong this => move |dialog, response| {
this.stack.set_visible_child_name("loading");
dialog.hide();
if let gtk::ResponseType::Accept = response {
if let Some(file) = dialog.get_file() {
if let Some(path) = file.get_path() {
let context = glib::MainContext::default();
let clone = this.clone();
context.spawn_local(async move {
let folder = FolderSource::new(PathBuf::from(path));
match folder.load().await {
Ok(_) => {
let navigator = clone.navigator.borrow().clone();
if let Some(navigator) = navigator {
let source = Rc::new(Box::new(folder) as Box<dyn Source>);
let editor = MediumEditor::new(clone.backend.clone(), source);
navigator.push(editor);
}
clone.info_bar.set_revealed(false);
clone.stack.set_visible_child_name("start");
}
Err(_) => {
// TODO: Present error.
clone.info_bar.set_revealed(true);
clone.stack.set_visible_child_name("start");
}
}
});
}
}
}
}));
dialog.show();
}));
disc_button.connect_clicked(clone!(@strong this => move |_| { disc_button.connect_clicked(clone!(@strong this => move |_| {
this.stack.set_visible_child_name("loading"); this.stack.set_visible_child_name("loading");
@ -69,6 +121,7 @@ impl SourceSelector {
clone.stack.set_visible_child_name("start"); clone.stack.set_visible_child_name("start");
} }
Err(_) => { Err(_) => {
// TODO: Present error.
clone.info_bar.set_revealed(true); clone.info_bar.set_revealed(true);
clone.stack.set_visible_child_name("start"); clone.stack.set_visible_child_name("start");
} }

View file

@ -87,13 +87,11 @@ impl TrackSelector {
} }
})); }));
let title = format!("Track {}", track.number);
let row = libadwaita::ActionRow::new(); let row = libadwaita::ActionRow::new();
row.add_prefix(&check); row.add_prefix(&check);
row.set_activatable_widget(Some(&check)); row.set_activatable_widget(Some(&check));
row.set_activatable(true); row.set_activatable(true);
row.set_title(Some(&title)); row.set_title(Some(&track.name));
track_list.append(&row); track_list.append(&row);
} }

View file

@ -175,9 +175,7 @@ impl TrackSetEditor {
}; };
let tracks = this.source.tracks().unwrap(); let tracks = this.source.tracks().unwrap();
let track_name = &tracks[track.track_source].name;
let number = tracks[track.track_source].number;
let subtitle = format!("Track {}", number);
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();
@ -188,7 +186,7 @@ impl TrackSetEditor {
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(track_name));
row.add_suffix(&edit_button); row.add_suffix(&edit_button);
row.set_activatable_widget(Some(&edit_button)); row.set_activatable_widget(Some(&edit_button));

View file

@ -66,6 +66,7 @@ sources = files(
'editors/work_part.rs', 'editors/work_part.rs',
'editors/work_section.rs', 'editors/work_section.rs',
'import/disc_source.rs', 'import/disc_source.rs',
'import/folder_source.rs',
'import/medium_editor.rs', 'import/medium_editor.rs',
'import/mod.rs', 'import/mod.rs',
'import/source.rs', 'import/source.rs',