0.2.73
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ponzischeme89/memby/server/internal/cache"
|
||||
"github.com/ponzischeme89/memby/server/internal/recommend"
|
||||
"github.com/ponzischeme89/memby/server/internal/store"
|
||||
)
|
||||
@@ -53,12 +56,24 @@ func (s *Server) handleMagic(w http.ResponseWriter, r *http.Request, sess store.
|
||||
_ = json.NewDecoder(r.Body).Decode(&req)
|
||||
}
|
||||
|
||||
selection, ok := s.recommender.MagicPick(ctx, credentials(sess), recommend.MagicOptions{
|
||||
pool, cached := s.magicPool(ctx, sess)
|
||||
selection, ok := recommend.ChooseMagic(pool, recommend.MagicOptions{
|
||||
ExcludeIDs: req.ExcludeIDs,
|
||||
AvailableMinutes: req.AvailableMinutes,
|
||||
// The one non-deterministic thing about the feature, named in one place.
|
||||
Roll: rand.Float64(),
|
||||
})
|
||||
if !ok && cached {
|
||||
// Everything the kept pool held has already been offered. That is a pool that has
|
||||
// run its course rather than a household with nothing left, so it is rebuilt once
|
||||
// before the button is allowed to say no.
|
||||
pool = s.rebuildMagicPool(ctx, sess)
|
||||
selection, ok = recommend.ChooseMagic(pool, recommend.MagicOptions{
|
||||
ExcludeIDs: req.ExcludeIDs,
|
||||
AvailableMinutes: req.AvailableMinutes,
|
||||
Roll: rand.Float64(),
|
||||
})
|
||||
}
|
||||
if !ok {
|
||||
// A household that has run out of unseen library is not an error, and the television
|
||||
// says so quietly rather than showing a failure over somebody's film.
|
||||
@@ -67,24 +82,54 @@ func (s *Server) handleMagic(w http.ResponseWriter, r *http.Request, sess store.
|
||||
return
|
||||
}
|
||||
|
||||
item, err := s.emby.Item(ctx, credentials(sess), selection.Item.ID, fieldsDetail)
|
||||
item, err := s.detailItem(ctx, sess, selection.ItemID)
|
||||
if err != nil {
|
||||
s.writeUpstreamError(ctx, w, err, "could not load the suggestion")
|
||||
return
|
||||
}
|
||||
decorated := []json.RawMessage{item}
|
||||
s.decorateItemRatings(ctx, decorated)
|
||||
|
||||
s.loggerFor(ctx).Info("magic picked",
|
||||
"item", selection.Item.ID,
|
||||
"title", selection.Item.Name,
|
||||
"item", selection.ItemID,
|
||||
"title", selection.Title,
|
||||
"score", selection.Score,
|
||||
"pool", selection.PoolSize,
|
||||
"signals", strings.Join(selection.Signals, ","),
|
||||
)
|
||||
|
||||
writeJSON(w, http.StatusOK, magicResponse{
|
||||
Item: decorated[0],
|
||||
Item: item,
|
||||
Reasons: selection.Reasons,
|
||||
})
|
||||
}
|
||||
|
||||
// magicPool returns the kept pool, and whether it came from the cache.
|
||||
//
|
||||
// The press is made with the film paused behind a loading panel, so what happens on it
|
||||
// matters: building a pool is two complete reads of this viewer's Emby history plus a
|
||||
// catalogue query, and none of that answer changes between one press and the next. Keeping
|
||||
// it turns every press after the first into arithmetic over a few dozen numbers.
|
||||
func (s *Server) magicPool(ctx context.Context, sess store.Session) ([]recommend.MagicCandidate, bool) {
|
||||
if raw, err := s.cache.Get(ctx, cache.MagicPoolKey(sess.EmbyUserID)); err == nil {
|
||||
var pool []recommend.MagicCandidate
|
||||
if err := json.Unmarshal(raw, &pool); err == nil && len(pool) > 0 {
|
||||
return pool, true
|
||||
}
|
||||
}
|
||||
return s.rebuildMagicPool(ctx, sess), false
|
||||
}
|
||||
|
||||
func (s *Server) rebuildMagicPool(ctx context.Context, sess store.Session) []recommend.MagicCandidate {
|
||||
pool := s.recommender.MagicPool(ctx, credentials(sess), time.Time{})
|
||||
if len(pool) == 0 {
|
||||
// Deliberately not cached: an empty pool is a household whose library or Emby was
|
||||
// unavailable far more often than it is one with no films, and keeping that answer
|
||||
// would withdraw the button for hours over a moment's trouble.
|
||||
return nil
|
||||
}
|
||||
if raw, err := json.Marshal(pool); err == nil {
|
||||
if err := s.cache.Set(ctx, cache.MagicPoolKey(sess.EmbyUserID), raw, s.cfg.MagicPoolTTL); err != nil {
|
||||
s.loggerFor(ctx).Warn("magic pool cache write failed", "error", err)
|
||||
}
|
||||
}
|
||||
return pool
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user