From c34e4ad029c119bff8f70a07ccc6bca58b11ea3c Mon Sep 17 00:00:00 2001 From: Caleb Allen Date: Sat, 28 Feb 2026 13:30:36 -0500 Subject: [PATCH] feat(quick-001): add multi-file picker and batch import support - Update PlaylistFilePicker to return []string via OpenMultipleFilesDialog - Add ImportPlaylists method for sequential batch import with partial success --- backend/frontendutil/frontendutil.go | 12 ++++----- backend/playlist/playlist.go | 40 ++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/backend/frontendutil/frontendutil.go b/backend/frontendutil/frontendutil.go index f6ad8b1..ada82a7 100644 --- a/backend/frontendutil/frontendutil.go +++ b/backend/frontendutil/frontendutil.go @@ -40,14 +40,14 @@ func (fe *FrontendUtil) DirectoryPicker() (string, error) { } // PlaylistFilePicker opens a file selection dialog filtered -// to M3U/M3U8 playlist files. +// to M3U/M3U8 playlist files. Multiple files may be selected. func (fe *FrontendUtil) PlaylistFilePicker() ( - string, + []string, error, ) { - runtime.LogInfo(fe.ctx, "selecting a playlist file") + runtime.LogInfo(fe.ctx, "selecting playlist files") - file, err := runtime.OpenFileDialog( + files, err := runtime.OpenMultipleFilesDialog( fe.ctx, runtime.OpenDialogOptions{ Title: "Import Playlist", @@ -60,10 +60,10 @@ func (fe *FrontendUtil) PlaylistFilePicker() ( }, ) if err != nil { - return "", fmt.Errorf( + return nil, fmt.Errorf( "could not open file dialog: %w", err, ) } - return file, nil + return files, nil } diff --git a/backend/playlist/playlist.go b/backend/playlist/playlist.go index e038ccf..51e099a 100644 --- a/backend/playlist/playlist.go +++ b/backend/playlist/playlist.go @@ -793,6 +793,46 @@ func (s *Service) ImportPlaylist( return summary, nil } +// ImportPlaylists imports multiple playlists from external M3U/M3U8 +// files. Each file is imported sequentially using ImportPlaylist. +// Errors from individual imports are collected; partial success is +// possible. Returns the summaries of successfully imported playlists +// and the first error encountered (if any). +func (s *Service) ImportPlaylists( + filePaths []string, +) ([]Summary, error) { + if len(filePaths) == 0 { + return nil, errNoFilePaths + } + + summaries := make([]Summary, 0, len(filePaths)) + + var firstErr error + + for _, fp := range filePaths { + summary, err := s.ImportPlaylist(fp) + if err != nil { + s.logger.Warn( + "Failed to import playlist file", + "path", fp, + "err", err, + ) + + if firstErr == nil { + firstErr = fmt.Errorf( + "import %q failed: %w", fp, err, + ) + } + + continue + } + + summaries = append(summaries, summary) + } + + return summaries, firstErr +} + // RestoreAllPlaylists restores playlist tracks from M3U8 files. // This is called after a full library rescan to repopulate // playlist_tracks from the surviving M3U8 files.