mirror of
https://github.com/navidrome/navidrome.git
synced 2025-04-04 21:17:37 +03:00
Implemented first repository using tiedot
This commit is contained in:
parent
e760952263
commit
85ddd19c3d
18 changed files with 279 additions and 53 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -1,5 +1,7 @@
|
||||||
lastupdate.tmp
|
lastupdate.tmp
|
||||||
gosonic
|
gosonic
|
||||||
iTunes Music Library.xml
|
iTunes*.xml
|
||||||
gosonic.index
|
gosonic.index
|
||||||
static/Jamstash
|
static/Jamstash
|
||||||
|
devDb
|
||||||
|
tmp
|
|
@ -5,6 +5,7 @@ path = github.com/deluan/gosonic
|
||||||
github.com/astaxie/beego = tag:v1.6.0
|
github.com/astaxie/beego = tag:v1.6.0
|
||||||
github.com/blevesearch/bleve = commit:a5bb81e
|
github.com/blevesearch/bleve = commit:a5bb81e
|
||||||
github.com/dhowden/itl = commit:35d15a3
|
github.com/dhowden/itl = commit:35d15a3
|
||||||
|
github.com/HouzuoGuo/tiedot = tag:3.2
|
||||||
|
|
||||||
[res]
|
[res]
|
||||||
include = conf
|
include = conf
|
|
@ -9,7 +9,7 @@ import (
|
||||||
type GetMusicFoldersController struct{ beego.Controller }
|
type GetMusicFoldersController struct{ beego.Controller }
|
||||||
|
|
||||||
func (c *GetMusicFoldersController) Get() {
|
func (c *GetMusicFoldersController) Get() {
|
||||||
repository := new(repositories.MediaFolderRepository)
|
repository := repositories.NewMediaFolderRepository()
|
||||||
mediaFolderList, _ := repository.GetAll()
|
mediaFolderList, _ := repository.GetAll()
|
||||||
folders := make([]responses.MusicFolder, len(mediaFolderList))
|
folders := make([]responses.MusicFolder, len(mediaFolderList))
|
||||||
for i, f := range mediaFolderList {
|
for i, f := range mediaFolderList {
|
||||||
|
|
|
@ -5,9 +5,10 @@ autoRender = false
|
||||||
copyRequestBody = true
|
copyRequestBody = true
|
||||||
|
|
||||||
apiVersion = 1.0.0
|
apiVersion = 1.0.0
|
||||||
musicFolder=.
|
musicFolder=./iTunes.xml
|
||||||
user=deluan
|
user=deluan
|
||||||
password=wordpass
|
password=wordpass
|
||||||
|
dbPath = ./devDb
|
||||||
|
|
||||||
[dev]
|
[dev]
|
||||||
disableValidation = true
|
disableValidation = true
|
||||||
|
@ -16,6 +17,8 @@ indexPath = ./gosonic.index
|
||||||
|
|
||||||
[test]
|
[test]
|
||||||
disableValidation = false
|
disableValidation = false
|
||||||
|
httpPort = 8081
|
||||||
enableAdmin = false
|
enableAdmin = false
|
||||||
user=deluan
|
user=deluan
|
||||||
password=wordpass
|
password=wordpass
|
||||||
|
dbPath = ./tmp/testDb
|
|
@ -1 +1,15 @@
|
||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/astaxie/beego"
|
||||||
|
"github.com/deluan/gosonic/scanner"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SyncController struct{ beego.Controller }
|
||||||
|
|
||||||
|
func (c *SyncController) Get() {
|
||||||
|
scanner.StartImport()
|
||||||
|
c.Ctx.WriteString("Import started. Check logs")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
12
models/album.go
Normal file
12
models/album.go
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
type Album struct {
|
||||||
|
Id string
|
||||||
|
Name string
|
||||||
|
Artist *Artist
|
||||||
|
CoverArtPath string
|
||||||
|
Year int
|
||||||
|
Compilation bool
|
||||||
|
Rating int
|
||||||
|
|
||||||
|
}
|
6
models/artist.go
Normal file
6
models/artist.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
type Artist struct {
|
||||||
|
Id string
|
||||||
|
Name string
|
||||||
|
}
|
|
@ -1,8 +1,6 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import "time"
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MediaFile struct {
|
type MediaFile struct {
|
||||||
Id string
|
Id string
|
||||||
|
|
78
repositories/base_repository.go
Normal file
78
repositories/base_repository.go
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/HouzuoGuo/tiedot/db"
|
||||||
|
"github.com/astaxie/beego"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BaseRepository struct {
|
||||||
|
col *db.Col
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *BaseRepository) marshal(rec interface{}) (map[string]interface{}, error) {
|
||||||
|
// Convert to JSON...
|
||||||
|
b, err := json.Marshal(rec);
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... then convert to map
|
||||||
|
var m map[string]interface{}
|
||||||
|
err = json.Unmarshal(b, &m)
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r*BaseRepository) query(q string, a ...interface{}) (map[int]struct{}, error) {
|
||||||
|
q = fmt.Sprintf(q, a)
|
||||||
|
|
||||||
|
var query interface{}
|
||||||
|
json.Unmarshal([]byte(q), &query)
|
||||||
|
|
||||||
|
queryResult := make(map[int]struct{})
|
||||||
|
|
||||||
|
err := db.EvalQuery(query, r.col, &queryResult)
|
||||||
|
if err != nil {
|
||||||
|
beego.Warn("Error '%s' - query='%s'", q, err)
|
||||||
|
}
|
||||||
|
return queryResult, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r*BaseRepository) queryFirstKey(q string, a ...interface{}) (int, error) {
|
||||||
|
result, err := r.query(q, a)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
for key, _ := range result {
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *BaseRepository) saveOrUpdate(rec interface{}) error {
|
||||||
|
m, err := r.marshal(rec)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
docId, err := r.queryFirstKey(`{"in": ["Id"], "eq": "%s"}`, m["Id"])
|
||||||
|
if docId == 0 {
|
||||||
|
_, err = r.col.Insert(m)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = r.col.Update(docId, m)
|
||||||
|
if err != nil {
|
||||||
|
beego.Warn("Error updating %s[%d]: %s", r.col, docId, err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *BaseRepository) Dump() {
|
||||||
|
r.col.ForEachDoc(func(id int, docContent []byte) (willMoveOn bool) {
|
||||||
|
beego.Debug("Document", id, "=", string(docContent))
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
37
repositories/init_database.go
Normal file
37
repositories/init_database.go
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/HouzuoGuo/tiedot/db"
|
||||||
|
"github.com/astaxie/beego"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_dbInstance *db.DB
|
||||||
|
once sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
func createCollection(name string) *db.Col {
|
||||||
|
col := dbInstance().Use(name)
|
||||||
|
if col != nil {
|
||||||
|
return col
|
||||||
|
}
|
||||||
|
if err := dbInstance().Create(name); err != nil {
|
||||||
|
beego.Error(err)
|
||||||
|
}
|
||||||
|
if err := col.Index([]string{"Id"}); err != nil {
|
||||||
|
beego.Error(name, err)
|
||||||
|
}
|
||||||
|
return col
|
||||||
|
}
|
||||||
|
|
||||||
|
func dbInstance() *db.DB {
|
||||||
|
once.Do(func() {
|
||||||
|
instance, err := db.OpenDB(beego.AppConfig.String("dbPath"))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
_dbInstance = instance
|
||||||
|
})
|
||||||
|
return _dbInstance
|
||||||
|
}
|
|
@ -1,10 +1,24 @@
|
||||||
package repositories
|
package repositories
|
||||||
|
|
||||||
//import "github.com/deluan/gosonic/models"
|
import (
|
||||||
//
|
"github.com/deluan/gosonic/models"
|
||||||
//func AddMediaFile(m models.MediaFile) string {
|
"fmt"
|
||||||
// m.ID = "user_" + strconv.FormatInt(time.Now().UnixNano(), 10)
|
"crypto/md5"
|
||||||
// UserList[u.Id] = &u
|
)
|
||||||
// return u.Id
|
|
||||||
//}
|
type MediaFile struct {
|
||||||
//
|
BaseRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMediaFileRepository() *MediaFile {
|
||||||
|
r := &MediaFile{}
|
||||||
|
r.col = createCollection("MediaFiles")
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *MediaFile) Add(m *models.MediaFile) error {
|
||||||
|
if m.Id == "" {
|
||||||
|
m.Id = fmt.Sprintf("%x", md5.Sum([]byte(m.Path)))
|
||||||
|
}
|
||||||
|
return r.saveOrUpdate(m)
|
||||||
|
}
|
|
@ -5,9 +5,14 @@ import (
|
||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MediaFolderRepository struct {}
|
type MediaFolder struct {}
|
||||||
|
|
||||||
func (*MediaFolderRepository) GetAll() ([]*models.MediaFolder, error) {
|
func NewMediaFolderRepository() *MediaFolder {
|
||||||
|
return &MediaFolder{}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (*MediaFolder) GetAll() ([]*models.MediaFolder, error) {
|
||||||
mediaFolder := models.MediaFolder{Id: "0", Name: "iTunes Library", Path: beego.AppConfig.String("musicFolder")}
|
mediaFolder := models.MediaFolder{Id: "0", Name: "iTunes Library", Path: beego.AppConfig.String("musicFolder")}
|
||||||
result := make([]*models.MediaFolder, 1)
|
result := make([]*models.MediaFolder, 1)
|
||||||
result[0] = &mediaFolder
|
result[0] = &mediaFolder
|
||||||
|
|
|
@ -17,6 +17,7 @@ func init() {
|
||||||
beego.AddNamespace(ns)
|
beego.AddNamespace(ns)
|
||||||
|
|
||||||
beego.Router("/", &controllers.MainController{})
|
beego.Router("/", &controllers.MainController{})
|
||||||
|
beego.Router("/sync", &controllers.SyncController{})
|
||||||
|
|
||||||
var ValidateRequest = func(ctx *context.Context) {
|
var ValidateRequest = func(ctx *context.Context) {
|
||||||
api.Validate(&beego.Controller{Ctx: ctx})
|
api.Validate(&beego.Controller{Ctx: ctx})
|
||||||
|
|
34
scanner/itunes_scanner.go
Normal file
34
scanner/itunes_scanner.go
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package scanner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/dhowden/itl"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ItunesScanner struct {}
|
||||||
|
|
||||||
|
func (s *ItunesScanner) LoadFolder(path string) []Track {
|
||||||
|
xml, _ := os.Open(path)
|
||||||
|
l, _ := itl.ReadFromXML(xml)
|
||||||
|
|
||||||
|
mediaFiles := make([]Track, len(l.Tracks))
|
||||||
|
i := 0
|
||||||
|
for id, t := range l.Tracks {
|
||||||
|
if t.Location != "" && strings.Contains(t.Kind, "audio") {
|
||||||
|
mediaFiles[i].Id = id
|
||||||
|
mediaFiles[i].Album = t.Album
|
||||||
|
mediaFiles[i].Title = t.Name
|
||||||
|
mediaFiles[i].Artist = t.Artist
|
||||||
|
path, _ = url.QueryUnescape(t.Location)
|
||||||
|
mediaFiles[i].Path = strings.TrimPrefix(path, "file://")
|
||||||
|
mediaFiles[i].CreatedAt = t.DateAdded
|
||||||
|
mediaFiles[i].UpdatedAt = t.DateModified
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mediaFiles[0:i]
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Scanner = (*ItunesScanner)(nil)
|
42
scanner/scanner.go
Normal file
42
scanner/scanner.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package scanner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/astaxie/beego"
|
||||||
|
"github.com/deluan/gosonic/repositories"
|
||||||
|
"github.com/deluan/gosonic/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Scanner interface {
|
||||||
|
LoadFolder(path string) []Track
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartImport() {
|
||||||
|
go doImport(beego.AppConfig.String("musicFolder"), &ItunesScanner{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func doImport(mediaFolder string, scanner Scanner) {
|
||||||
|
beego.Info("Starting iTunes import from:", mediaFolder)
|
||||||
|
files := scanner.LoadFolder(mediaFolder)
|
||||||
|
updateDatastore(files)
|
||||||
|
beego.Info("Finished importing", len(files), "files")
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateDatastore(files []Track) {
|
||||||
|
mfRepo := repositories.NewMediaFileRepository()
|
||||||
|
for _, t := range files {
|
||||||
|
m := &models.MediaFile{
|
||||||
|
Id: t.Id,
|
||||||
|
Album: t.Album,
|
||||||
|
Artist: t.Artist,
|
||||||
|
Title: t.Title,
|
||||||
|
Path: t.Path,
|
||||||
|
CreatedAt: t.CreatedAt,
|
||||||
|
UpdatedAt: t.UpdatedAt,
|
||||||
|
}
|
||||||
|
err := mfRepo.Add(m)
|
||||||
|
if err != nil {
|
||||||
|
beego.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mfRepo.Dump()
|
||||||
|
}
|
15
scanner/track.go
Normal file
15
scanner/track.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package scanner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Track struct {
|
||||||
|
Id string
|
||||||
|
Path string
|
||||||
|
Album string
|
||||||
|
Artist string
|
||||||
|
Title string
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
}
|
|
@ -1,29 +0,0 @@
|
||||||
package itunes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/deluan/gosonic/models"
|
|
||||||
"github.com/dhowden/itl"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func LoadFolder(path string) []models.MediaFile {
|
|
||||||
xml, _ := os.Open(path)
|
|
||||||
l, _ := itl.ReadFromXML(xml)
|
|
||||||
|
|
||||||
mediaFiles := make([]models.MediaFile, len(l.Tracks))
|
|
||||||
i := 0
|
|
||||||
for id, track := range l.Tracks {
|
|
||||||
mediaFiles[i].Id = id
|
|
||||||
mediaFiles[i].Album = track.Album
|
|
||||||
mediaFiles[i].Title = track.Name
|
|
||||||
mediaFiles[i].Artist = track.Artist
|
|
||||||
path, _ = url.QueryUnescape(track.Location)
|
|
||||||
mediaFiles[i].Path = strings.TrimPrefix(path, "file://")
|
|
||||||
mediaFiles[i].CreatedAt = track.DateAdded
|
|
||||||
mediaFiles[i].UpdatedAt = track.DateModified
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
return mediaFiles
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
package scanners
|
|
||||||
|
|
||||||
import "github.com/deluan/gosonic/models"
|
|
||||||
|
|
||||||
type Scanner interface {
|
|
||||||
LoadFolder(path string) []models.MediaFile
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue