mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 19:57:25 +01:00
Add folder source to import
This commit is contained in:
parent
030eccf253
commit
357a4a4429
8 changed files with 156 additions and 7 deletions
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
90
src/import/folder_source.rs
Normal file
90
src/import/folder_source.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue