From 109e17e5e49244465631faecaa196ae57639e6e5 Mon Sep 17 00:00:00 2001 From: Elias Projahn Date: Sun, 30 Mar 2025 11:22:50 +0200 Subject: [PATCH] Implement deleting recordings --- data/ui/recording_tile.blp | 1 + src/library.rs | 40 +++++++++++++++++++++++++++++++++++++- src/recording_tile.rs | 37 ++++++++++++++++++++++++++++++++--- 3 files changed, 74 insertions(+), 4 deletions(-) diff --git a/data/ui/recording_tile.blp b/data/ui/recording_tile.blp index 405ee74..6f4e4ad 100644 --- a/data/ui/recording_tile.blp +++ b/data/ui/recording_tile.blp @@ -69,4 +69,5 @@ menu edit_menu { item (_("_Add to playlist"), "recording.add-to-playlist") item (_("Edit _recording"), "recording.edit-recording") item (_("Edit _tracks"), "recording.edit-tracks") + item (_("_Delete from library"), "recording.delete") } diff --git a/src/library.rs b/src/library.rs index 8d709a8..6bba536 100644 --- a/src/library.rs +++ b/src/library.rs @@ -13,7 +13,7 @@ use adw::{ prelude::*, subclass::prelude::*, }; -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, Context, Error, Result}; use chrono::prelude::*; use diesel::{dsl::exists, prelude::*, sql_types, QueryDsl, SqliteConnection}; use formatx::formatx; @@ -1513,6 +1513,44 @@ impl Library { Ok(()) } + pub fn delete_recording_and_tracks(&self, recording_id: &str) -> Result<()> { + let connection = &mut *self.imp().connection.get().unwrap().lock().unwrap(); + + let tracks = tracks::table + .filter(tracks::recording_id.eq(recording_id)) + .load::(connection)?; + + // Delete from library first to avoid orphan tracks in case of file + // system related errors. + + connection.transaction::<(), Error, _>(|connection| { + for track in &tracks { + 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)?; + } + + diesel::delete(recordings::table) + .filter(recordings::recording_id.eq(recording_id)) + .execute(connection)?; + + Ok(()) + })?; + + let library_path = PathBuf::from(self.folder()); + for track in tracks { + fs::remove_file(library_path.join(&track.path))?; + } + + self.changed(); + + Ok(()) + } + pub fn create_album( &self, name: TranslatedString, diff --git a/src/recording_tile.rs b/src/recording_tile.rs index 844103c..8ff1373 100644 --- a/src/recording_tile.rs +++ b/src/recording_tile.rs @@ -1,7 +1,8 @@ use std::cell::OnceCell; +use adw::prelude::*; use gettextrs::gettext; -use gtk::{gio, glib, prelude::*, subclass::prelude::*}; +use gtk::{gio, glib, subclass::prelude::*}; use crate::{ db::models::Recording, editor::recording::RecordingEditor, library::Library, player::Player, @@ -9,7 +10,7 @@ use crate::{ mod imp { use super::*; - use crate::editor::tracks::TracksEditor; + use crate::{editor::tracks::TracksEditor, util}; #[derive(Debug, Default, gtk::CompositeTemplate)] #[template(file = "data/ui/recording_tile.blp")] @@ -85,8 +86,38 @@ mod imp { }) .build(); + let obj = self.obj().to_owned(); + let delete_action = gio::ActionEntry::builder("delete") + .activate(move |_, _, _| { + let dialog = adw::AlertDialog::builder() + .heading(&gettext("Delete recording?")) + .body(&gettext("The recording will be removed from your music library and the corresponding audio files will be deleted. This action cannot be undone.")) + .build(); + + dialog.add_response("delete", &gettext("Delete")); + dialog.set_response_appearance("delete", adw::ResponseAppearance::Destructive); + dialog.add_response("cancel", &gettext("Cancel")); + dialog.set_default_response(Some("cancel")); + dialog.set_close_response("cancel"); + + let obj = obj.clone(); + glib::spawn_future_local(async move { + if dialog.choose_future(&obj).await == "delete" { + if let Err(err) = obj.imp().library.get().unwrap().delete_recording_and_tracks(&obj.recording().recording_id) { + util::error_toast("Failed to delete recording", err, obj.imp().toast_overlay.get().unwrap()); + } + } + }); + }) + .build(); + let actions = gio::SimpleActionGroup::new(); - actions.add_action_entries([append_action, edit_recording_action, edit_tracks_action]); + actions.add_action_entries([ + append_action, + edit_recording_action, + edit_tracks_action, + delete_action, + ]); self.obj().insert_action_group("recording", Some(&actions)); } }