This commit is contained in:
@@ -122,13 +122,13 @@ func (c *LibraryController) Scan(w http.ResponseWriter, r *http.Request) {
|
|||||||
c.scanMutex.Unlock()
|
c.scanMutex.Unlock()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err := c.service.Scan(id)
|
report, err := c.service.Scan(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Scan failed: %v", err)
|
log.Printf("Scan failed for library %d: %v", id, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Scan completed successfully for library %d", id)
|
log.Printf("Scan completed for library %d: %+v", id, report)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
jsonMsg(w, "Library scan started.")
|
jsonMsg(w, "Library scan started.")
|
||||||
|
|||||||
@@ -3,9 +3,21 @@ package service
|
|||||||
import (
|
import (
|
||||||
"butterfliu/internal/repository"
|
"butterfliu/internal/repository"
|
||||||
"butterfliu/internal/scanner"
|
"butterfliu/internal/scanner"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ScanReport holds the result of a library scan.
|
||||||
|
type ScanReport struct {
|
||||||
|
TotalFiles int
|
||||||
|
Added int
|
||||||
|
Skipped int
|
||||||
|
FailedFiles []string
|
||||||
|
}
|
||||||
|
|
||||||
type LibraryService struct {
|
type LibraryService struct {
|
||||||
repo *repository.LibraryRepository
|
repo *repository.LibraryRepository
|
||||||
}
|
}
|
||||||
@@ -23,14 +35,26 @@ func (s *LibraryService) GetByID(id int) (repository.Library, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *LibraryService) Create(name, path string) (repository.Library, error) {
|
func (s *LibraryService) Create(name, path string) (repository.Library, error) {
|
||||||
|
if name = strings.TrimSpace(name); name == "" {
|
||||||
|
return repository.Library{}, errors.New("library name cannot be empty")
|
||||||
|
}
|
||||||
|
if path = strings.TrimSpace(path); path == "" {
|
||||||
|
return repository.Library{}, errors.New("library path cannot be empty")
|
||||||
|
}
|
||||||
return s.repo.Create(name, path)
|
return s.repo.Create(name, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LibraryService) UpdateName(id int, name string) error {
|
func (s *LibraryService) UpdateName(id int, name string) error {
|
||||||
|
if name = strings.TrimSpace(name); name == "" {
|
||||||
|
return errors.New("library name cannot be empty")
|
||||||
|
}
|
||||||
return s.repo.UpdateName(id, name)
|
return s.repo.UpdateName(id, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LibraryService) UpdatePath(id int, path string) error {
|
func (s *LibraryService) UpdatePath(id int, path string) error {
|
||||||
|
if path = strings.TrimSpace(path); path == "" {
|
||||||
|
return errors.New("library path cannot be empty")
|
||||||
|
}
|
||||||
return s.repo.UpdatePath(id, path)
|
return s.repo.UpdatePath(id, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,53 +74,75 @@ func (s *LibraryService) GetAlbums() ([]repository.Album, error) {
|
|||||||
return s.repo.GetAlbums()
|
return s.repo.GetAlbums()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LibraryService) Scan(id int) error {
|
func (s *LibraryService) Scan(id int) (*ScanReport, error) {
|
||||||
lib, err := s.repo.GetByID(id)
|
lib, err := s.repo.GetByID(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, fmt.Errorf("library %d not found: %w", id, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
scannedSongs, err := scanner.ScanDirectory(lib.Path)
|
scannedSongs, err := scanner.ScanDirectory(lib.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, fmt.Errorf("scan directory %s: %w", lib.Path, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Scanned songs:", len(scannedSongs))
|
report := &ScanReport{TotalFiles: len(scannedSongs)}
|
||||||
|
|
||||||
for _, song := range scannedSongs {
|
for _, song := range scannedSongs {
|
||||||
mediaFile, err := s.repo.GetMediaFileByPath(song.Path)
|
if err := s.addScannedSong(song, lib.ID, report); err != nil {
|
||||||
if err != nil {
|
log.Printf("Failed to add %s: %v", filepath.Base(song.Path), err)
|
||||||
mediaFile, err = s.repo.CreateMediaFile(song.Path, id)
|
report.FailedFiles = append(report.FailedFiles, song.Path)
|
||||||
if err != nil {
|
|
||||||
log.Println("Error adding media file:", song.Path, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
artist, err := s.repo.GetArtistByName(song.Artist)
|
|
||||||
if err != nil {
|
|
||||||
artist, err = s.repo.CreateArtist(song.Artist)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Error adding artist:", song.Artist, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
album, err := s.repo.GetAlbumByTitleAndArtist(song.Album, artist.ID)
|
|
||||||
if err != nil {
|
|
||||||
album, err = s.repo.CreateAlbum(song.Album, artist.ID)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Error adding album:", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = s.repo.CreateSong(song.Title, artist.ID, album.ID, song.Duration, mediaFile.ID)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Song already exists or error adding: %s - %v", song.Title, err)
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Printf("Scan complete for library %d: %+v", id, report)
|
||||||
|
return report, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addScannedSong upserts a scanned song and its related artist, album, and media file.
|
||||||
|
func (s *LibraryService) addScannedSong(song scanner.ScannedSong, libraryID int, report *ScanReport) error {
|
||||||
|
mediaFile, err := s.getOrCreateMediaFile(song.Path, libraryID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("media file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
artist, err := s.getOrCreateArtist(song.Artist)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("artist: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
album, err := s.getOrCreateAlbum(song.Album, artist.ID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("album: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := s.repo.CreateSong(song.Title, artist.ID, album.ID, song.Duration, mediaFile.ID); err != nil {
|
||||||
|
return fmt.Errorf("song: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
report.Added++
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *LibraryService) getOrCreateMediaFile(path string, libraryID int) (repository.MediaFile, error) {
|
||||||
|
mf, err := s.repo.GetMediaFileByPath(path)
|
||||||
|
if err == nil {
|
||||||
|
return mf, nil
|
||||||
|
}
|
||||||
|
return s.repo.CreateMediaFile(path, libraryID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LibraryService) getOrCreateArtist(name string) (repository.Artist, error) {
|
||||||
|
a, err := s.repo.GetArtistByName(name)
|
||||||
|
if err == nil {
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
return s.repo.CreateArtist(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LibraryService) getOrCreateAlbum(title string, artistID int) (repository.Album, error) {
|
||||||
|
al, err := s.repo.GetAlbumByTitleAndArtist(title, artistID)
|
||||||
|
if err == nil {
|
||||||
|
return al, nil
|
||||||
|
}
|
||||||
|
return s.repo.CreateAlbum(title, artistID)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import "butterfliu/internal/repository"
|
import (
|
||||||
|
"butterfliu/internal/repository"
|
||||||
|
)
|
||||||
|
|
||||||
type SongService struct {
|
type SongService struct {
|
||||||
songRepo *repository.SongRepository
|
songRepo *repository.SongRepository
|
||||||
@@ -8,25 +10,21 @@ type SongService struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewSongService(songRepo *repository.SongRepository, mediaRepo *repository.MediaRepository) *SongService {
|
func NewSongService(songRepo *repository.SongRepository, mediaRepo *repository.MediaRepository) *SongService {
|
||||||
return &SongService{songRepo, mediaRepo}
|
return &SongService{
|
||||||
}
|
songRepo: songRepo,
|
||||||
|
mediaRepo: mediaRepo,
|
||||||
func (s *SongService) GetAll() ([]repository.Song, error) {
|
}
|
||||||
return s.songRepo.GetAll()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SongService) GetAllWithDetails() ([]repository.SongDetail, error) {
|
func (s *SongService) GetAllWithDetails() ([]repository.SongDetail, error) {
|
||||||
return s.songRepo.GetAllWithDetails()
|
return s.songRepo.GetAllWithDetails()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service *SongService) GetMediaFile(id int) (repository.MediaFile, error) {
|
// GetMediaFilePath returns the file path of a song by its song ID.
|
||||||
song, err := service.songRepo.Get(id)
|
func (s *SongService) GetMediaFile(id int) (repository.MediaFile, error) {
|
||||||
|
song, err := s.songRepo.Get(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return repository.MediaFile{}, err
|
return repository.MediaFile{}, err
|
||||||
}
|
}
|
||||||
media, err := service.mediaRepo.Get(song.MediaFileID)
|
return s.mediaRepo.Get(song.MediaFileID)
|
||||||
if err != nil {
|
|
||||||
return repository.MediaFile{}, err
|
|
||||||
}
|
|
||||||
return media, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user