From 740ad0cf0bc412822a7507613ab8b9f411c3a3b4 Mon Sep 17 00:00:00 2001 From: Elias Projahn Date: Sun, 16 Feb 2025 16:30:35 +0100 Subject: [PATCH] Implement tracks import --- src/editor/tracks_editor.rs | 52 +++++++++-- src/editor/tracks_editor_track_row.rs | 19 ++-- src/library.rs | 128 +++++++++++++++++++++++++- 3 files changed, 181 insertions(+), 18 deletions(-) diff --git a/src/editor/tracks_editor.rs b/src/editor/tracks_editor.rs index ea9dbb2..8002aaa 100644 --- a/src/editor/tracks_editor.rs +++ b/src/editor/tracks_editor.rs @@ -1,6 +1,6 @@ -use super::tracks_editor_track_row::{PathType, TracksEditorTrackData}; +use super::tracks_editor_track_row::{TrackLocation, TracksEditorTrackData}; use crate::{ - db::models::{Recording, Work}, + db::models::{Recording, Track, Work}, editor::{ recording_editor::MusicusRecordingEditor, recording_selector_popover::RecordingSelectorPopover, @@ -37,6 +37,7 @@ mod imp { pub recording: RefCell>, pub recordings_popover: OnceCell, pub track_rows: RefCell>, + pub removed_tracks: RefCell>, #[template_child] pub recording_row: TemplateChild, @@ -196,6 +197,9 @@ impl TracksEditor { self.imp().track_list.remove(&track_row); } + // Forget previously removed tracks (see above). + self.imp().removed_tracks.borrow_mut().clear(); + let tracks = self .library() .tracks_for_recording(&recording.recording_id) @@ -208,8 +212,7 @@ impl TracksEditor { self.add_track_row( recording.clone(), TracksEditorTrackData { - track_id: Some(track.track_id), - path: PathType::Library(track.path), + location: TrackLocation::Library(track.clone()), parts: track.works, }, ); @@ -246,8 +249,7 @@ impl TracksEditor { self.add_track_row( recording.to_owned(), TracksEditorTrackData { - track_id: None, - path: PathType::System(path), + location: TrackLocation::System(path), parts: next_part, }, ); @@ -264,6 +266,10 @@ impl TracksEditor { #[weak(rename_to = this)] self, move |row| { + if let TrackLocation::Library(track) = row.track_data().location { + this.imp().removed_tracks.borrow_mut().push(track); + } + this.imp().track_list.remove(row); this.imp().track_rows.borrow_mut().retain(|p| p != row); } @@ -278,7 +284,39 @@ impl TracksEditor { #[template_callback] fn save(&self) { - // TODO + for track in self.imp().removed_tracks.borrow_mut().drain(..) { + self.library().delete_track(&track).unwrap(); + } + + for (index, track_row) in self.imp().track_rows.borrow_mut().drain(..).enumerate() { + let track_data = track_row.track_data(); + + match track_data.location { + TrackLocation::Undefined => { + log::error!("Failed to save track: Undefined track location."); + } + TrackLocation::Library(track) => self + .library() + .update_track(&track.track_id, index as i32, track_data.parts) + .unwrap(), + TrackLocation::System(path) => { + if let Some(recording) = &*self.imp().recording.borrow() { + self.library() + .import_track( + &path, + &recording.recording_id, + index as i32, + track_data.parts, + ) + .unwrap(); + } else { + log::error!("Failed to save track: No recording set."); + } + } + } + + self.imp().track_list.remove(&track_row); + } self.navigation().pop(); } diff --git a/src/editor/tracks_editor_track_row.rs b/src/editor/tracks_editor_track_row.rs index 2a24d2b..60125b1 100644 --- a/src/editor/tracks_editor_track_row.rs +++ b/src/editor/tracks_editor_track_row.rs @@ -1,5 +1,5 @@ use crate::{ - db::models::{Recording, Work}, + db::models::{Recording, Track, Work}, editor::tracks_editor_parts_popover::TracksEditorPartsPopover, library::MusicusLibrary, }; @@ -90,10 +90,10 @@ impl TracksEditorTrackRow { obj.set_activatable(!recording.work.parts.is_empty()); - obj.set_subtitle(&match &track_data.path { - PathType::None => String::new(), - PathType::Library(path) => path.to_owned(), - PathType::System(path) => { + obj.set_subtitle(&match &track_data.location { + TrackLocation::Undefined => String::new(), + TrackLocation::Library(track) => track.path.clone(), + TrackLocation::System(path) => { let format_string = gettext("Import from {}"); let file_name = path.file_name().unwrap().to_str().unwrap(); match formatx!(&format_string, file_name) { @@ -178,15 +178,14 @@ impl TracksEditorTrackRow { #[derive(Clone, Default, Debug)] pub struct TracksEditorTrackData { - pub track_id: Option, - pub path: PathType, + pub location: TrackLocation, pub parts: Vec, } #[derive(Clone, Default, Debug)] -pub enum PathType { +pub enum TrackLocation { #[default] - None, - Library(String), + Undefined, + Library(Track), System(PathBuf), } diff --git a/src/library.rs b/src/library.rs index ddc3651..68628ec 100644 --- a/src/library.rs +++ b/src/library.rs @@ -8,13 +8,15 @@ use adw::{ prelude::*, subclass::prelude::*, }; -use anyhow::Result; +use anyhow::{anyhow, Result}; use chrono::prelude::*; use diesel::{dsl::exists, prelude::*, QueryDsl, SqliteConnection}; use once_cell::sync::Lazy; use std::{ cell::{OnceCell, RefCell}, + ffi::OsString, + fs, path::{Path, PathBuf}, }; @@ -1232,6 +1234,130 @@ impl MusicusLibrary { Ok(()) } + /// Import a track into the music library. + // TODO: Support mediums, think about albums. + pub fn import_track( + &self, + path: impl AsRef, + recording_id: &str, + recording_index: i32, + works: Vec, + ) -> Result<()> { + let mut binding = self.imp().connection.borrow_mut(); + let connection = &mut *binding.as_mut().unwrap(); + + let track_id = db::generate_id(); + let now = Local::now().naive_local(); + + // TODO: Human interpretable filenames? + let mut filename = OsString::from(recording_id); + filename.push("_"); + filename.push(OsString::from(format!("{recording_index:02}"))); + if let Some(extension) = path.as_ref().extension() { + filename.push("."); + filename.push(extension); + }; + + let mut to_path = PathBuf::from(self.folder()); + to_path.push(&filename); + let library_path = filename + .into_string() + .or(Err(anyhow!("Filename contains invalid Unicode.")))?; + + fs::copy(path, to_path)?; + + let track_data = tables::Track { + track_id: track_id.clone(), + recording_id: recording_id.to_owned(), + recording_index, + medium_id: None, + medium_index: None, + path: library_path, + created_at: now, + edited_at: now, + last_used_at: now, + last_played_at: None, + }; + + diesel::insert_into(tracks::table) + .values(&track_data) + .execute(connection)?; + + for (index, work) in works.into_iter().enumerate() { + let track_work_data = tables::TrackWork { + track_id: track_id.clone(), + work_id: work.work_id, + sequence_number: index as i32, + }; + + diesel::insert_into(track_works::table) + .values(&track_work_data) + .execute(connection)?; + } + + Ok(()) + } + + // TODO: Support mediums, think about albums. + pub fn delete_track(&self, track: &Track) -> Result<()> { + let mut binding = self.imp().connection.borrow_mut(); + let connection = &mut *binding.as_mut().unwrap(); + + diesel::delete(track_works::table) + .filter(track_works::track_id.eq(&track.track_id)) + .execute(connection)?; + + diesel::delete(tracks::table) + .filter(tracks::track_id.eq(&track.track_id)) + .execute(connection)?; + + let mut path = PathBuf::from(self.folder()); + path.push(&track.path); + fs::remove_file(path)?; + + Ok(()) + } + + // TODO: Support mediums, think about albums. + pub fn update_track( + &self, + track_id: &str, + recording_index: i32, + works: Vec, + ) -> Result<()> { + let mut binding = self.imp().connection.borrow_mut(); + let connection = &mut *binding.as_mut().unwrap(); + + let now = Local::now().naive_local(); + + diesel::update(tracks::table) + .filter(tracks::track_id.eq(track_id.to_owned())) + .set(( + tracks::recording_index.eq(recording_index), + tracks::edited_at.eq(now), + tracks::last_used_at.eq(now), + )) + .execute(connection)?; + + diesel::delete(track_works::table) + .filter(track_works::track_id.eq(track_id)) + .execute(connection)?; + + for (index, work) in works.into_iter().enumerate() { + let track_work_data = tables::TrackWork { + track_id: track_id.to_owned(), + work_id: work.work_id, + sequence_number: index as i32, + }; + + diesel::insert_into(track_works::table) + .values(&track_work_data) + .execute(connection)?; + } + + Ok(()) + } + pub fn connect_changed(&self, f: F) -> glib::SignalHandlerId { self.connect_local("changed", true, move |values| { let obj = values[0].get::().unwrap();