db992a9941
Making StarredAt more "precise", setting it to the time the change was detected. getStarred.view now returns albums ordered by StarredAt. loadRange method now take QueryOptions.Desc into account
307 lines
7.1 KiB
Go
307 lines
7.1 KiB
Go
package scanner
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/astaxie/beego"
|
|
"github.com/deluan/gosonic/consts"
|
|
"github.com/deluan/gosonic/domain"
|
|
"github.com/deluan/gosonic/engine"
|
|
"github.com/deluan/gosonic/utils"
|
|
)
|
|
|
|
type Scanner interface {
|
|
ScanLibrary(lastModifiedSince time.Time, path string) (int, error)
|
|
MediaFiles() map[string]*domain.MediaFile
|
|
Albums() map[string]*domain.Album
|
|
Artists() map[string]*domain.Artist
|
|
Playlists() map[string]*domain.Playlist
|
|
}
|
|
|
|
type tempIndex map[string]domain.ArtistInfo
|
|
|
|
var (
|
|
inProgress chan int
|
|
lastCheck time.Time
|
|
itunesLibrary string
|
|
)
|
|
|
|
func init() {
|
|
inProgress = make(chan int)
|
|
go func() {
|
|
time.Sleep(5 * time.Second)
|
|
startImport()
|
|
}()
|
|
}
|
|
|
|
func CheckForUpdates(force bool) {
|
|
<-inProgress
|
|
|
|
if force {
|
|
lastCheck = time.Time{}
|
|
}
|
|
|
|
startImport()
|
|
}
|
|
|
|
func startImport() {
|
|
go func() {
|
|
itunesLibrary = beego.AppConfig.String("musicFolder")
|
|
|
|
info, err := os.Stat(itunesLibrary)
|
|
if err != nil {
|
|
inProgress <- 1
|
|
beego.Error(err)
|
|
return
|
|
}
|
|
if lastCheck.After(info.ModTime()) {
|
|
inProgress <- 1
|
|
return
|
|
}
|
|
lastCheck = time.Now()
|
|
|
|
// TODO Move all to DI
|
|
i := &Importer{mediaFolder: beego.AppConfig.String("musicFolder")}
|
|
utils.ResolveDependencies(&i.mfRepo, &i.albumRepo, &i.artistRepo, &i.idxRepo, &i.plsRepo,
|
|
&i.propertyRepo, &i.search, &i.scanner)
|
|
i.Run()
|
|
inProgress <- 1
|
|
}()
|
|
}
|
|
|
|
type Importer struct {
|
|
scanner Scanner
|
|
mediaFolder string
|
|
mfRepo domain.MediaFileRepository
|
|
albumRepo domain.AlbumRepository
|
|
artistRepo domain.ArtistRepository
|
|
idxRepo domain.ArtistIndexRepository
|
|
plsRepo domain.PlaylistRepository
|
|
propertyRepo engine.PropertyRepository
|
|
search engine.Search
|
|
lastScan time.Time
|
|
}
|
|
|
|
func (i *Importer) Run() {
|
|
i.lastScan = i.lastModifiedSince()
|
|
|
|
if i.lastScan.IsZero() {
|
|
beego.Info("Starting first iTunes Library scan. This can take a while...")
|
|
}
|
|
|
|
total, err := i.scanner.ScanLibrary(i.lastScan, i.mediaFolder)
|
|
if err != nil {
|
|
beego.Error("Error importing iTunes Library:", err)
|
|
return
|
|
}
|
|
|
|
beego.Debug("Found", total, "tracks,",
|
|
len(i.scanner.MediaFiles()), "songs,",
|
|
len(i.scanner.Albums()), "albums,",
|
|
len(i.scanner.Artists()), "artists",
|
|
len(i.scanner.Playlists()), "playlists")
|
|
|
|
if err := i.importLibrary(); err != nil {
|
|
beego.Error("Error persisting data:", err)
|
|
}
|
|
if i.lastScan.IsZero() {
|
|
beego.Info("Finished first iTunes Library import")
|
|
} else {
|
|
beego.Debug("Finished updating tracks from iTunes Library")
|
|
}
|
|
}
|
|
|
|
func (i *Importer) lastModifiedSince() time.Time {
|
|
ms, err := i.propertyRepo.Get(consts.LastScan)
|
|
if err != nil {
|
|
beego.Warn("Couldn't read LastScan:", err)
|
|
return time.Time{}
|
|
}
|
|
if ms == "" {
|
|
beego.Debug("First scan")
|
|
return time.Time{}
|
|
}
|
|
s, _ := strconv.ParseInt(ms, 10, 64)
|
|
return time.Unix(0, s*int64(time.Millisecond))
|
|
}
|
|
|
|
func (i *Importer) importLibrary() (err error) {
|
|
indexGroups := utils.ParseIndexGroups(beego.AppConfig.String("indexGroups"))
|
|
artistIndex := make(map[string]tempIndex)
|
|
mfs := make(domain.MediaFiles, len(i.scanner.MediaFiles()))
|
|
als := make(domain.Albums, len(i.scanner.Albums()))
|
|
ars := make(domain.Artists, len(i.scanner.Artists()))
|
|
pls := make(domain.Playlists, len(i.scanner.Playlists()))
|
|
arc, _ := i.artistRepo.CountAll()
|
|
alc, _ := i.albumRepo.CountAll()
|
|
mfc, _ := i.mfRepo.CountAll()
|
|
plc, _ := i.plsRepo.CountAll()
|
|
alu := 0
|
|
mfu := 0
|
|
|
|
//i.search.ClearAll()
|
|
//
|
|
beego.Debug("Saving updated data")
|
|
j := 0
|
|
for _, mf := range i.scanner.MediaFiles() {
|
|
mfs[j] = *mf
|
|
j++
|
|
if mf.UpdatedAt.Before(i.lastScan) {
|
|
continue
|
|
}
|
|
if mf.Starred {
|
|
original, err := i.mfRepo.Get(mf.Id)
|
|
if err != nil || !original.Starred {
|
|
mf.StarredAt = mf.UpdatedAt
|
|
}
|
|
}
|
|
if err := i.mfRepo.Put(mf); err != nil {
|
|
beego.Error(err)
|
|
}
|
|
if err := i.search.IndexMediaFile(mf); err != nil {
|
|
beego.Error("Error indexing artist:", err)
|
|
}
|
|
mfu++
|
|
if !i.lastScan.IsZero() {
|
|
beego.Debug("-- Updated Track:", mf.Title)
|
|
}
|
|
}
|
|
|
|
j = 0
|
|
for _, al := range i.scanner.Albums() {
|
|
als[j] = *al
|
|
j++
|
|
if al.UpdatedAt.Before(i.lastScan) {
|
|
continue
|
|
}
|
|
if al.Starred {
|
|
original, err := i.albumRepo.Get(al.Id)
|
|
if err != nil || !original.Starred {
|
|
al.StarredAt = al.UpdatedAt
|
|
}
|
|
}
|
|
if err := i.albumRepo.Put(al); err != nil {
|
|
beego.Error(err)
|
|
}
|
|
if err := i.search.IndexAlbum(al); err != nil {
|
|
beego.Error("Error indexing artist:", err)
|
|
}
|
|
alu++
|
|
if !i.lastScan.IsZero() {
|
|
beego.Debug(fmt.Sprintf(`-- Updated Album:"%s" from "%s"`, al.Name, al.Artist))
|
|
}
|
|
}
|
|
|
|
j = 0
|
|
for _, ar := range i.scanner.Artists() {
|
|
ars[j] = *ar
|
|
j++
|
|
if err := i.artistRepo.Put(ar); err != nil {
|
|
beego.Error(err)
|
|
}
|
|
if err := i.search.IndexArtist(ar); err != nil {
|
|
beego.Error("Error indexing artist:", err)
|
|
}
|
|
i.collectIndex(indexGroups, ar, artistIndex)
|
|
}
|
|
|
|
j = 0
|
|
for _, pl := range i.scanner.Playlists() {
|
|
pl.Public = true
|
|
pl.Owner = beego.AppConfig.String("user")
|
|
pls[j] = *pl
|
|
j++
|
|
if err := i.plsRepo.Put(pl); err != nil {
|
|
beego.Error(err)
|
|
}
|
|
}
|
|
|
|
if err = i.saveIndex(artistIndex); err != nil {
|
|
beego.Error(err)
|
|
}
|
|
|
|
beego.Debug("Purging old data")
|
|
if deleted, err := i.mfRepo.PurgeInactive(mfs); err != nil {
|
|
beego.Error(err)
|
|
} else {
|
|
i.search.RemoveMediaFile(deleted)
|
|
}
|
|
if deleted, err := i.albumRepo.PurgeInactive(als); err != nil {
|
|
beego.Error(err)
|
|
} else {
|
|
i.search.RemoveAlbum(deleted)
|
|
}
|
|
if deleted, err := i.artistRepo.PurgeInactive(ars); err != nil {
|
|
beego.Error(err)
|
|
} else {
|
|
i.search.RemoveArtist(deleted)
|
|
}
|
|
if _, err := i.plsRepo.PurgeInactive(pls); err != nil {
|
|
beego.Error(err)
|
|
}
|
|
|
|
arc2, _ := i.artistRepo.CountAll()
|
|
alc2, _ := i.albumRepo.CountAll()
|
|
mfc2, _ := i.mfRepo.CountAll()
|
|
plc2, _ := i.plsRepo.CountAll()
|
|
|
|
if arc != arc2 || alc != alc2 || mfc != mfc2 || plc != plc2 {
|
|
beego.Info(fmt.Sprintf("Updated library totals: %d(%+d) artists, %d(%+d) albums, %d(%+d) songs, %d(%+d) playlists", arc2, arc2-arc, alc2, alc2-alc, mfc2, mfc2-mfc, plc2, plc2-plc))
|
|
}
|
|
if alu > 0 || mfu > 0 {
|
|
beego.Info(fmt.Sprintf("Updated items: %d album(s), %d song(s)", alu, mfu))
|
|
}
|
|
|
|
if err == nil {
|
|
millis := time.Now().UnixNano() / int64(time.Millisecond)
|
|
i.propertyRepo.Put(consts.LastScan, fmt.Sprint(millis))
|
|
beego.Debug("LastScan timestamp:", millis)
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (i *Importer) collectIndex(ig utils.IndexGroups, a *domain.Artist, artistIndex map[string]tempIndex) {
|
|
name := a.Name
|
|
indexName := strings.ToLower(utils.NoArticle(name))
|
|
if indexName == "" {
|
|
return
|
|
}
|
|
group := i.findGroup(ig, indexName)
|
|
artists := artistIndex[group]
|
|
if artists == nil {
|
|
artists = make(tempIndex)
|
|
artistIndex[group] = artists
|
|
}
|
|
artists[indexName] = domain.ArtistInfo{ArtistId: a.Id, Artist: a.Name}
|
|
}
|
|
|
|
func (i *Importer) findGroup(ig utils.IndexGroups, name string) string {
|
|
for k, v := range ig {
|
|
key := strings.ToLower(k)
|
|
if strings.HasPrefix(name, key) {
|
|
return v
|
|
}
|
|
}
|
|
return "#"
|
|
}
|
|
|
|
func (i *Importer) saveIndex(artistIndex map[string]tempIndex) error {
|
|
for k, temp := range artistIndex {
|
|
idx := &domain.ArtistIndex{Id: k}
|
|
for _, v := range temp {
|
|
idx.Artists = append(idx.Artists, v)
|
|
}
|
|
err := i.idxRepo.Put(idx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|