feat: delete song from disk via edit-metadata modal

- /api/delete-song endpoint: resolves path via same fallback chain as
  edit-metadata, validates file is inside music root + is an audio file,
  deletes it, then prunes the album folder if empty and the artist folder
  if that too becomes empty — no recursive rm, depth strictly limited to
  two levels above the file so no accidental mass-deletes
- Safety: path.resolve() guard ensures no path-traversal outside music root;
  rmdirSync (non-recursive) fails if dir is non-empty as extra safety net
- Frontend: delete button (red outline, trash icon) at bottom of edit mode
  with confirmation dialog showing title + folder-cleanup warning

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-05-29 00:36:31 +02:00
1 parent 0788c801fa
commit a2714dddaa
4 files changed
+118 -2

No files matched your search

+2 -2
View File
@@ -45,7 +45,7 @@ import {
toggleMobileSearch, closeMobileSearch, switchSetupTab,
toggleNotifPanel, deleteNotif, clearNotifs, initNotifBell, _notifNavigate,
_pickTheme,
toggleInfoEdit, onInfoArtFile, autofillMusicBrainz, autofillItunes, saveMetadata,
toggleInfoEdit, onInfoArtFile, autofillMusicBrainz, autofillItunes, saveMetadata, deleteSong,
} from './ui.js';
// ─── CONNECT ───
@@ -316,7 +316,7 @@ Object.assign(window, {
loadSoundCloudImporter, resolveSoundCloudLink, clearSoundCloudPreview, scDlAll, scDlSingle, scDlTrack,
executeSpotifyDownload, resolveYouTubeLink, executeYouTubeDownload,
openPlModal, closePlModal, addSongsToPlaylist, createAndAddPlaylist, createPlaylistPrompt,
showSongInfo, closeInfoModal, toggleInfoEdit, onInfoArtFile, autofillMusicBrainz, autofillItunes, saveMetadata,
showSongInfo, closeInfoModal, toggleInfoEdit, onInfoArtFile, autofillMusicBrainz, autofillItunes, saveMetadata, deleteSong,
openDlModal, closeDlModal, selectDlType, executeDownload,
openEditPlModal, closeEditPlModal, saveEditPlaylist,
editPlUploadCover, editPlCoverSelected, editPlRemoveCover, deletePlaylist, _confirmOk, _confirmCancel,
+26
View File
@@ -515,6 +515,32 @@ export async function saveMetadata() {
}
}
export async function deleteSong() {
if (!S.cfg.backendUrl) { toast('⚠ Kein Backend konfiguriert'); return; }
const s = S.infoSong;
if (!s?.id) return;
const ok = await showConfirm(
'Song löschen',
`"${s.title}" wird unwiderruflich von der Festplatte gelöscht. Leere Album- und Künstlerordner werden ebenfalls entfernt.`,
'Löschen'
);
if (!ok) return;
const status = document.getElementById('info-edit-status');
status.textContent = '🗑 Löschen…';
try {
const r = await fetch(`${S.cfg.backendUrl}/api/delete-song`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ndUrl: S.cfg.url, user: S.cfg.user, pass: S.cfg.pass, songId: s.id }),
});
const d = await r.json();
if (!r.ok) throw new Error(d.error || `HTTP ${r.status}`);
toast(`🗑 "${s.title}" gelöscht`);
closeInfoModal();
} catch (e) {
status.textContent = '❌ Fehler: ' + e.message;
}
}
// ─── DOWNLOAD MODAL ───
export function openDlModal(track) {
if(!S.cfg.backendUrl) { toast('⚠ Kein Backend konfiguriert. Bitte in Einstellungen eintragen.'); return; }