package safew import ( "context" "log/slog" "sync" "time" "aiaa-notification-service/internal/adapter" ) type Poller func(token string, offset int64, timeout int) ([]adapter.SafewChat, int64, error) type Watcher struct { store ChatStore poll Poller bgIdle time.Duration mu sync.Mutex running map[string]context.CancelFunc stopped bool } func NewWatcher(store ChatStore, poll Poller) *Watcher { return &Watcher{ store: store, poll: poll, bgIdle: time.Second, running: map[string]context.CancelFunc{}, } } func (w *Watcher) Ensure(token string) { if token == "" || w == nil { return } w.mu.Lock() defer w.mu.Unlock() if w.stopped { return } if _, ok := w.running[token]; ok { return } ctx, cancel := context.WithCancel(context.Background()) w.running[token] = cancel go w.loop(ctx, token) } func (w *Watcher) Stop() { w.mu.Lock() w.stopped = true for _, cancel := range w.running { cancel() } w.running = map[string]context.CancelFunc{} w.mu.Unlock() } func (w *Watcher) Refresh(ctx context.Context, token string) error { return w.pollOnce(ctx, token, 0) } func (w *Watcher) List(ctx context.Context, token, q string) ([]adapter.SafewChat, error) { chats, err := w.store.ListSafewChats(ctx, token) if err != nil { return nil, err } return adapter.FilterSafewChats(chats, q), nil } func (w *Watcher) loop(ctx context.Context, token string) { for { if err := w.pollOnce(ctx, token, 30); err != nil { if ctx.Err() != nil { return } slog.Warn("safew watcher poll", "error", err) } idle := w.bgIdle if idle <= 0 { idle = time.Second } select { case <-ctx.Done(): return case <-time.After(idle): } } } func (w *Watcher) pollOnce(ctx context.Context, token string, timeout int) error { ok, err := w.store.TrySafewPollLock(ctx, token, 35*time.Second) if err != nil { return err } if !ok { return nil } defer func() { _ = w.store.UnlockSafewPoll(ctx, token) }() offset, err := w.store.GetSafewOffset(ctx, token) if err != nil { return err } chats, next, err := w.poll(token, offset, timeout) if err != nil { return err } if err := w.store.MergeSafewChats(ctx, token, chats); err != nil { return err } if next != offset { return w.store.SetSafewOffset(ctx, token, next) } return nil }