136 lines
3.2 KiB
Go
136 lines
3.2 KiB
Go
package memory
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"sync"
|
|
"time"
|
|
|
|
"git.vakhrushev.me/av/remembos/internal/memos"
|
|
"git.vakhrushev.me/av/remembos/internal/search"
|
|
"git.vakhrushev.me/av/remembos/internal/storage"
|
|
)
|
|
|
|
// Service provides "one memory per day" with in-memory caching.
|
|
type Service struct {
|
|
selector *search.Selector
|
|
store *storage.Storage
|
|
client *memos.Client
|
|
loc *time.Location
|
|
logger *slog.Logger
|
|
|
|
mu sync.Mutex
|
|
cacheDay string // "2006-01-02"
|
|
cached *search.Memory
|
|
}
|
|
|
|
func NewService(selector *search.Selector, store *storage.Storage, client *memos.Client, loc *time.Location, logger *slog.Logger) *Service {
|
|
return &Service{
|
|
selector: selector,
|
|
store: store,
|
|
client: client,
|
|
loc: loc,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// GetTodayMemory returns the memory for today, caching the result.
|
|
// On cache miss (e.g. after restart), it checks the DB for a memo already shown today.
|
|
func (s *Service) GetTodayMemory(ctx context.Context) (*search.Memory, error) {
|
|
today := time.Now().In(s.loc)
|
|
dayKey := today.Format("2006-01-02")
|
|
|
|
s.mu.Lock()
|
|
if s.cacheDay == dayKey && s.cached != nil {
|
|
mem := s.cached
|
|
s.mu.Unlock()
|
|
return mem, nil
|
|
}
|
|
s.mu.Unlock()
|
|
|
|
// Try to restore from DB — check if a memo was already shown today
|
|
dayStart := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, s.loc)
|
|
dayEnd := dayStart.AddDate(0, 0, 1)
|
|
|
|
memoName, tier, err := s.store.GetLastShownToday(ctx, dayStart.Unix(), dayEnd.Unix())
|
|
if err != nil {
|
|
s.logger.Error("failed to check today's show history", "error", err)
|
|
// Fall through to select a new one
|
|
}
|
|
|
|
if memoName != "" {
|
|
s.logger.Info("restoring today's memory from history", "memo", memoName)
|
|
|
|
memo, err := s.client.GetMemo(ctx, memoName)
|
|
if err != nil {
|
|
s.logger.Error("failed to fetch memo from history, selecting new", "memo", memoName, "error", err)
|
|
} else {
|
|
mem := &search.Memory{
|
|
Memo: memo,
|
|
Tier: tier,
|
|
Date: memo.DisplayTime,
|
|
}
|
|
|
|
s.mu.Lock()
|
|
s.cacheDay = dayKey
|
|
s.cached = mem
|
|
s.mu.Unlock()
|
|
|
|
return mem, nil
|
|
}
|
|
}
|
|
|
|
s.logger.Info("selecting new memory", "date", dayKey)
|
|
|
|
mem, err := s.selector.Select(ctx, today)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if mem == nil {
|
|
s.logger.Warn("no memory found for today")
|
|
return nil, nil
|
|
}
|
|
|
|
if err := s.store.RecordShow(ctx, mem.Memo.Name, mem.Tier); err != nil {
|
|
s.logger.Error("failed to record show", "error", err)
|
|
}
|
|
|
|
s.mu.Lock()
|
|
s.cacheDay = dayKey
|
|
s.cached = mem
|
|
s.mu.Unlock()
|
|
|
|
return mem, nil
|
|
}
|
|
|
|
// LoadNewMemory selects a new memory ignoring the cache, records the show,
|
|
// and updates the cache so that subsequent GetTodayMemory calls return it.
|
|
func (s *Service) LoadNewMemory(ctx context.Context) (*search.Memory, error) {
|
|
today := time.Now().In(s.loc)
|
|
dayKey := today.Format("2006-01-02")
|
|
|
|
s.logger.Info("loading additional memory", "date", dayKey)
|
|
|
|
mem, err := s.selector.Select(ctx, today)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if mem == nil {
|
|
s.logger.Warn("no memory found for load more")
|
|
return nil, nil
|
|
}
|
|
|
|
if err := s.store.RecordShow(ctx, mem.Memo.Name, mem.Tier); err != nil {
|
|
s.logger.Error("failed to record show", "error", err)
|
|
}
|
|
|
|
s.mu.Lock()
|
|
s.cacheDay = dayKey
|
|
s.cached = mem
|
|
s.mu.Unlock()
|
|
|
|
return mem, nil
|
|
}
|