🏡 index : quine-global/quintodrome.git

package core

import (
	"context"
	"fmt"
	"time"

	"github.com/navidrome/navidrome/conf"
	"github.com/navidrome/navidrome/consts"
	"github.com/navidrome/navidrome/log"
	"github.com/navidrome/navidrome/model"
	"github.com/navidrome/navidrome/model/id"
	"github.com/navidrome/navidrome/model/request"
	"github.com/navidrome/navidrome/utils"
)

type Players interface {
	Get(ctx context.Context, playerId string) (*model.Player, error)
	Register(ctx context.Context, id, client, userAgent, ip string) (*model.Player, *model.Transcoding, error)
	Touch(ctx context.Context, plr model.Player, client, userAgent, ip string) (*model.Player, *model.Transcoding, error)
}

func NewPlayers(ds model.DataStore) Players {
	return &players{
		ds:      ds,
		limiter: utils.Limiter{Interval: consts.UpdatePlayerFrequency},
	}
}

type players struct {
	ds      model.DataStore
	limiter utils.Limiter
}

func (p *players) Register(ctx context.Context, playerID, client, userAgent, ip string) (*model.Player, *model.Transcoding, error) {
	var plr *model.Player
	var err error
	user, _ := request.UserFrom(ctx)
	if playerID != "" {
		plr, err = p.ds.Player().Get(ctx, playerID)
		if err == nil && (plr.Client != client || plr.UserId != user.ID) {
			playerID = ""
		}
	}
	username := userName(ctx)
	if err != nil || playerID == "" {
		plr, err = p.ds.Player().FindMatch(ctx, user.ID, client, userAgent)
		if err == nil {
			log.Debug(ctx, "Found matching player", "id", plr.ID, "client", client, "username", username, "type", userAgent)
		} else {
			plr = &model.Player{
				ID:              id.NewRandom(),
				UserId:          user.ID,
				Client:          client,
				ScrobbleEnabled: true,
				ReportRealPath:  conf.Server.Subsonic.DefaultReportRealPath,
			}
			log.Info(ctx, "Registering new player", "id", plr.ID, "client", client, "username", username, "type", userAgent)
		}
	}
	if !plr.HasAPIKey {
		plr.Name = fmt.Sprintf("%s [%s]", client, userAgent)
	}
	return p.refresh(ctx, plr, userAgent, ip)
}

// Touch refreshes a player that the request already identified (by API key), without guessing or renaming it.
func (p *players) Touch(ctx context.Context, plr model.Player, client, userAgent, ip string) (*model.Player, *model.Transcoding, error) {
	if plr.Client == "" {
		plr.Client = client
	}
	return p.refresh(ctx, &plr, userAgent, ip)
}

func (p *players) refresh(ctx context.Context, plr *model.Player, userAgent, ip string) (*model.Player, *model.Transcoding, error) {
	plr.UserAgent = userAgent
	plr.IP = ip
	plr.LastSeen = time.Now()
	p.limiter.Do(plr.ID, func() {
		ctx, cancel := context.WithTimeout(ctx, time.Second)
		defer cancel()

		if err := p.ds.Player().Put(ctx, plr); err != nil {
			log.Warn(ctx, "Could not save player", "id", plr.ID, "client", plr.Client, "username", userName(ctx), "type", plr.UserAgent, err)
		}
	})
	if plr.TranscodingId == "" {
		return plr, nil, nil
	}
	trc, err := p.ds.Transcoding().Get(ctx, plr.TranscodingId)
	return plr, trc, err
}

func (p *players) Get(ctx context.Context, playerId string) (*model.Player, error) {
	return p.ds.Player().Get(ctx, playerId)
}