diff --git a/models/album.go b/models/album.go index 50fbfd5ad..81179230a 100644 --- a/models/album.go +++ b/models/album.go @@ -1,12 +1,34 @@ package models type Album struct { - Id string - Name string - Artist *Artist + Id string + Name string + ArtistId string CoverArtPath string - Year int - Compilation bool - Rating int - + Year int + Compilation bool + Rating int + MediaFiles map[string]bool } + +func (a *Album) AdMediaFiles(mfs ...*MediaFile) { + for _, mf := range mfs { + a.MediaFiles[mf.Id] = true + } +} + +func (a *Album) AddMediaFiles(mfs ...interface{}) { + if a.MediaFiles == nil { + a.MediaFiles = make(map[string]bool) + } + for _, v := range mfs { + switch v := v.(type) { + case *MediaFile: + a.MediaFiles[v.Id] = true + case map[string]bool: + for k, _ := range v { + a.MediaFiles[k] = true + } + } + } +} \ No newline at end of file diff --git a/models/artist.go b/models/artist.go index 7637bcff5..43247ae51 100644 --- a/models/artist.go +++ b/models/artist.go @@ -6,8 +6,9 @@ import ( ) type Artist struct { - Id string - Name string + Id string + Name string + Albums map[string]bool } func NoArticle(name string) string { @@ -19,4 +20,20 @@ func NoArticle(name string) string { } } return name +} + +func (a *Artist) AddAlbums(albums ...interface{}) { + if a.Albums == nil { + a.Albums = make(map[string]bool) + } + for _, v := range albums { + switch v := v.(type) { + case *Album: + a.Albums[v.Id] = true + case map[string]bool: + for k, _ := range v { + a.Albums[k] = true + } + } + } } \ No newline at end of file diff --git a/repositories/album_repository.go b/repositories/album_repository.go new file mode 100644 index 000000000..18a460c46 --- /dev/null +++ b/repositories/album_repository.go @@ -0,0 +1,34 @@ +package repositories + +import ( + "github.com/deluan/gosonic/models" +) + +type Album struct { + BaseRepository +} + +func NewAlbumRepository() *Album { + r := &Album{} + r.key = "album" + return r +} + +func (r *Album) Put(m *models.Album) (*models.Album, error) { + if m.Id == "" { + m.Id = r.NewId(m.Name) + } + return m, r.saveOrUpdate(m.Id, m) +} + +func (r *Album) Get(id string) (*models.Album, error) { + rec := &models.Album{} + err := readStruct(r.key, id, rec) + return rec, err +} + +func (r *Album) GetByName(name string) (*models.Album, error) { + id := r.NewId(name) + return r.Get(id) +} + diff --git a/repositories/artist_repository.go b/repositories/artist_repository.go new file mode 100644 index 000000000..b67919c06 --- /dev/null +++ b/repositories/artist_repository.go @@ -0,0 +1,34 @@ +package repositories + +import ( + "github.com/deluan/gosonic/models" +) + +type Artist struct { + BaseRepository +} + +func NewArtistRepository() *Artist { + r := &Artist{} + r.key = "artist" + return r +} + +func (r *Artist) Put(m *models.Artist) (*models.Artist, error) { + if m.Id == "" { + m.Id = r.NewId(m.Name) + } + return m, r.saveOrUpdate(m.Id, m) +} + +func (r *Artist) Get(id string) (*models.Artist, error) { + rec := &models.Artist{} + err := readStruct(r.key, id, rec) + return rec, err +} + +func (r *Artist) GetByName(name string) (*models.Artist, error) { + id := r.NewId(name) + return r.Get(id) +} + diff --git a/repositories/base_repository.go b/repositories/base_repository.go index a9882a597..d5057d566 100644 --- a/repositories/base_repository.go +++ b/repositories/base_repository.go @@ -1,18 +1,28 @@ package repositories +import ( + "fmt" + "crypto/md5" + "strings" +) + type BaseRepository struct { - key string + key string // TODO Rename to 'table' } - -func (r *BaseRepository) saveOrUpdate(id string, rec interface{}) error { - return saveStruct(r.key, id, rec) +func (r *BaseRepository) NewId(fields ...string) string { + s := fmt.Sprintf("%s\\%s", strings.ToUpper(r.key), strings.Join(fields, "")) + return fmt.Sprintf("%x", md5.Sum([]byte(s))) } func (r *BaseRepository) CountAll() (int, error) { return count(r.key) } +func (r *BaseRepository) saveOrUpdate(id string, rec interface{}) error { + return saveStruct(r.key, id, rec) +} + func (r *BaseRepository) Dump() { } diff --git a/repositories/ledis_database.go b/repositories/ledis_utils.go similarity index 83% rename from repositories/ledis_database.go rename to repositories/ledis_utils.go index f5709aa40..9cc01db77 100644 --- a/repositories/ledis_database.go +++ b/repositories/ledis_utils.go @@ -46,8 +46,9 @@ func saveStruct(key, id string, data interface{}) error { return db().HMset([]byte(kh), fvList...) } -func readStruct(key string) (interface{}, error) { - fvs, _ := db().HGetAll([]byte(key)) +func readStruct(key, id string, rec interface{}) error { + kh := key + "_id_" + id + fvs, _ := db().HGetAll([]byte(kh)) var m = make(map[string]interface{}, len(fvs)) for _, fv := range fvs { var v interface{} @@ -55,15 +56,10 @@ func readStruct(key string) (interface{}, error) { m[string(fv.Field)] = v } - return utils.ToStruct(m) + return utils.ToStruct(m, rec) } func count(key string) (int, error) { ids, err := db().SMembers([]byte(key + "_ids")) return len(ids), err -} - -func hset(key, field, value string) error { - _, err := db().HSet([]byte(key), []byte(field), []byte(value)) - return err } \ No newline at end of file diff --git a/repositories/media_file_repository.go b/repositories/media_file_repository.go index 752fb31fa..e0869e5c1 100644 --- a/repositories/media_file_repository.go +++ b/repositories/media_file_repository.go @@ -2,8 +2,6 @@ package repositories import ( "github.com/deluan/gosonic/models" - "fmt" - "crypto/md5" ) type MediaFile struct { @@ -16,9 +14,6 @@ func NewMediaFileRepository() *MediaFile { return r } -func (r *MediaFile) Add(m *models.MediaFile) error { - if m.Id == "" { - m.Id = fmt.Sprintf("%x", md5.Sum([]byte(m.Path))) - } +func (r *MediaFile) Put(m *models.MediaFile) error { return r.saveOrUpdate(m.Id, m) } \ No newline at end of file diff --git a/scanner/itunes_scanner.go b/scanner/itunes_scanner.go index a60bdf399..c15d14791 100644 --- a/scanner/itunes_scanner.go +++ b/scanner/itunes_scanner.go @@ -24,6 +24,7 @@ func (s *ItunesScanner) LoadFolder(path string) []Track { mediaFiles[i].Artist = t.Artist mediaFiles[i].AlbumArtist = t.AlbumArtist mediaFiles[i].Compilation = t.Compilation + mediaFiles[i].Year = t.Year path, _ = url.QueryUnescape(t.Location) mediaFiles[i].Path = strings.TrimPrefix(path, "file://") mediaFiles[i].CreatedAt = t.DateAdded diff --git a/scanner/scanner.go b/scanner/scanner.go index 532cd5341..0cb5eff78 100644 --- a/scanner/scanner.go +++ b/scanner/scanner.go @@ -5,6 +5,7 @@ import ( "github.com/deluan/gosonic/repositories" "github.com/deluan/gosonic/models" "strings" + "fmt" ) type Scanner interface { @@ -18,38 +19,86 @@ func StartImport() { func doImport(mediaFolder string, scanner Scanner) { beego.Info("Starting iTunes import from:", mediaFolder) files := scanner.LoadFolder(mediaFolder) - updateDatastore(files) + importLibrary(files) beego.Info("Finished importing", len(files), "files") } -func updateDatastore(files []Track) { +func importLibrary(files []Track) { mfRepo := repositories.NewMediaFileRepository() + albumRepo := repositories.NewAlbumRepository() + artistRepo := repositories.NewArtistRepository() var artistIndex = make(map[string]map[string]string) + for _, t := range files { - m := &models.MediaFile{ - Id: t.Id, - Album: t.Album, - Artist: t.Artist, - AlbumArtist: t.AlbumArtist, - Title: t.Title, - Compilation: t.Compilation, - Path: t.Path, - CreatedAt: t.CreatedAt, - UpdatedAt: t.UpdatedAt, - } - err := mfRepo.Add(m) - if err != nil { - beego.Error(err) - } - collectIndex(m, artistIndex) + mf, album, artist := processTrack(&t) + mergeInfo(mfRepo, mf, albumRepo, album, artistRepo, artist) + fmt.Printf("%#v\n", album) + fmt.Printf("%#v\n\n", artist) + collectIndex(mf, artistIndex) } - //mfRepo.Dump() + //j,_ := json.MarshalIndent(artistIndex, "", " ") //fmt.Println(string(j)) c, _ := mfRepo.CountAll() + beego.Info("Total mediafiles in database:", c) } +func processTrack(t *Track) (*models.MediaFile, *models.Album, *models.Artist) { + mf := &models.MediaFile{ + Id: t.Id, + Album: t.Album, + Artist: t.Artist, + AlbumArtist: t.AlbumArtist, + Title: t.Title, + Compilation: t.Compilation, + Path: t.Path, + CreatedAt: t.CreatedAt, + UpdatedAt: t.UpdatedAt, + } + + album := &models.Album{ + Name: t.Album, + Year: t.Year, + Compilation: t.Compilation, + } + + artist := &models.Artist{ + Name: t.Artist, + } + + return mf, album, artist +} + +func mergeInfo(mfRepo *repositories.MediaFile, mf *models.MediaFile, albumRepo *repositories.Album, album *models.Album, artistRepo *repositories.Artist, artist *models.Artist) { + artist.Id = artistRepo.NewId(artist.Name) + + sAlbum, err := albumRepo.GetByName(album.Name) + if err != nil { + beego.Error(err) + } + album.ArtistId = artist.Id + album.AddMediaFiles(mf, sAlbum.MediaFiles) + sAlbum, err = albumRepo.Put(album) + if err != nil { + beego.Error(err) + } + + sArtist, err := artistRepo.GetByName(artist.Name) + if err != nil { + beego.Error(err) + } + artist.AddAlbums(sAlbum, sArtist.Albums) + sArtist, err = artistRepo.Put(artist) + if err != nil { + beego.Error(err) + } + + if err := mfRepo.Put(mf); err != nil { + beego.Error(err) + } +} + func collectIndex(m *models.MediaFile, artistIndex map[string]map[string]string) { name := m.RealArtist() indexName := strings.ToLower(models.NoArticle(name)) diff --git a/scanner/track.go b/scanner/track.go index 478c8a993..f1730d24f 100644 --- a/scanner/track.go +++ b/scanner/track.go @@ -11,6 +11,7 @@ type Track struct { Album string Artist string AlbumArtist string + Year int Compilation bool CreatedAt time.Time UpdatedAt time.Time diff --git a/utils/mapping.go b/utils/mapping.go index 3a6949f9a..f950e900f 100644 --- a/utils/mapping.go +++ b/utils/mapping.go @@ -121,17 +121,16 @@ func ToMap(rec interface{}) (map[string]interface{}, error) { return m, err } -func ToStruct(m map[string]interface{}) (interface{}, error) { +func ToStruct(m map[string]interface{}, rec interface{}) error { // Convert to JSON... b, err := json.Marshal(m); if err != nil { - return nil, err + return err } - // ... then convert to map - var rec interface{} + // ... then convert to struct err = json.Unmarshal(b, &rec) - return rec, err + return err } func Flatten(input interface{}) (map[string]interface{}, error) {