Files
QTransfer/internal/ui/select.go
2026-04-03 21:26:08 +02:00

175 lines
4.3 KiB
Go

package ui
import (
"bufio"
"fmt"
"os"
"sort"
"strconv"
"strings"
"qtransfer/internal/model"
)
func ResolveSelection(lib model.Library, allPlaylists, includeLiked, nonInteractive bool, names []string) ([]model.Playlist, bool, error) {
if allPlaylists || len(names) > 0 || nonInteractive {
selected, liked, err := selectByFlags(lib, allPlaylists, includeLiked, names)
if err != nil {
return nil, false, err
}
if nonInteractive && len(selected) == 0 && !liked {
return nil, false, fmt.Errorf("no selection provided in non-interactive mode (use --all, --playlist, or --liked)")
}
if len(selected) == 0 && !liked && !nonInteractive {
return nil, false, fmt.Errorf("no playlists selected")
}
return selected, liked, nil
}
return interactiveSelection(lib)
}
func selectByFlags(lib model.Library, allPlaylists, includeLiked bool, names []string) ([]model.Playlist, bool, error) {
if allPlaylists {
return lib.Playlists, includeLiked, nil
}
if len(names) == 0 {
return nil, includeLiked, nil
}
lookup := map[string]model.Playlist{}
for _, p := range lib.Playlists {
lookup[strings.ToLower(strings.TrimSpace(p.Name))] = p
}
selected := make([]model.Playlist, 0, len(names))
missing := []string{}
seen := map[string]struct{}{}
for _, n := range names {
k := strings.ToLower(strings.TrimSpace(n))
p, ok := lookup[k]
if !ok {
missing = append(missing, n)
continue
}
if _, exists := seen[p.SourceID]; exists {
continue
}
seen[p.SourceID] = struct{}{}
selected = append(selected, p)
}
if len(missing) > 0 {
return nil, false, fmt.Errorf("playlist(s) not found: %s", strings.Join(missing, ", "))
}
return selected, includeLiked, nil
}
func interactiveSelection(lib model.Library) ([]model.Playlist, bool, error) {
fmt.Println("Fetched Spotify data:")
for i, p := range lib.Playlists {
fmt.Printf(" %2d) %s (%d tracks)\n", i+1, p.Name, len(p.Tracks))
}
fmt.Printf(" L) %s (%d tracks)\n", lib.LikedName, len(lib.LikedSongs))
fmt.Println("\nSelect playlists to transfer. Examples: 1,2,5-8,L or A for all playlists + liked songs")
scanner := bufio.NewScanner(os.Stdin)
for {
fmt.Print("Selection: ")
if !scanner.Scan() {
if scanner.Err() != nil {
return nil, false, scanner.Err()
}
return nil, false, fmt.Errorf("input closed")
}
line := strings.TrimSpace(scanner.Text())
if line == "" {
continue
}
idxs, liked, all, err := parseSelection(line, len(lib.Playlists))
if err != nil {
fmt.Printf("Invalid selection: %v\n", err)
continue
}
if all {
return lib.Playlists, true, nil
}
selected := make([]model.Playlist, 0, len(idxs))
for _, idx := range idxs {
selected = append(selected, lib.Playlists[idx-1])
}
if len(selected) == 0 && !liked {
fmt.Println("No playlists selected. Please choose at least one playlist or L.")
continue
}
return selected, liked, nil
}
}
func parseSelection(s string, max int) ([]int, bool, bool, error) {
parts := strings.Split(strings.ToUpper(s), ",")
set := map[int]struct{}{}
liked := false
all := false
for _, p := range parts {
p = strings.TrimSpace(p)
if p == "" {
continue
}
switch p {
case "A", "ALL":
all = true
liked = true
continue
case "L", "LIKED":
liked = true
continue
}
if strings.Contains(p, "-") {
r := strings.SplitN(p, "-", 2)
if len(r) != 2 {
return nil, false, false, fmt.Errorf("invalid range %q", p)
}
start, err := strconv.Atoi(strings.TrimSpace(r[0]))
if err != nil {
return nil, false, false, fmt.Errorf("invalid range start in %q", p)
}
end, err := strconv.Atoi(strings.TrimSpace(r[1]))
if err != nil {
return nil, false, false, fmt.Errorf("invalid range end in %q", p)
}
if start < 1 || end < 1 || start > max || end > max || end < start {
return nil, false, false, fmt.Errorf("range out of bounds: %q", p)
}
for i := start; i <= end; i++ {
set[i] = struct{}{}
}
continue
}
n, err := strconv.Atoi(p)
if err != nil {
return nil, false, false, fmt.Errorf("invalid token %q", p)
}
if n < 1 || n > max {
return nil, false, false, fmt.Errorf("playlist index out of bounds: %d", n)
}
set[n] = struct{}{}
}
idxs := make([]int, 0, len(set))
for i := range set {
idxs = append(idxs, i)
}
sort.Ints(idxs)
return idxs, liked, all, nil
}