2020-01-14 14:23:29 -09:00
|
|
|
package persistence
|
2020-01-12 14:36:19 -09:00
|
|
|
|
|
|
|
|
import (
|
2020-01-28 04:22:17 -09:00
|
|
|
"context"
|
2020-06-10 19:11:43 -08:00
|
|
|
"fmt"
|
2020-01-31 13:56:02 -09:00
|
|
|
"os"
|
2020-06-12 09:26:46 -08:00
|
|
|
"path/filepath"
|
2020-01-12 14:36:19 -09:00
|
|
|
|
2020-01-28 04:22:17 -09:00
|
|
|
. "github.com/Masterminds/squirrel"
|
2020-01-12 14:36:19 -09:00
|
|
|
"github.com/astaxie/beego/orm"
|
2020-01-31 13:56:02 -09:00
|
|
|
"github.com/deluan/navidrome/log"
|
2020-01-23 15:44:08 -09:00
|
|
|
"github.com/deluan/navidrome/model"
|
2020-01-28 04:22:17 -09:00
|
|
|
"github.com/deluan/rest"
|
2020-01-12 14:36:19 -09:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type mediaFileRepository struct {
|
2020-01-28 04:22:17 -09:00
|
|
|
sqlRepository
|
2020-03-21 16:00:46 -08:00
|
|
|
sqlRestful
|
2020-01-12 14:36:19 -09:00
|
|
|
}
|
|
|
|
|
|
2020-01-28 04:22:17 -09:00
|
|
|
func NewMediaFileRepository(ctx context.Context, o orm.Ormer) *mediaFileRepository {
|
2020-01-12 14:36:19 -09:00
|
|
|
r := &mediaFileRepository{}
|
2020-01-28 04:22:17 -09:00
|
|
|
r.ctx = ctx
|
2020-01-19 11:37:41 -09:00
|
|
|
r.ormer = o
|
2020-01-12 20:04:11 -09:00
|
|
|
r.tableName = "media_file"
|
2020-02-05 10:12:13 -09:00
|
|
|
r.sortMappings = map[string]string{
|
2020-04-24 13:37:28 -08:00
|
|
|
"artist": "order_artist_name asc, album asc, disc_number asc, track_number asc",
|
|
|
|
|
"album": "order_album_name asc, disc_number asc, track_number asc",
|
2020-05-05 12:17:09 -08:00
|
|
|
"random": "RANDOM()",
|
2020-02-05 10:12:13 -09:00
|
|
|
}
|
2020-03-19 18:26:18 -08:00
|
|
|
r.filterMappings = map[string]filterFunc{
|
2020-05-22 19:10:58 -08:00
|
|
|
"title": fullTextFilter,
|
|
|
|
|
"starred": booleanFilter,
|
2020-03-19 18:26:18 -08:00
|
|
|
}
|
2020-01-12 14:36:19 -09:00
|
|
|
return r
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-28 04:22:17 -09:00
|
|
|
func (r mediaFileRepository) CountAll(options ...model.QueryOptions) (int64, error) {
|
2020-05-22 20:43:45 -08:00
|
|
|
return r.count(r.newSelectWithAnnotation("media_file.id"), options...)
|
2020-01-12 14:36:19 -09:00
|
|
|
}
|
|
|
|
|
|
2020-01-28 04:22:17 -09:00
|
|
|
func (r mediaFileRepository) Exists(id string) (bool, error) {
|
|
|
|
|
return r.exists(Select().Where(Eq{"id": id}))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r mediaFileRepository) Put(m *model.MediaFile) error {
|
2020-04-24 06:13:59 -08:00
|
|
|
m.FullText = getFullText(m.Title, m.Album, m.Artist, m.AlbumArtist,
|
2020-05-12 07:17:22 -08:00
|
|
|
m.SortTitle, m.SortAlbumName, m.SortArtistName, m.SortAlbumArtistName, m.DiscSubtitle)
|
2020-01-31 11:35:06 -09:00
|
|
|
_, err := r.put(m.ID, m)
|
2020-03-19 17:43:30 -08:00
|
|
|
return err
|
2020-01-12 14:36:19 -09:00
|
|
|
}
|
|
|
|
|
|
2020-01-28 04:22:17 -09:00
|
|
|
func (r mediaFileRepository) selectMediaFile(options ...model.QueryOptions) SelectBuilder {
|
2020-01-31 17:09:23 -09:00
|
|
|
return r.newSelectWithAnnotation("media_file.id", options...).Columns("media_file.*")
|
2020-01-12 14:36:19 -09:00
|
|
|
}
|
|
|
|
|
|
2020-01-28 04:22:17 -09:00
|
|
|
func (r mediaFileRepository) Get(id string) (*model.MediaFile, error) {
|
|
|
|
|
sel := r.selectMediaFile().Where(Eq{"id": id})
|
2020-05-22 11:23:42 -08:00
|
|
|
var res model.MediaFiles
|
|
|
|
|
if err := r.queryAll(sel, &res); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if len(res) == 0 {
|
|
|
|
|
return nil, model.ErrNotFound
|
|
|
|
|
}
|
|
|
|
|
return &res[0], nil
|
2020-01-12 14:36:19 -09:00
|
|
|
}
|
|
|
|
|
|
2020-01-28 04:22:17 -09:00
|
|
|
func (r mediaFileRepository) GetAll(options ...model.QueryOptions) (model.MediaFiles, error) {
|
|
|
|
|
sq := r.selectMediaFile(options...)
|
2020-01-31 12:47:13 -09:00
|
|
|
res := model.MediaFiles{}
|
2020-01-28 04:22:17 -09:00
|
|
|
err := r.queryAll(sq, &res)
|
|
|
|
|
return res, err
|
2020-01-16 11:56:24 -09:00
|
|
|
}
|
|
|
|
|
|
2020-01-28 04:22:17 -09:00
|
|
|
func (r mediaFileRepository) FindByAlbum(albumId string) (model.MediaFiles, error) {
|
2020-01-31 05:53:19 -09:00
|
|
|
sel := r.selectMediaFile().Where(Eq{"album_id": albumId}).OrderBy("disc_number", "track_number")
|
2020-01-31 12:47:13 -09:00
|
|
|
res := model.MediaFiles{}
|
2020-01-28 04:22:17 -09:00
|
|
|
err := r.queryAll(sel, &res)
|
|
|
|
|
return res, err
|
2020-01-16 12:53:48 -09:00
|
|
|
}
|
|
|
|
|
|
2020-06-12 09:26:46 -08:00
|
|
|
// FindByPath only return mediafiles that are direct children of requested path
|
2020-01-28 04:22:17 -09:00
|
|
|
func (r mediaFileRepository) FindByPath(path string) (model.MediaFiles, error) {
|
2020-06-10 19:11:43 -08:00
|
|
|
// Query by path based on https://stackoverflow.com/a/13911906/653632
|
|
|
|
|
sel0 := r.selectMediaFile().Columns(fmt.Sprintf("substr(path, %d) AS item", len(path)+2)).
|
2020-06-12 09:26:46 -08:00
|
|
|
Where(Like{"path": filepath.Join(path, "%")})
|
2020-06-10 19:11:43 -08:00
|
|
|
sel := r.newSelect().Columns("*", "item NOT GLOB '*"+string(os.PathSeparator)+"*' AS isLast").
|
|
|
|
|
Where(Eq{"isLast": 1}).FromSelect(sel0, "sel0")
|
|
|
|
|
|
2020-01-31 12:47:13 -09:00
|
|
|
res := model.MediaFiles{}
|
2020-01-28 04:22:17 -09:00
|
|
|
err := r.queryAll(sel, &res)
|
2020-06-10 19:11:43 -08:00
|
|
|
return res, err
|
2020-01-28 04:22:17 -09:00
|
|
|
}
|
|
|
|
|
|
2020-06-12 09:26:46 -08:00
|
|
|
// FindPathsRecursively returns a list of all subfolders of basePath, recursively
|
|
|
|
|
func (r mediaFileRepository) FindPathsRecursively(basePath string) ([]string, error) {
|
|
|
|
|
// Query based on https://stackoverflow.com/a/38330814/653632
|
|
|
|
|
sel := r.newSelect().Columns("distinct rtrim(path, replace(path, '/', ''))").
|
|
|
|
|
Where(Like{"path": filepath.Join(basePath, "%")})
|
|
|
|
|
var res []string
|
|
|
|
|
err := r.queryAll(sel, &res)
|
|
|
|
|
return res, err
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-30 18:07:02 -09:00
|
|
|
func (r mediaFileRepository) GetStarred(options ...model.QueryOptions) (model.MediaFiles, error) {
|
2020-01-28 04:22:17 -09:00
|
|
|
sq := r.selectMediaFile(options...).Where("starred = true")
|
2020-01-31 12:47:13 -09:00
|
|
|
starred := model.MediaFiles{}
|
2020-01-28 04:22:17 -09:00
|
|
|
err := r.queryAll(sq, &starred)
|
|
|
|
|
return starred, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO Keep order when paginating
|
|
|
|
|
func (r mediaFileRepository) GetRandom(options ...model.QueryOptions) (model.MediaFiles, error) {
|
|
|
|
|
sq := r.selectMediaFile(options...)
|
2020-02-28 12:09:27 -09:00
|
|
|
sq = sq.OrderBy("RANDOM()")
|
2020-01-31 12:47:13 -09:00
|
|
|
results := model.MediaFiles{}
|
2020-02-01 10:48:22 -09:00
|
|
|
err := r.queryAll(sq, &results)
|
2020-01-28 04:22:17 -09:00
|
|
|
return results, err
|
2020-01-21 04:49:43 -09:00
|
|
|
}
|
|
|
|
|
|
2020-01-28 04:22:17 -09:00
|
|
|
func (r mediaFileRepository) Delete(id string) error {
|
|
|
|
|
return r.delete(Eq{"id": id})
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-12 09:26:46 -08:00
|
|
|
// DeleteByPath delete from the DB all mediafiles that are direct children of path
|
2020-01-28 04:22:17 -09:00
|
|
|
func (r mediaFileRepository) DeleteByPath(path string) error {
|
2020-06-12 09:26:46 -08:00
|
|
|
path = filepath.Clean(path)
|
2020-06-10 19:11:43 -08:00
|
|
|
del := Delete(r.tableName).
|
2020-06-12 09:26:46 -08:00
|
|
|
Where(And{Like{"path": filepath.Join(path, "%")},
|
2020-06-10 19:11:43 -08:00
|
|
|
Eq{fmt.Sprintf("substr(path, %d) glob '*%s*'", len(path)+2, string(os.PathSeparator)): 0}})
|
|
|
|
|
log.Debug(r.ctx, "Deleting mediafiles by path", "path", path)
|
|
|
|
|
_, err := r.executeSQL(del)
|
2020-01-28 04:22:17 -09:00
|
|
|
return err
|
2020-01-18 16:59:20 -09:00
|
|
|
}
|
|
|
|
|
|
2020-01-28 04:22:17 -09:00
|
|
|
func (r mediaFileRepository) Search(q string, offset int, size int) (model.MediaFiles, error) {
|
2020-01-31 12:47:13 -09:00
|
|
|
results := model.MediaFiles{}
|
2020-01-31 11:35:06 -09:00
|
|
|
err := r.doSearch(q, offset, size, &results, "title")
|
2020-01-28 04:22:17 -09:00
|
|
|
return results, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r mediaFileRepository) Count(options ...rest.QueryOptions) (int64, error) {
|
|
|
|
|
return r.CountAll(r.parseRestOptions(options...))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r mediaFileRepository) Read(id string) (interface{}, error) {
|
|
|
|
|
return r.Get(id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r mediaFileRepository) ReadAll(options ...rest.QueryOptions) (interface{}, error) {
|
|
|
|
|
return r.GetAll(r.parseRestOptions(options...))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r mediaFileRepository) EntityName() string {
|
|
|
|
|
return "mediafile"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r mediaFileRepository) NewInstance() interface{} {
|
2020-05-22 11:23:42 -08:00
|
|
|
return &model.MediaFile{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r mediaFileRepository) Save(entity interface{}) (string, error) {
|
|
|
|
|
mf := entity.(*model.MediaFile)
|
|
|
|
|
err := r.Put(mf)
|
|
|
|
|
return mf.ID, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r mediaFileRepository) Update(entity interface{}, cols ...string) error {
|
|
|
|
|
mf := entity.(*model.MediaFile)
|
|
|
|
|
return r.Put(mf)
|
2020-01-13 06:44:35 -09:00
|
|
|
}
|
|
|
|
|
|
2020-01-14 18:22:34 -09:00
|
|
|
var _ model.MediaFileRepository = (*mediaFileRepository)(nil)
|
2020-01-28 04:22:17 -09:00
|
|
|
var _ model.ResourceRepository = (*mediaFileRepository)(nil)
|
2020-05-22 11:23:42 -08:00
|
|
|
var _ rest.Persistable = (*mediaFileRepository)(nil)
|