refactor(scan): move ParseTargets function to model package

This commit is contained in:
deluan 2025-11-13 11:55:41 -05:00
parent a2d48e5138
commit f1f4615966
5 changed files with 56 additions and 57 deletions

View File

@ -76,7 +76,7 @@ func runScanner(ctx context.Context) {
var scanTargets []model.ScanTarget
if targets != "" {
var err error
scanTargets, err = scanner.ParseTargets(strings.Split(targets, ","))
scanTargets, err = model.ParseTargets(strings.Split(targets, ","))
if err != nil {
log.Fatal(ctx, "Failed to parse targets", err)
}

View File

@ -3,6 +3,8 @@ package model
import (
"context"
"fmt"
"strconv"
"strings"
"time"
)
@ -36,3 +38,44 @@ type Scanner interface {
ScanFolders(ctx context.Context, fullScan bool, targets []ScanTarget) (warnings []string, err error)
Status(context.Context) (*ScannerStatus, error)
}
// ParseTargets parses scan targets strings into ScanTarget structs.
// Example: []string{"1:Music/Rock", "2:Classical"}
func ParseTargets(libFolders []string) ([]ScanTarget, error) {
targets := make([]ScanTarget, 0, len(libFolders))
for _, part := range libFolders {
part = strings.TrimSpace(part)
if part == "" {
continue
}
// Split by the first colon
colonIdx := strings.Index(part, ":")
if colonIdx == -1 {
return nil, fmt.Errorf("invalid target format: %q (expected libraryID:folderPath)", part)
}
libIDStr := part[:colonIdx]
folderPath := part[colonIdx+1:]
libID, err := strconv.Atoi(libIDStr)
if err != nil {
return nil, fmt.Errorf("invalid library ID %q: %w", libIDStr, err)
}
if libID <= 0 {
return nil, fmt.Errorf("invalid library ID %q", libIDStr)
}
targets = append(targets, ScanTarget{
LibraryID: libID,
FolderPath: folderPath,
})
}
if len(targets) == 0 {
return nil, fmt.Errorf("no valid targets found")
}
return targets, nil
}

View File

@ -4,8 +4,6 @@ import (
"context"
"errors"
"fmt"
"strconv"
"strings"
"sync/atomic"
"time"
@ -28,47 +26,6 @@ var (
ErrAlreadyScanning = errors.New("already scanning")
)
// ParseTargets parses scan targets strings into ScanTarget structs.
// Example: []string{"1:Music/Rock", "2:Classical"}
func ParseTargets(libFolders []string) ([]model.ScanTarget, error) {
targets := make([]model.ScanTarget, 0, len(libFolders))
for _, part := range libFolders {
part = strings.TrimSpace(part)
if part == "" {
continue
}
// Split by the first colon
colonIdx := strings.Index(part, ":")
if colonIdx == -1 {
return nil, fmt.Errorf("invalid target format: %q (expected libraryID:folderPath)", part)
}
libIDStr := part[:colonIdx]
folderPath := part[colonIdx+1:]
libID, err := strconv.Atoi(libIDStr)
if err != nil {
return nil, fmt.Errorf("invalid library ID %q: %w", libIDStr, err)
}
if libID <= 0 {
return nil, fmt.Errorf("invalid library ID %q", libIDStr)
}
targets = append(targets, model.ScanTarget{
LibraryID: libID,
FolderPath: folderPath,
})
}
if len(targets) == 0 {
return nil, fmt.Errorf("no valid targets found")
}
return targets, nil
}
func New(rootCtx context.Context, ds model.DataStore, cw artwork.CacheWarmer, broker events.Broker,
pls core.Playlists, m metrics.Metrics) model.Scanner {
c := &controller{

View File

@ -57,7 +57,7 @@ var _ = Describe("Controller", func() {
var _ = Describe("ParseTargets", func() {
It("parses multiple entries in slice", func() {
targets, err := scanner.ParseTargets([]string{"1:Music/Rock", "1:Music/Jazz", "2:Classical"})
targets, err := model.ParseTargets([]string{"1:Music/Rock", "1:Music/Jazz", "2:Classical"})
Expect(err).ToNot(HaveOccurred())
Expect(targets).To(HaveLen(3))
Expect(targets[0].LibraryID).To(Equal(1))
@ -69,7 +69,7 @@ var _ = Describe("ParseTargets", func() {
})
It("handles empty folder paths", func() {
targets, err := scanner.ParseTargets([]string{"1:", "2:"})
targets, err := model.ParseTargets([]string{"1:", "2:"})
Expect(err).ToNot(HaveOccurred())
Expect(targets).To(HaveLen(2))
Expect(targets[0].FolderPath).To(Equal(""))
@ -77,7 +77,7 @@ var _ = Describe("ParseTargets", func() {
})
It("trims whitespace from entries", func() {
targets, err := scanner.ParseTargets([]string{" 1:Music/Rock", " 2:Classical "})
targets, err := model.ParseTargets([]string{" 1:Music/Rock", " 2:Classical "})
Expect(err).ToNot(HaveOccurred())
Expect(targets).To(HaveLen(2))
Expect(targets[0].LibraryID).To(Equal(1))
@ -87,13 +87,13 @@ var _ = Describe("ParseTargets", func() {
})
It("skips empty strings", func() {
targets, err := scanner.ParseTargets([]string{"1:Music/Rock", "", "2:Classical"})
targets, err := model.ParseTargets([]string{"1:Music/Rock", "", "2:Classical"})
Expect(err).ToNot(HaveOccurred())
Expect(targets).To(HaveLen(2))
})
It("handles paths with colons", func() {
targets, err := scanner.ParseTargets([]string{"1:C:/Music/Rock", "2:/path:with:colons"})
targets, err := model.ParseTargets([]string{"1:C:/Music/Rock", "2:/path:with:colons"})
Expect(err).ToNot(HaveOccurred())
Expect(targets).To(HaveLen(2))
Expect(targets[0].FolderPath).To(Equal("C:/Music/Rock"))
@ -101,37 +101,37 @@ var _ = Describe("ParseTargets", func() {
})
It("returns error for invalid format without colon", func() {
_, err := scanner.ParseTargets([]string{"1Music/Rock"})
_, err := model.ParseTargets([]string{"1Music/Rock"})
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("invalid target format"))
})
It("returns error for non-numeric library ID", func() {
_, err := scanner.ParseTargets([]string{"abc:Music/Rock"})
_, err := model.ParseTargets([]string{"abc:Music/Rock"})
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("invalid library ID"))
})
It("returns error for negative library ID", func() {
_, err := scanner.ParseTargets([]string{"-1:Music/Rock"})
_, err := model.ParseTargets([]string{"-1:Music/Rock"})
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("invalid library ID"))
})
It("returns error for zero library ID", func() {
_, err := scanner.ParseTargets([]string{"0:Music/Rock"})
_, err := model.ParseTargets([]string{"0:Music/Rock"})
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("invalid library ID"))
})
It("returns error for empty input", func() {
_, err := scanner.ParseTargets([]string{})
_, err := model.ParseTargets([]string{})
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("no valid targets found"))
})
It("returns error for all empty strings", func() {
_, err := scanner.ParseTargets([]string{"", " ", ""})
_, err := model.ParseTargets([]string{"", " ", ""})
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("no valid targets found"))
})

View File

@ -9,7 +9,6 @@ import (
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/model/request"
"github.com/navidrome/navidrome/scanner"
"github.com/navidrome/navidrome/server/subsonic/responses"
"github.com/navidrome/navidrome/utils/req"
)
@ -51,7 +50,7 @@ func (api *Router) StartScan(r *http.Request) (*responses.Subsonic, error) {
// Parse optional target parameters for selective scanning
var targets []model.ScanTarget
if targetParams, err := p.Strings("target"); err == nil && len(targetParams) > 0 {
targets, err = scanner.ParseTargets(targetParams)
targets, err = model.ParseTargets(targetParams)
if err != nil {
return nil, newError(responses.ErrorGeneric, fmt.Sprintf("Invalid target parameter: %v", err))
}