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 }