Big Refactor:
- Create model.DataStore, with provision for transactions - Change all layers dependencies on repositories to use DataStore - Implemented persistence.SQLStore - Removed iTunes Bridge/Importer support
This commit is contained in:
@@ -36,22 +36,21 @@ type albumRepository struct {
|
||||
searchableRepository
|
||||
}
|
||||
|
||||
func NewAlbumRepository() model.AlbumRepository {
|
||||
func NewAlbumRepository(o orm.Ormer) model.AlbumRepository {
|
||||
r := &albumRepository{}
|
||||
r.ormer = o
|
||||
r.tableName = "album"
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *albumRepository) Put(a *model.Album) error {
|
||||
ta := album(*a)
|
||||
return withTx(func(o orm.Ormer) error {
|
||||
return r.put(o, a.ID, a.Name, &ta)
|
||||
})
|
||||
return r.put(a.ID, a.Name, &ta)
|
||||
}
|
||||
|
||||
func (r *albumRepository) Get(id string) (*model.Album, error) {
|
||||
ta := album{ID: id}
|
||||
err := Db().Read(&ta)
|
||||
err := r.ormer.Read(&ta)
|
||||
if err == orm.ErrNoRows {
|
||||
return nil, model.ErrNotFound
|
||||
}
|
||||
@@ -64,7 +63,7 @@ func (r *albumRepository) Get(id string) (*model.Album, error) {
|
||||
|
||||
func (r *albumRepository) FindByArtist(artistId string) (model.Albums, error) {
|
||||
var albums []album
|
||||
_, err := r.newQuery(Db()).Filter("artist_id", artistId).OrderBy("year", "name").All(&albums)
|
||||
_, err := r.newQuery().Filter("artist_id", artistId).OrderBy("year", "name").All(&albums)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -73,7 +72,7 @@ func (r *albumRepository) FindByArtist(artistId string) (model.Albums, error) {
|
||||
|
||||
func (r *albumRepository) GetAll(options ...model.QueryOptions) (model.Albums, error) {
|
||||
var all []album
|
||||
_, err := r.newQuery(Db(), options...).All(&all)
|
||||
_, err := r.newQuery(options...).All(&all)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -95,7 +94,7 @@ func (r *albumRepository) Refresh(ids ...string) error {
|
||||
HasCoverArt bool
|
||||
}
|
||||
var albums []refreshAlbum
|
||||
o := Db()
|
||||
o := r.ormer
|
||||
sql := fmt.Sprintf(`
|
||||
select album_id as id, album as name, f.artist, f.album_artist, f.artist_id, f.compilation, f.genre,
|
||||
max(f.year) as year, sum(f.play_count) as play_count, max(f.play_date) as play_date, sum(f.duration) as duration,
|
||||
@@ -126,7 +125,7 @@ group by album_id order by f.id`, strings.Join(ids, "','"))
|
||||
} else {
|
||||
toInsert = append(toInsert, al.album)
|
||||
}
|
||||
err := r.addToIndex(o, r.tableName, al.ID, al.Name)
|
||||
err := r.addToIndex(r.tableName, al.ID, al.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -153,23 +152,20 @@ group by album_id order by f.id`, strings.Join(ids, "','"))
|
||||
}
|
||||
|
||||
func (r *albumRepository) PurgeInactive(activeList model.Albums) error {
|
||||
return withTx(func(o orm.Ormer) error {
|
||||
_, err := r.purgeInactive(o, activeList, func(item interface{}) string {
|
||||
return item.(model.Album).ID
|
||||
})
|
||||
return err
|
||||
_, err := r.purgeInactive(activeList, func(item interface{}) string {
|
||||
return item.(model.Album).ID
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *albumRepository) PurgeEmpty() error {
|
||||
o := Db()
|
||||
_, err := o.Raw("delete from album where id not in (select distinct(album_id) from media_file)").Exec()
|
||||
_, err := r.ormer.Raw("delete from album where id not in (select distinct(album_id) from media_file)").Exec()
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *albumRepository) GetStarred(options ...model.QueryOptions) (model.Albums, error) {
|
||||
var starred []album
|
||||
_, err := r.newQuery(Db(), options...).Filter("starred", true).All(&starred)
|
||||
_, err := r.newQuery(options...).Filter("starred", true).All(&starred)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -184,7 +180,7 @@ func (r *albumRepository) SetStar(starred bool, ids ...string) error {
|
||||
if starred {
|
||||
starredAt = time.Now()
|
||||
}
|
||||
_, err := r.newQuery(Db()).Filter("id__in", ids).Update(orm.Params{
|
||||
_, err := r.newQuery().Filter("id__in", ids).Update(orm.Params{
|
||||
"starred": starred,
|
||||
"starred_at": starredAt,
|
||||
})
|
||||
@@ -192,7 +188,7 @@ func (r *albumRepository) SetStar(starred bool, ids ...string) error {
|
||||
}
|
||||
|
||||
func (r *albumRepository) MarkAsPlayed(id string, playDate time.Time) error {
|
||||
_, err := r.newQuery(Db()).Filter("id", id).Update(orm.Params{
|
||||
_, err := r.newQuery().Filter("id", id).Update(orm.Params{
|
||||
"play_count": orm.ColValue(orm.ColAdd, 1),
|
||||
"play_date": playDate,
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego/orm"
|
||||
"github.com/cloudsonic/sonic-server/model"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
@@ -10,7 +11,7 @@ var _ = Describe("AlbumRepository", func() {
|
||||
var repo model.AlbumRepository
|
||||
|
||||
BeforeEach(func() {
|
||||
repo = NewAlbumRepository()
|
||||
repo = NewAlbumRepository(orm.NewOrm())
|
||||
})
|
||||
|
||||
Describe("GetAll", func() {
|
||||
|
||||
@@ -26,8 +26,9 @@ type artistRepository struct {
|
||||
indexGroups utils.IndexGroups
|
||||
}
|
||||
|
||||
func NewArtistRepository() model.ArtistRepository {
|
||||
func NewArtistRepository(o orm.Ormer) model.ArtistRepository {
|
||||
r := &artistRepository{}
|
||||
r.ormer = o
|
||||
r.indexGroups = utils.ParseIndexGroups(conf.Sonic.IndexGroups)
|
||||
r.tableName = "artist"
|
||||
return r
|
||||
@@ -46,14 +47,12 @@ func (r *artistRepository) getIndexKey(a *artist) string {
|
||||
|
||||
func (r *artistRepository) Put(a *model.Artist) error {
|
||||
ta := artist(*a)
|
||||
return withTx(func(o orm.Ormer) error {
|
||||
return r.put(o, a.ID, a.Name, &ta)
|
||||
})
|
||||
return r.put(a.ID, a.Name, &ta)
|
||||
}
|
||||
|
||||
func (r *artistRepository) Get(id string) (*model.Artist, error) {
|
||||
ta := artist{ID: id}
|
||||
err := Db().Read(&ta)
|
||||
err := r.ormer.Read(&ta)
|
||||
if err == orm.ErrNoRows {
|
||||
return nil, model.ErrNotFound
|
||||
}
|
||||
@@ -68,7 +67,7 @@ func (r *artistRepository) Get(id string) (*model.Artist, error) {
|
||||
func (r *artistRepository) GetIndex() (model.ArtistIndexes, error) {
|
||||
var all []artist
|
||||
// TODO Paginate
|
||||
_, err := r.newQuery(Db()).OrderBy("name").All(&all)
|
||||
_, err := r.newQuery().OrderBy("name").All(&all)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -101,7 +100,7 @@ func (r *artistRepository) Refresh(ids ...string) error {
|
||||
Compilation bool
|
||||
}
|
||||
var artists []refreshArtist
|
||||
o := Db()
|
||||
o := r.ormer
|
||||
sql := fmt.Sprintf(`
|
||||
select f.artist_id as id,
|
||||
f.artist as name,
|
||||
@@ -131,7 +130,7 @@ where f.artist_id in ('%s') group by f.artist_id order by f.id`, strings.Join(id
|
||||
} else {
|
||||
toInsert = append(toInsert, ar.artist)
|
||||
}
|
||||
err := r.addToIndex(o, r.tableName, ar.ID, ar.Name)
|
||||
err := r.addToIndex(r.tableName, ar.ID, ar.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -158,7 +157,7 @@ where f.artist_id in ('%s') group by f.artist_id order by f.id`, strings.Join(id
|
||||
|
||||
func (r *artistRepository) GetStarred(options ...model.QueryOptions) (model.Artists, error) {
|
||||
var starred []artist
|
||||
_, err := r.newQuery(Db(), options...).Filter("starred", true).All(&starred)
|
||||
_, err := r.newQuery(options...).Filter("starred", true).All(&starred)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -173,7 +172,7 @@ func (r *artistRepository) SetStar(starred bool, ids ...string) error {
|
||||
if starred {
|
||||
starredAt = time.Now()
|
||||
}
|
||||
_, err := r.newQuery(Db()).Filter("id__in", ids).Update(orm.Params{
|
||||
_, err := r.newQuery().Filter("id__in", ids).Update(orm.Params{
|
||||
"starred": starred,
|
||||
"starred_at": starredAt,
|
||||
})
|
||||
@@ -181,17 +180,14 @@ func (r *artistRepository) SetStar(starred bool, ids ...string) error {
|
||||
}
|
||||
|
||||
func (r *artistRepository) PurgeInactive(activeList model.Artists) error {
|
||||
return withTx(func(o orm.Ormer) error {
|
||||
_, err := r.purgeInactive(o, activeList, func(item interface{}) string {
|
||||
return item.(model.Artist).ID
|
||||
})
|
||||
return err
|
||||
_, err := r.purgeInactive(activeList, func(item interface{}) string {
|
||||
return item.(model.Artist).ID
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *artistRepository) PurgeEmpty() error {
|
||||
o := Db()
|
||||
_, err := o.Raw("delete from artist where id not in (select distinct(artist_id) from album)").Exec()
|
||||
_, err := r.ormer.Raw("delete from artist where id not in (select distinct(artist_id) from album)").Exec()
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego/orm"
|
||||
"github.com/cloudsonic/sonic-server/model"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
@@ -10,7 +11,7 @@ var _ = Describe("ArtistRepository", func() {
|
||||
var repo model.ArtistRepository
|
||||
|
||||
BeforeEach(func() {
|
||||
repo = NewArtistRepository()
|
||||
repo = NewArtistRepository(orm.NewOrm())
|
||||
})
|
||||
|
||||
Describe("Put/Get", func() {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
)
|
||||
|
||||
type checkSumRepository struct {
|
||||
ormer orm.Ormer
|
||||
}
|
||||
|
||||
const checkSumId = "1"
|
||||
@@ -15,8 +16,8 @@ type checksum struct {
|
||||
Sum string
|
||||
}
|
||||
|
||||
func NewCheckSumRepository() model.ChecksumRepository {
|
||||
r := &checkSumRepository{}
|
||||
func NewCheckSumRepository(o orm.Ormer) model.ChecksumRepository {
|
||||
r := &checkSumRepository{ormer: o}
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -24,7 +25,7 @@ func (r *checkSumRepository) GetData() (model.ChecksumMap, error) {
|
||||
loadedData := make(map[string]string)
|
||||
|
||||
var all []checksum
|
||||
_, err := Db().QueryTable(&checksum{}).Limit(-1).All(&all)
|
||||
_, err := r.ormer.QueryTable(&checksum{}).Limit(-1).All(&all)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -37,24 +38,17 @@ func (r *checkSumRepository) GetData() (model.ChecksumMap, error) {
|
||||
}
|
||||
|
||||
func (r *checkSumRepository) SetData(newSums model.ChecksumMap) error {
|
||||
err := withTx(func(o orm.Ormer) error {
|
||||
_, err := Db().Raw("delete from checksum").Exec()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := r.ormer.Raw("delete from checksum").Exec()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var checksums []checksum
|
||||
for k, v := range newSums {
|
||||
cks := checksum{ID: k, Sum: v}
|
||||
checksums = append(checksums, cks)
|
||||
}
|
||||
_, err = Db().InsertMulti(batchSize, &checksums)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
var checksums []checksum
|
||||
for k, v := range newSums {
|
||||
cks := checksum{ID: k, Sum: v}
|
||||
checksums = append(checksums, cks)
|
||||
}
|
||||
_, err = r.ormer.InsertMulti(batchSize, &checksums)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego/orm"
|
||||
"github.com/cloudsonic/sonic-server/model"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
@@ -10,8 +11,7 @@ var _ = Describe("ChecksumRepository", func() {
|
||||
var repo model.ChecksumRepository
|
||||
|
||||
BeforeEach(func() {
|
||||
Db().Delete(&checksum{ID: checkSumId})
|
||||
repo = NewCheckSumRepository()
|
||||
repo = NewCheckSumRepository(orm.NewOrm())
|
||||
err := repo.SetData(map[string]string{
|
||||
"a": "AAA", "b": "BBB",
|
||||
})
|
||||
@@ -27,7 +27,7 @@ var _ = Describe("ChecksumRepository", func() {
|
||||
})
|
||||
|
||||
It("persists data", func() {
|
||||
newRepo := NewCheckSumRepository()
|
||||
newRepo := NewCheckSumRepository(orm.NewOrm())
|
||||
sums, err := newRepo.GetData()
|
||||
Expect(err).To(BeNil())
|
||||
Expect(sums["b"]).To(Equal("BBB"))
|
||||
|
||||
@@ -7,19 +7,20 @@ import (
|
||||
"github.com/cloudsonic/sonic-server/model"
|
||||
)
|
||||
|
||||
type genreRepository struct{}
|
||||
type genreRepository struct {
|
||||
ormer orm.Ormer
|
||||
}
|
||||
|
||||
func NewGenreRepository() model.GenreRepository {
|
||||
return &genreRepository{}
|
||||
func NewGenreRepository(o orm.Ormer) model.GenreRepository {
|
||||
return &genreRepository{ormer: o}
|
||||
}
|
||||
|
||||
func (r genreRepository) GetAll() (model.Genres, error) {
|
||||
o := Db()
|
||||
genres := make(map[string]model.Genre)
|
||||
|
||||
// Collect SongCount
|
||||
var res []orm.Params
|
||||
_, err := o.Raw("select genre, count(*) as c from media_file group by genre").Values(&res)
|
||||
_, err := r.ormer.Raw("select genre, count(*) as c from media_file group by genre").Values(&res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -35,7 +36,7 @@ func (r genreRepository) GetAll() (model.Genres, error) {
|
||||
}
|
||||
|
||||
// Collect AlbumCount
|
||||
_, err = o.Raw("select genre, count(*) as c from album group by genre").Values(&res)
|
||||
_, err = r.ormer.Raw("select genre, count(*) as c from album group by genre").Values(&res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego/orm"
|
||||
"github.com/cloudsonic/sonic-server/model"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
@@ -10,7 +11,7 @@ var _ = Describe("GenreRepository", func() {
|
||||
var repo model.GenreRepository
|
||||
|
||||
BeforeEach(func() {
|
||||
repo = NewGenreRepository()
|
||||
repo = NewGenreRepository(orm.NewOrm())
|
||||
})
|
||||
|
||||
It("returns all records", func() {
|
||||
|
||||
@@ -41,28 +41,27 @@ type mediaFileRepository struct {
|
||||
searchableRepository
|
||||
}
|
||||
|
||||
func NewMediaFileRepository() model.MediaFileRepository {
|
||||
func NewMediaFileRepository(o orm.Ormer) model.MediaFileRepository {
|
||||
r := &mediaFileRepository{}
|
||||
r.ormer = o
|
||||
r.tableName = "media_file"
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *mediaFileRepository) Put(m *model.MediaFile, overrideAnnotation bool) error {
|
||||
tm := mediaFile(*m)
|
||||
return withTx(func(o orm.Ormer) error {
|
||||
if !overrideAnnotation {
|
||||
// Don't update media annotation fields (playcount, starred, etc..)
|
||||
return r.put(o, m.ID, m.Title, &tm, "path", "title", "album", "artist", "artist_id", "album_artist",
|
||||
"album_id", "has_cover_art", "track_number", "disc_number", "year", "size", "suffix", "duration",
|
||||
"bit_rate", "genre", "compilation", "updated_at")
|
||||
}
|
||||
return r.put(o, m.ID, m.Title, &tm)
|
||||
})
|
||||
if !overrideAnnotation {
|
||||
// Don't update media annotation fields (playcount, starred, etc..)
|
||||
return r.put(m.ID, m.Title, &tm, "path", "title", "album", "artist", "artist_id", "album_artist",
|
||||
"album_id", "has_cover_art", "track_number", "disc_number", "year", "size", "suffix", "duration",
|
||||
"bit_rate", "genre", "compilation", "updated_at")
|
||||
}
|
||||
return r.put(m.ID, m.Title, &tm)
|
||||
}
|
||||
|
||||
func (r *mediaFileRepository) Get(id string) (*model.MediaFile, error) {
|
||||
tm := mediaFile{ID: id}
|
||||
err := Db().Read(&tm)
|
||||
err := r.ormer.Read(&tm)
|
||||
if err == orm.ErrNoRows {
|
||||
return nil, model.ErrNotFound
|
||||
}
|
||||
@@ -83,7 +82,7 @@ func (r *mediaFileRepository) toMediaFiles(all []mediaFile) model.MediaFiles {
|
||||
|
||||
func (r *mediaFileRepository) FindByAlbum(albumId string) (model.MediaFiles, error) {
|
||||
var mfs []mediaFile
|
||||
_, err := r.newQuery(Db()).Filter("album_id", albumId).OrderBy("disc_number", "track_number").All(&mfs)
|
||||
_, err := r.newQuery().Filter("album_id", albumId).OrderBy("disc_number", "track_number").All(&mfs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -92,7 +91,7 @@ func (r *mediaFileRepository) FindByAlbum(albumId string) (model.MediaFiles, err
|
||||
|
||||
func (r *mediaFileRepository) FindByPath(path string) (model.MediaFiles, error) {
|
||||
var mfs []mediaFile
|
||||
_, err := r.newQuery(Db()).Filter("path__istartswith", path).OrderBy("disc_number", "track_number").All(&mfs)
|
||||
_, err := r.newQuery().Filter("path__istartswith", path).OrderBy("disc_number", "track_number").All(&mfs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -109,10 +108,9 @@ func (r *mediaFileRepository) FindByPath(path string) (model.MediaFiles, error)
|
||||
}
|
||||
|
||||
func (r *mediaFileRepository) DeleteByPath(path string) error {
|
||||
o := Db()
|
||||
var mfs []mediaFile
|
||||
// TODO Paginate this (and all other situations similar)
|
||||
_, err := r.newQuery(o).Filter("path__istartswith", path).OrderBy("disc_number", "track_number").All(&mfs)
|
||||
_, err := r.newQuery().Filter("path__istartswith", path).OrderBy("disc_number", "track_number").All(&mfs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -128,13 +126,13 @@ func (r *mediaFileRepository) DeleteByPath(path string) error {
|
||||
if len(filtered) == 0 {
|
||||
return nil
|
||||
}
|
||||
_, err = r.newQuery(o).Filter("id__in", filtered).Delete()
|
||||
_, err = r.newQuery().Filter("id__in", filtered).Delete()
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *mediaFileRepository) GetStarred(options ...model.QueryOptions) (model.MediaFiles, error) {
|
||||
var starred []mediaFile
|
||||
_, err := r.newQuery(Db(), options...).Filter("starred", true).All(&starred)
|
||||
_, err := r.newQuery(options...).Filter("starred", true).All(&starred)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -149,7 +147,7 @@ func (r *mediaFileRepository) SetStar(starred bool, ids ...string) error {
|
||||
if starred {
|
||||
starredAt = time.Now()
|
||||
}
|
||||
_, err := r.newQuery(Db()).Filter("id__in", ids).Update(orm.Params{
|
||||
_, err := r.newQuery().Filter("id__in", ids).Update(orm.Params{
|
||||
"starred": starred,
|
||||
"starred_at": starredAt,
|
||||
})
|
||||
@@ -160,12 +158,12 @@ func (r *mediaFileRepository) SetRating(rating int, ids ...string) error {
|
||||
if len(ids) == 0 {
|
||||
return model.ErrNotFound
|
||||
}
|
||||
_, err := r.newQuery(Db()).Filter("id__in", ids).Update(orm.Params{"rating": rating})
|
||||
_, err := r.newQuery().Filter("id__in", ids).Update(orm.Params{"rating": rating})
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *mediaFileRepository) MarkAsPlayed(id string, playDate time.Time) error {
|
||||
_, err := r.newQuery(Db()).Filter("id", id).Update(orm.Params{
|
||||
_, err := r.newQuery().Filter("id", id).Update(orm.Params{
|
||||
"play_count": orm.ColValue(orm.ColAdd, 1),
|
||||
"play_date": playDate,
|
||||
})
|
||||
@@ -173,12 +171,10 @@ func (r *mediaFileRepository) MarkAsPlayed(id string, playDate time.Time) error
|
||||
}
|
||||
|
||||
func (r *mediaFileRepository) PurgeInactive(activeList model.MediaFiles) error {
|
||||
return withTx(func(o orm.Ormer) error {
|
||||
_, err := r.purgeInactive(o, activeList, func(item interface{}) string {
|
||||
return item.(model.MediaFile).ID
|
||||
})
|
||||
return err
|
||||
_, err := r.purgeInactive(activeList, func(item interface{}) string {
|
||||
return item.(model.MediaFile).ID
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *mediaFileRepository) Search(q string, offset int, size int) (model.MediaFiles, error) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/astaxie/beego/orm"
|
||||
"github.com/cloudsonic/sonic-server/model"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
@@ -13,7 +14,7 @@ var _ = Describe("MediaFileRepository", func() {
|
||||
var repo model.MediaFileRepository
|
||||
|
||||
BeforeEach(func() {
|
||||
repo = NewMediaFileRepository()
|
||||
repo = NewMediaFileRepository(orm.NewOrm())
|
||||
})
|
||||
|
||||
Describe("FindByPath", func() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego/orm"
|
||||
"github.com/cloudsonic/sonic-server/conf"
|
||||
"github.com/cloudsonic/sonic-server/model"
|
||||
)
|
||||
@@ -9,17 +10,13 @@ type mediaFolderRepository struct {
|
||||
model.MediaFolderRepository
|
||||
}
|
||||
|
||||
func NewMediaFolderRepository() model.MediaFolderRepository {
|
||||
func NewMediaFolderRepository(o orm.Ormer) model.MediaFolderRepository {
|
||||
return &mediaFolderRepository{}
|
||||
}
|
||||
|
||||
func (*mediaFolderRepository) GetAll() (model.MediaFolders, error) {
|
||||
mediaFolder := model.MediaFolder{ID: "0", Path: conf.Sonic.MusicFolder}
|
||||
if conf.Sonic.DevUseFileScanner {
|
||||
mediaFolder.Name = "Music Library"
|
||||
} else {
|
||||
mediaFolder.Name = "iTunes Library"
|
||||
}
|
||||
mediaFolder.Name = "Music Library"
|
||||
result := make(model.MediaFolders, 1)
|
||||
result[0] = mediaFolder
|
||||
return result, nil
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package persistence
|
||||
|
||||
import "github.com/cloudsonic/sonic-server/model"
|
||||
|
||||
type MockDataStore struct {
|
||||
MockedGenre model.GenreRepository
|
||||
MockedAlbum model.AlbumRepository
|
||||
MockedArtist model.ArtistRepository
|
||||
MockedMediaFile model.MediaFileRepository
|
||||
}
|
||||
|
||||
func (db *MockDataStore) Album() model.AlbumRepository {
|
||||
if db.MockedAlbum == nil {
|
||||
db.MockedAlbum = CreateMockAlbumRepo()
|
||||
}
|
||||
return db.MockedAlbum
|
||||
}
|
||||
|
||||
func (db *MockDataStore) Artist() model.ArtistRepository {
|
||||
if db.MockedArtist == nil {
|
||||
db.MockedArtist = CreateMockArtistRepo()
|
||||
}
|
||||
return db.MockedArtist
|
||||
}
|
||||
|
||||
func (db *MockDataStore) MediaFile() model.MediaFileRepository {
|
||||
if db.MockedMediaFile == nil {
|
||||
db.MockedMediaFile = CreateMockMediaFileRepo()
|
||||
}
|
||||
return db.MockedMediaFile
|
||||
}
|
||||
|
||||
func (db *MockDataStore) MediaFolder() model.MediaFolderRepository {
|
||||
return struct{ model.MediaFolderRepository }{}
|
||||
}
|
||||
|
||||
func (db *MockDataStore) Genre() model.GenreRepository {
|
||||
if db.MockedGenre != nil {
|
||||
return db.MockedGenre
|
||||
}
|
||||
return struct{ model.GenreRepository }{}
|
||||
}
|
||||
|
||||
func (db *MockDataStore) Playlist() model.PlaylistRepository {
|
||||
return struct{ model.PlaylistRepository }{}
|
||||
}
|
||||
|
||||
func (db *MockDataStore) Property() model.PropertyRepository {
|
||||
return struct{ model.PropertyRepository }{}
|
||||
}
|
||||
|
||||
func (db *MockDataStore) WithTx(block func(db model.DataStore) error) error {
|
||||
return block(db)
|
||||
}
|
||||
+54
-12
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/astaxie/beego/orm"
|
||||
"github.com/cloudsonic/sonic-server/conf"
|
||||
"github.com/cloudsonic/sonic-server/log"
|
||||
"github.com/cloudsonic/sonic-server/model"
|
||||
_ "github.com/lib/pq"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
@@ -19,7 +20,11 @@ var (
|
||||
driver = "sqlite3"
|
||||
)
|
||||
|
||||
func Db() orm.Ormer {
|
||||
type SQLStore struct {
|
||||
orm orm.Ormer
|
||||
}
|
||||
|
||||
func New() model.DataStore {
|
||||
once.Do(func() {
|
||||
dbPath := conf.Sonic.DbPath
|
||||
if dbPath == ":memory:" {
|
||||
@@ -31,17 +36,47 @@ func Db() orm.Ormer {
|
||||
}
|
||||
log.Debug("Opening DB from: "+dbPath, "driver", driver)
|
||||
})
|
||||
return orm.NewOrm()
|
||||
return &SQLStore{}
|
||||
}
|
||||
|
||||
func withTx(block func(orm.Ormer) error) error {
|
||||
func (db *SQLStore) Album() model.AlbumRepository {
|
||||
return NewAlbumRepository(db.getOrmer())
|
||||
}
|
||||
|
||||
func (db *SQLStore) Artist() model.ArtistRepository {
|
||||
return NewArtistRepository(db.getOrmer())
|
||||
}
|
||||
|
||||
func (db *SQLStore) MediaFile() model.MediaFileRepository {
|
||||
return NewMediaFileRepository(db.getOrmer())
|
||||
}
|
||||
|
||||
func (db *SQLStore) MediaFolder() model.MediaFolderRepository {
|
||||
return NewMediaFolderRepository(db.getOrmer())
|
||||
}
|
||||
|
||||
func (db *SQLStore) Genre() model.GenreRepository {
|
||||
return NewGenreRepository(db.getOrmer())
|
||||
}
|
||||
|
||||
func (db *SQLStore) Playlist() model.PlaylistRepository {
|
||||
return NewPlaylistRepository(db.getOrmer())
|
||||
}
|
||||
|
||||
func (db *SQLStore) Property() model.PropertyRepository {
|
||||
return NewPropertyRepository(db.getOrmer())
|
||||
}
|
||||
|
||||
func (db *SQLStore) WithTx(block func(tx model.DataStore) error) error {
|
||||
o := orm.NewOrm()
|
||||
err := o.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = block(o)
|
||||
newDb := &SQLStore{orm: o}
|
||||
err = block(newDb)
|
||||
|
||||
if err != nil {
|
||||
err2 := o.Rollback()
|
||||
if err2 != nil {
|
||||
@@ -57,15 +92,11 @@ func withTx(block func(orm.Ormer) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func collectField(collection interface{}, getValue func(item interface{}) string) []string {
|
||||
s := reflect.ValueOf(collection)
|
||||
result := make([]string, s.Len())
|
||||
|
||||
for i := 0; i < s.Len(); i++ {
|
||||
result[i] = getValue(s.Index(i).Interface())
|
||||
func (db *SQLStore) getOrmer() orm.Ormer {
|
||||
if db.orm == nil {
|
||||
return orm.NewOrm()
|
||||
}
|
||||
|
||||
return result
|
||||
return db.orm
|
||||
}
|
||||
|
||||
func initORM(dbPath string) error {
|
||||
@@ -87,3 +118,14 @@ func initORM(dbPath string) error {
|
||||
}
|
||||
return orm.RunSyncdb("default", false, verbose)
|
||||
}
|
||||
|
||||
func collectField(collection interface{}, getValue func(item interface{}) string) []string {
|
||||
s := reflect.ValueOf(collection)
|
||||
result := make([]string, s.Len())
|
||||
|
||||
for i := 0; i < s.Len(); i++ {
|
||||
result[i] = getValue(s.Index(i).Interface())
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -57,19 +57,19 @@ var _ = Describe("Initialize test DB", func() {
|
||||
//conf.Sonic.DbPath, _ = ioutil.TempDir("", "cloudsonic_tests")
|
||||
//os.MkdirAll(conf.Sonic.DbPath, 0700)
|
||||
conf.Sonic.DbPath = ":memory:"
|
||||
Db()
|
||||
artistRepo := NewArtistRepository()
|
||||
ds := New()
|
||||
artistRepo := ds.Artist()
|
||||
for _, a := range testArtists {
|
||||
artistRepo.Put(&a)
|
||||
}
|
||||
albumRepository := NewAlbumRepository()
|
||||
albumRepository := ds.Album()
|
||||
for _, a := range testAlbums {
|
||||
err := albumRepository.Put(&a)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
mediaFileRepository := NewMediaFileRepository()
|
||||
mediaFileRepository := ds.MediaFile()
|
||||
for _, s := range testSongs {
|
||||
err := mediaFileRepository.Put(&s, true)
|
||||
if err != nil {
|
||||
|
||||
@@ -22,22 +22,21 @@ type playlistRepository struct {
|
||||
sqlRepository
|
||||
}
|
||||
|
||||
func NewPlaylistRepository() model.PlaylistRepository {
|
||||
func NewPlaylistRepository(o orm.Ormer) model.PlaylistRepository {
|
||||
r := &playlistRepository{}
|
||||
r.ormer = o
|
||||
r.tableName = "playlist"
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *playlistRepository) Put(p *model.Playlist) error {
|
||||
tp := r.fromDomain(p)
|
||||
return withTx(func(o orm.Ormer) error {
|
||||
return r.put(o, p.ID, &tp)
|
||||
})
|
||||
return r.put(p.ID, &tp)
|
||||
}
|
||||
|
||||
func (r *playlistRepository) Get(id string) (*model.Playlist, error) {
|
||||
tp := &playlist{ID: id}
|
||||
err := Db().Read(tp)
|
||||
err := r.ormer.Read(tp)
|
||||
if err == orm.ErrNoRows {
|
||||
return nil, model.ErrNotFound
|
||||
}
|
||||
@@ -50,7 +49,7 @@ func (r *playlistRepository) Get(id string) (*model.Playlist, error) {
|
||||
|
||||
func (r *playlistRepository) GetAll(options ...model.QueryOptions) (model.Playlists, error) {
|
||||
var all []playlist
|
||||
_, err := r.newQuery(Db(), options...).All(&all)
|
||||
_, err := r.newQuery(options...).All(&all)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -66,12 +65,10 @@ func (r *playlistRepository) toPlaylists(all []playlist) (model.Playlists, error
|
||||
}
|
||||
|
||||
func (r *playlistRepository) PurgeInactive(activeList model.Playlists) ([]string, error) {
|
||||
return nil, withTx(func(o orm.Ormer) error {
|
||||
_, err := r.purgeInactive(o, activeList, func(item interface{}) string {
|
||||
return item.(model.Playlist).ID
|
||||
})
|
||||
return err
|
||||
_, err := r.purgeInactive(activeList, func(item interface{}) string {
|
||||
return item.(model.Playlist).ID
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (r *playlistRepository) toDomain(p *playlist) model.Playlist {
|
||||
|
||||
@@ -14,27 +14,28 @@ type propertyRepository struct {
|
||||
sqlRepository
|
||||
}
|
||||
|
||||
func NewPropertyRepository() model.PropertyRepository {
|
||||
func NewPropertyRepository(o orm.Ormer) model.PropertyRepository {
|
||||
r := &propertyRepository{}
|
||||
r.ormer = o
|
||||
r.tableName = "property"
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *propertyRepository) Put(id string, value string) error {
|
||||
p := &property{ID: id, Value: value}
|
||||
num, err := Db().Update(p)
|
||||
num, err := r.ormer.Update(p)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if num == 0 {
|
||||
_, err = Db().Insert(p)
|
||||
_, err = r.ormer.Insert(p)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *propertyRepository) Get(id string) (string, error) {
|
||||
p := &property{ID: id}
|
||||
err := Db().Read(p)
|
||||
err := r.ormer.Read(p)
|
||||
if err == orm.ErrNoRows {
|
||||
return "", model.ErrNotFound
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego/orm"
|
||||
"github.com/cloudsonic/sonic-server/model"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
@@ -10,7 +11,7 @@ var _ = Describe("PropertyRepository", func() {
|
||||
var repo model.PropertyRepository
|
||||
|
||||
BeforeEach(func() {
|
||||
repo = NewPropertyRepository()
|
||||
repo = NewPropertyRepository(orm.NewOrm())
|
||||
repo.(*propertyRepository).DeleteAll()
|
||||
})
|
||||
|
||||
|
||||
@@ -20,59 +20,57 @@ type searchableRepository struct {
|
||||
}
|
||||
|
||||
func (r *searchableRepository) DeleteAll() error {
|
||||
return withTx(func(o orm.Ormer) error {
|
||||
_, err := r.newQuery(Db()).Filter("id__isnull", false).Delete()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.removeAllFromIndex(o, r.tableName)
|
||||
})
|
||||
_, err := r.newQuery().Filter("id__isnull", false).Delete()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.removeAllFromIndex(r.ormer, r.tableName)
|
||||
}
|
||||
|
||||
func (r *searchableRepository) put(o orm.Ormer, id string, textToIndex string, a interface{}, fields ...string) error {
|
||||
c, err := r.newQuery(o).Filter("id", id).Count()
|
||||
func (r *searchableRepository) put(id string, textToIndex string, a interface{}, fields ...string) error {
|
||||
c, err := r.newQuery().Filter("id", id).Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c == 0 {
|
||||
err = r.insert(o, a)
|
||||
err = r.insert(a)
|
||||
if err != nil && err.Error() == "LastInsertId is not supported by this driver" {
|
||||
err = nil
|
||||
}
|
||||
} else {
|
||||
_, err = o.Update(a, fields...)
|
||||
_, err = r.ormer.Update(a, fields...)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.addToIndex(o, r.tableName, id, textToIndex)
|
||||
return r.addToIndex(r.tableName, id, textToIndex)
|
||||
}
|
||||
|
||||
func (r *searchableRepository) purgeInactive(o orm.Ormer, activeList interface{}, getId func(item interface{}) string) ([]string, error) {
|
||||
idsToDelete, err := r.sqlRepository.purgeInactive(o, activeList, getId)
|
||||
func (r *searchableRepository) purgeInactive(activeList interface{}, getId func(item interface{}) string) ([]string, error) {
|
||||
idsToDelete, err := r.sqlRepository.purgeInactive(activeList, getId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return idsToDelete, r.removeFromIndex(o, r.tableName, idsToDelete)
|
||||
return idsToDelete, r.removeFromIndex(r.tableName, idsToDelete)
|
||||
}
|
||||
|
||||
func (r *searchableRepository) addToIndex(o orm.Ormer, table, id, text string) error {
|
||||
func (r *searchableRepository) addToIndex(table, id, text string) error {
|
||||
item := Search{ID: id, Table: table}
|
||||
err := o.Read(&item)
|
||||
err := r.ormer.Read(&item)
|
||||
if err != nil && err != orm.ErrNoRows {
|
||||
return err
|
||||
}
|
||||
sanitizedText := strings.TrimSpace(sanitize.Accents(strings.ToLower(text)))
|
||||
item = Search{ID: id, Table: table, FullText: sanitizedText}
|
||||
if err == orm.ErrNoRows {
|
||||
err = r.insert(o, &item)
|
||||
err = r.insert(&item)
|
||||
} else {
|
||||
_, err = o.Update(&item)
|
||||
_, err = r.ormer.Update(&item)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *searchableRepository) removeFromIndex(o orm.Ormer, table string, ids []string) error {
|
||||
func (r *searchableRepository) removeFromIndex(table string, ids []string) error {
|
||||
var offset int
|
||||
for {
|
||||
var subset = paginateSlice(ids, offset, batchSize)
|
||||
@@ -81,7 +79,7 @@ func (r *searchableRepository) removeFromIndex(o orm.Ormer, table string, ids []
|
||||
}
|
||||
log.Trace("Deleting searchable items", "table", table, "num", len(subset), "from", offset)
|
||||
offset += len(subset)
|
||||
_, err := o.QueryTable(&Search{}).Filter("table", table).Filter("id__in", subset).Delete()
|
||||
_, err := r.ormer.QueryTable(&Search{}).Filter("table", table).Filter("id__in", subset).Delete()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -116,6 +114,6 @@ func (r *searchableRepository) doSearch(table string, q string, offset, size int
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = Db().Raw(sql, args...).QueryRows(results)
|
||||
_, err = r.ormer.Raw(sql, args...).QueryRows(results)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -8,10 +8,11 @@ import (
|
||||
|
||||
type sqlRepository struct {
|
||||
tableName string
|
||||
ormer orm.Ormer
|
||||
}
|
||||
|
||||
func (r *sqlRepository) newQuery(o orm.Ormer, options ...model.QueryOptions) orm.QuerySeter {
|
||||
q := o.QueryTable(r.tableName)
|
||||
func (r *sqlRepository) newQuery(options ...model.QueryOptions) orm.QuerySeter {
|
||||
q := r.ormer.QueryTable(r.tableName)
|
||||
if len(options) > 0 {
|
||||
opts := options[0]
|
||||
q = q.Offset(opts.Offset)
|
||||
@@ -30,17 +31,17 @@ func (r *sqlRepository) newQuery(o orm.Ormer, options ...model.QueryOptions) orm
|
||||
}
|
||||
|
||||
func (r *sqlRepository) CountAll() (int64, error) {
|
||||
return r.newQuery(Db()).Count()
|
||||
return r.newQuery().Count()
|
||||
}
|
||||
|
||||
func (r *sqlRepository) Exists(id string) (bool, error) {
|
||||
c, err := r.newQuery(Db()).Filter("id", id).Count()
|
||||
c, err := r.newQuery().Filter("id", id).Count()
|
||||
return c == 1, err
|
||||
}
|
||||
|
||||
// TODO This is used to generate random lists. Can be optimized in SQL: https://stackoverflow.com/a/19419
|
||||
func (r *sqlRepository) GetAllIds() ([]string, error) {
|
||||
qs := r.newQuery(Db())
|
||||
qs := r.newQuery()
|
||||
var values []orm.Params
|
||||
num, err := qs.Values(&values, "id")
|
||||
if num == 0 {
|
||||
@@ -55,27 +56,27 @@ func (r *sqlRepository) GetAllIds() ([]string, error) {
|
||||
}
|
||||
|
||||
// "Hack" to bypass Postgres driver limitation
|
||||
func (r *sqlRepository) insert(o orm.Ormer, record interface{}) error {
|
||||
_, err := o.Insert(record)
|
||||
func (r *sqlRepository) insert(record interface{}) error {
|
||||
_, err := r.ormer.Insert(record)
|
||||
if err != nil && err.Error() != "LastInsertId is not supported by this driver" {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *sqlRepository) put(o orm.Ormer, id string, a interface{}) error {
|
||||
c, err := r.newQuery(o).Filter("id", id).Count()
|
||||
func (r *sqlRepository) put(id string, a interface{}) error {
|
||||
c, err := r.newQuery().Filter("id", id).Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c == 0 {
|
||||
err = r.insert(o, a)
|
||||
err = r.insert(a)
|
||||
if err != nil && err.Error() == "LastInsertId is not supported by this driver" {
|
||||
err = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
_, err = o.Update(a)
|
||||
_, err = r.ormer.Update(a)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -113,18 +114,16 @@ func difference(slice1 []string, slice2 []string) []string {
|
||||
}
|
||||
|
||||
func (r *sqlRepository) Delete(id string) error {
|
||||
_, err := r.newQuery(Db()).Filter("id", id).Delete()
|
||||
_, err := r.newQuery().Filter("id", id).Delete()
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *sqlRepository) DeleteAll() error {
|
||||
return withTx(func(o orm.Ormer) error {
|
||||
_, err := r.newQuery(Db()).Filter("id__isnull", false).Delete()
|
||||
return err
|
||||
})
|
||||
_, err := r.newQuery().Filter("id__isnull", false).Delete()
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *sqlRepository) purgeInactive(o orm.Ormer, activeList interface{}, getId func(item interface{}) string) ([]string, error) {
|
||||
func (r *sqlRepository) purgeInactive(activeList interface{}, getId func(item interface{}) string) ([]string, error) {
|
||||
allIds, err := r.GetAllIds()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -144,7 +143,7 @@ func (r *sqlRepository) purgeInactive(o orm.Ormer, activeList interface{}, getId
|
||||
}
|
||||
log.Trace("-- Purging inactive records", "table", r.tableName, "num", len(subset), "from", offset)
|
||||
offset += len(subset)
|
||||
_, err := r.newQuery(o).Filter("id__in", subset).Delete()
|
||||
_, err := r.newQuery().Filter("id__in", subset).Delete()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -5,12 +5,13 @@ import (
|
||||
)
|
||||
|
||||
var Set = wire.NewSet(
|
||||
NewArtistRepository,
|
||||
NewMediaFileRepository,
|
||||
NewAlbumRepository,
|
||||
NewCheckSumRepository,
|
||||
NewPropertyRepository,
|
||||
NewPlaylistRepository,
|
||||
NewMediaFolderRepository,
|
||||
NewGenreRepository,
|
||||
//NewArtistRepository,
|
||||
//NewMediaFileRepository,
|
||||
//NewAlbumRepository,
|
||||
//NewCheckSumRepository,
|
||||
//NewPropertyRepository,
|
||||
//NewPlaylistRepository,
|
||||
//NewMediaFolderRepository,
|
||||
//NewGenreRepository,
|
||||
New,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user