navidrome/core/share.go

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

179 lines
4.7 KiB
Go
Raw Normal View History

package core
import (
"context"
2023-01-20 04:52:55 +01:00
"strings"
"time"
2023-01-20 04:52:55 +01:00
"github.com/Masterminds/squirrel"
"github.com/deluan/rest"
2023-01-20 04:52:55 +01:00
gonanoid "github.com/matoous/go-nanoid/v2"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
2023-01-22 07:12:08 +01:00
"github.com/navidrome/navidrome/model/request"
2023-01-20 04:52:55 +01:00
"github.com/navidrome/navidrome/utils/slice"
)
type Share interface {
2023-01-20 04:52:55 +01:00
Load(ctx context.Context, id string) (*model.Share, error)
NewRepository(ctx context.Context) rest.Repository
}
func NewShare(ds model.DataStore) Share {
return &shareService{
ds: ds,
}
}
type shareService struct {
ds model.DataStore
}
2023-01-20 04:52:55 +01:00
func (s *shareService) Load(ctx context.Context, id string) (*model.Share, error) {
repo := s.ds.Share(ctx)
entity, err := repo.(rest.Repository).Read(id)
if err != nil {
return nil, err
}
share := entity.(*model.Share)
2023-01-23 01:52:11 +01:00
if !share.ExpiresAt.IsZero() && share.ExpiresAt.Before(time.Now()) {
return nil, model.ErrNotAvailable
}
2023-01-21 01:53:53 +01:00
share.LastVisitedAt = time.Now()
2023-01-20 04:52:55 +01:00
share.VisitCount++
err = repo.(rest.Persistable).Update(id, share, "last_visited_at", "visit_count")
if err != nil {
log.Warn(ctx, "Could not increment visit count for share", "share", share.ID)
}
idList := strings.Split(share.ResourceIDs, ",")
2023-01-22 07:12:08 +01:00
var mfs model.MediaFiles
2023-01-20 04:52:55 +01:00
switch share.ResourceType {
case "album":
2023-01-22 07:12:08 +01:00
mfs, err = s.loadMediafiles(ctx, squirrel.Eq{"album_id": idList}, "album")
case "playlist":
mfs, err = s.loadPlaylistTracks(ctx, share.ResourceIDs)
2023-01-20 04:52:55 +01:00
}
if err != nil {
return nil, err
}
share.Tracks = mfs
2023-01-22 07:12:08 +01:00
return entity.(*model.Share), nil
}
func (s *shareService) loadMediafiles(ctx context.Context, filter squirrel.Eq, sort string) (model.MediaFiles, error) {
return s.ds.MediaFile(ctx).GetAll(model.QueryOptions{Filters: filter, Sort: sort})
}
func (s *shareService) loadPlaylistTracks(ctx context.Context, id string) (model.MediaFiles, error) {
// Create a context with a fake admin user, to be able to access playlists
ctx = request.WithUser(ctx, model.User{IsAdmin: true})
2023-01-22 07:12:08 +01:00
tracks, err := s.ds.Playlist(ctx).Tracks(id, true).GetAll(model.QueryOptions{Sort: "id"})
if err != nil {
return nil, err
}
return tracks.MediaFiles(), nil
2023-01-20 04:52:55 +01:00
}
func (s *shareService) NewRepository(ctx context.Context) rest.Repository {
repo := s.ds.Share(ctx)
wrapper := &shareRepositoryWrapper{
ctx: ctx,
ShareRepository: repo,
Repository: repo.(rest.Repository),
Persistable: repo.(rest.Persistable),
ds: s.ds,
}
return wrapper
}
type shareRepositoryWrapper struct {
model.ShareRepository
rest.Repository
rest.Persistable
ctx context.Context
ds model.DataStore
}
2023-01-20 04:52:55 +01:00
func (r *shareRepositoryWrapper) newId() (string, error) {
for {
id, err := gonanoid.Generate("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 10)
if err != nil {
return "", err
}
exists, err := r.Exists(id)
if err != nil {
return "", err
}
if !exists {
return id, nil
}
}
}
func (r *shareRepositoryWrapper) Save(entity interface{}) (string, error) {
s := entity.(*model.Share)
2023-01-20 04:52:55 +01:00
id, err := r.newId()
if err != nil {
return "", err
}
2023-01-20 04:52:55 +01:00
s.ID = id
if s.ExpiresAt.IsZero() {
2023-01-21 01:53:53 +01:00
s.ExpiresAt = time.Now().Add(365 * 24 * time.Hour)
2023-01-20 04:52:55 +01:00
}
// TODO Validate all ids
firstId := strings.SplitN(s.ResourceIDs, ",", 1)[0]
v, err := model.GetEntityByID(r.ctx, r.ds, firstId)
if err != nil {
return "", err
}
switch v.(type) {
case *model.Album:
s.ResourceType = "album"
s.Contents = r.shareContentsFromAlbums(s.ID, s.ResourceIDs)
case *model.Playlist:
s.ResourceType = "playlist"
2023-01-22 07:12:08 +01:00
s.Contents = r.shareContentsFromPlaylist(s.ID, s.ResourceIDs)
case *model.Artist:
s.ResourceType = "artist"
case *model.MediaFile:
s.ResourceType = "song"
}
id, err = r.Persistable.Save(s)
return id, err
}
func (r *shareRepositoryWrapper) Update(id string, entity interface{}, _ ...string) error {
2023-01-20 04:52:55 +01:00
return r.Persistable.Update(id, entity, "description", "expires_at")
}
func (r *shareRepositoryWrapper) shareContentsFromAlbums(shareID string, ids string) string {
all, err := r.ds.Album(r.ctx).GetAll(model.QueryOptions{Filters: squirrel.Eq{"id": ids}})
if err != nil {
log.Error(r.ctx, "Error retrieving album names for share", "share", shareID, err)
return ""
}
names := slice.Map(all, func(a model.Album) string { return a.Name })
content := strings.Join(names, ", ")
if len(content) > 30 {
content = content[:26] + "..."
}
return content
}
2023-01-22 07:12:08 +01:00
func (r *shareRepositoryWrapper) shareContentsFromPlaylist(shareID string, id string) string {
pls, err := r.ds.Playlist(r.ctx).Get(id)
if err != nil {
log.Error(r.ctx, "Error retrieving album names for share", "share", shareID, err)
return ""
}
content := pls.Name
if len(content) > 30 {
content = content[:26] + "..."
}
return content
}