2020-01-15 00:23:29 +01:00
|
|
|
package persistence
|
2020-01-13 00:55:55 +01:00
|
|
|
|
|
|
|
import (
|
2020-01-28 14:22:17 +01:00
|
|
|
"context"
|
2020-12-13 20:05:48 +01:00
|
|
|
"fmt"
|
2020-04-18 01:56:52 +02:00
|
|
|
"strings"
|
2020-01-13 00:55:55 +01:00
|
|
|
|
2020-01-28 14:22:17 +01:00
|
|
|
. "github.com/Masterminds/squirrel"
|
2022-07-30 18:43:48 +02:00
|
|
|
"github.com/beego/beego/v2/client/orm"
|
2020-01-28 14:22:17 +01:00
|
|
|
"github.com/deluan/rest"
|
2020-06-13 19:55:58 +02:00
|
|
|
"github.com/navidrome/navidrome/conf"
|
2020-01-31 15:53:19 +01:00
|
|
|
"github.com/navidrome/navidrome/log"
|
2020-01-24 01:44:08 +01:00
|
|
|
"github.com/navidrome/navidrome/model"
|
2020-01-13 00:55:55 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
type albumRepository struct {
|
2020-01-28 14:22:17 +01:00
|
|
|
sqlRepository
|
2020-03-22 01:00:46 +01:00
|
|
|
sqlRestful
|
2020-01-13 00:55:55 +01:00
|
|
|
}
|
|
|
|
|
2022-07-30 18:43:48 +02:00
|
|
|
func NewAlbumRepository(ctx context.Context, o orm.QueryExecutor) model.AlbumRepository {
|
2020-01-13 00:55:55 +01:00
|
|
|
r := &albumRepository{}
|
2020-01-28 14:22:17 +01:00
|
|
|
r.ctx = ctx
|
2020-01-19 21:37:41 +01:00
|
|
|
r.ormer = o
|
2020-01-31 15:53:19 +01:00
|
|
|
r.tableName = "album"
|
2020-03-19 20:02:11 +01:00
|
|
|
r.sortMappings = map[string]string{
|
2021-03-12 23:49:47 +01:00
|
|
|
"name": "order_album_name asc, order_album_artist_name asc",
|
|
|
|
"artist": "compilation asc, order_album_artist_name asc, order_album_name asc",
|
|
|
|
"random": "RANDOM()",
|
|
|
|
"max_year": "max_year asc, name, order_album_name asc",
|
|
|
|
"recently_added": recentlyAddedSort(),
|
2020-03-19 20:02:11 +01:00
|
|
|
}
|
2020-03-20 02:09:57 +01:00
|
|
|
r.filterMappings = map[string]filterFunc{
|
2021-07-17 00:18:22 +02:00
|
|
|
"id": idFilter(r.tableName),
|
2020-07-28 14:49:28 +02:00
|
|
|
"name": fullTextFilter,
|
|
|
|
"compilation": booleanFilter,
|
|
|
|
"artist_id": artistFilter,
|
|
|
|
"year": yearFilter,
|
|
|
|
"recently_played": recentlyPlayedFilter,
|
2020-08-14 19:35:28 +02:00
|
|
|
"starred": booleanFilter,
|
2021-04-07 17:04:36 +02:00
|
|
|
"has_rating": hasRatingFilter,
|
2020-03-20 02:09:57 +01:00
|
|
|
}
|
2020-03-19 20:02:11 +01:00
|
|
|
|
2020-01-13 00:55:55 +01:00
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2021-03-12 23:49:47 +01:00
|
|
|
func recentlyAddedSort() string {
|
|
|
|
if conf.Server.RecentlyAddedByModTime {
|
|
|
|
return "updated_at"
|
|
|
|
}
|
|
|
|
return "created_at"
|
|
|
|
}
|
|
|
|
|
2020-07-28 14:49:28 +02:00
|
|
|
func recentlyPlayedFilter(field string, value interface{}) Sqlizer {
|
|
|
|
return Gt{"play_count": 0}
|
|
|
|
}
|
|
|
|
|
2021-04-07 17:04:36 +02:00
|
|
|
func hasRatingFilter(field string, value interface{}) Sqlizer {
|
|
|
|
return Gt{"rating": 0}
|
|
|
|
}
|
|
|
|
|
2020-03-28 02:06:02 +01:00
|
|
|
func yearFilter(field string, value interface{}) Sqlizer {
|
|
|
|
return Or{
|
|
|
|
And{
|
|
|
|
Gt{"min_year": 0},
|
|
|
|
LtOrEq{"min_year": value},
|
|
|
|
GtOrEq{"max_year": value},
|
|
|
|
},
|
|
|
|
Eq{"max_year": value},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-25 23:51:13 +01:00
|
|
|
func artistFilter(field string, value interface{}) Sqlizer {
|
2020-12-13 20:05:48 +01:00
|
|
|
return Like{"all_artist_ids": fmt.Sprintf("%%%s%%", value)}
|
2020-03-25 23:51:13 +01:00
|
|
|
}
|
|
|
|
|
2020-01-28 14:22:17 +01:00
|
|
|
func (r *albumRepository) CountAll(options ...model.QueryOptions) (int64, error) {
|
2021-07-17 02:20:33 +02:00
|
|
|
sql := r.newSelectWithAnnotation("album.id")
|
|
|
|
sql = r.withGenres(sql)
|
2021-07-17 01:41:49 +02:00
|
|
|
return r.count(sql, options...)
|
2020-01-28 14:22:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r *albumRepository) Exists(id string) (bool, error) {
|
2021-07-17 02:20:33 +02:00
|
|
|
return r.exists(Select().Where(Eq{"album.id": id}))
|
2020-01-28 14:22:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r *albumRepository) selectAlbum(options ...model.QueryOptions) SelectBuilder {
|
2021-07-17 02:20:33 +02:00
|
|
|
sql := r.newSelectWithAnnotation("album.id", options...).Columns("album.*")
|
2021-11-03 02:18:53 +01:00
|
|
|
if len(options) > 0 && options[0].Filters != nil {
|
|
|
|
s, _, _ := options[0].Filters.ToSql()
|
|
|
|
// If there's any reference of genre in the filter, joins with genre
|
|
|
|
if strings.Contains(s, "genre") {
|
|
|
|
sql = r.withGenres(sql)
|
|
|
|
// If there's no filter on genre_id, group the results by media_file.id
|
|
|
|
if !strings.Contains(s, "genre_id") {
|
|
|
|
sql = sql.GroupBy("album.id")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return sql
|
2020-01-13 00:55:55 +01:00
|
|
|
}
|
|
|
|
|
2020-01-15 04:22:34 +01:00
|
|
|
func (r *albumRepository) Get(id string) (*model.Album, error) {
|
2021-07-17 02:20:33 +02:00
|
|
|
sq := r.selectAlbum().Where(Eq{"album.id": id})
|
2020-05-22 21:23:42 +02:00
|
|
|
var res model.Albums
|
|
|
|
if err := r.queryAll(sq, &res); err != nil {
|
2020-01-13 00:55:55 +01:00
|
|
|
return nil, err
|
|
|
|
}
|
2020-05-22 21:23:42 +02:00
|
|
|
if len(res) == 0 {
|
|
|
|
return nil, model.ErrNotFound
|
|
|
|
}
|
2021-07-16 23:15:34 +02:00
|
|
|
err := r.loadAlbumGenres(&res)
|
|
|
|
return &res[0], err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *albumRepository) Put(m *model.Album) error {
|
|
|
|
_, err := r.put(m.ID, m)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-08-01 07:21:20 +02:00
|
|
|
return r.updateGenres(m.ID, r.tableName, m.Genres)
|
2020-01-13 00:55:55 +01:00
|
|
|
}
|
|
|
|
|
2020-01-15 04:22:34 +01:00
|
|
|
func (r *albumRepository) GetAll(options ...model.QueryOptions) (model.Albums, error) {
|
2021-11-03 02:18:53 +01:00
|
|
|
res, err := r.GetAllWithoutGenres(options...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
err = r.loadAlbumGenres(&res)
|
|
|
|
return res, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *albumRepository) GetAllWithoutGenres(options ...model.QueryOptions) (model.Albums, error) {
|
2021-07-17 02:20:33 +02:00
|
|
|
sq := r.selectAlbum(options...)
|
2020-01-31 22:47:13 +01:00
|
|
|
res := model.Albums{}
|
2020-01-28 14:22:17 +01:00
|
|
|
err := r.queryAll(sq, &res)
|
2021-07-16 23:15:34 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-01-28 14:22:17 +01:00
|
|
|
return res, err
|
2020-01-13 00:55:55 +01:00
|
|
|
}
|
|
|
|
|
2020-05-19 02:32:01 +02:00
|
|
|
func (r *albumRepository) purgeEmpty() error {
|
2020-01-31 23:57:06 +01:00
|
|
|
del := Delete(r.tableName).Where("id not in (select distinct(album_id) from media_file)")
|
|
|
|
c, err := r.executeSQL(del)
|
2020-01-31 15:53:19 +01:00
|
|
|
if err == nil {
|
|
|
|
if c > 0 {
|
2020-01-31 23:57:06 +01:00
|
|
|
log.Debug(r.ctx, "Purged empty albums", "totalDeleted", c)
|
2020-01-31 15:53:19 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return err
|
2020-01-18 05:28:11 +01:00
|
|
|
}
|
|
|
|
|
2020-01-15 04:22:34 +01:00
|
|
|
func (r *albumRepository) Search(q string, offset int, size int) (model.Albums, error) {
|
2020-01-31 22:47:13 +01:00
|
|
|
results := model.Albums{}
|
2020-01-31 21:42:48 +01:00
|
|
|
err := r.doSearch(q, offset, size, &results, "name")
|
|
|
|
return results, err
|
2020-01-28 14:22:17 +01:00
|
|
|
}
|
2020-01-13 21:41:14 +01:00
|
|
|
|
2020-01-28 14:22:17 +01:00
|
|
|
func (r *albumRepository) Count(options ...rest.QueryOptions) (int64, error) {
|
|
|
|
return r.CountAll(r.parseRestOptions(options...))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *albumRepository) Read(id string) (interface{}, error) {
|
|
|
|
return r.Get(id)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *albumRepository) ReadAll(options ...rest.QueryOptions) (interface{}, error) {
|
|
|
|
return r.GetAll(r.parseRestOptions(options...))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *albumRepository) EntityName() string {
|
|
|
|
return "album"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *albumRepository) NewInstance() interface{} {
|
|
|
|
return &model.Album{}
|
2020-01-13 00:55:55 +01:00
|
|
|
}
|
|
|
|
|
2020-01-15 04:22:34 +01:00
|
|
|
var _ model.AlbumRepository = (*albumRepository)(nil)
|
2020-01-28 14:22:17 +01:00
|
|
|
var _ model.ResourceRepository = (*albumRepository)(nil)
|