113 lines
2.7 KiB
Go
113 lines
2.7 KiB
Go
// Package notify provides notification functionality.
|
|
package notify
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
log "github.com/go-pkgz/lgr"
|
|
|
|
"github.com/umputun/remark/backend/app/store"
|
|
)
|
|
|
|
// Service delivers notifications to multiple destinations
|
|
type Service struct {
|
|
dataService Store
|
|
destinations []Destination
|
|
queue chan request
|
|
|
|
closed uint32 // non-zero means closed. uses uint instead of bool for atomic
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
}
|
|
|
|
// Destination defines interface for a given destination service, like telegram, email and so on
|
|
type Destination interface {
|
|
fmt.Stringer
|
|
Send(ctx context.Context, req request) error
|
|
}
|
|
|
|
// Store defines the minimal interface accessing stored comments used by notifier
|
|
type Store interface {
|
|
Get(locator store.Locator, id string) (store.Comment, error)
|
|
}
|
|
|
|
type request struct {
|
|
comment store.Comment
|
|
parent store.Comment
|
|
}
|
|
|
|
const defaultQueueSize = 100
|
|
const uiNav = "#remark42__comment-"
|
|
|
|
// NewService makes notification service routing comments to all destinations.
|
|
func NewService(dataService Store, size int, destinations ...Destination) *Service {
|
|
if size <= 0 {
|
|
size = defaultQueueSize
|
|
}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
res := Service{
|
|
dataService: dataService,
|
|
queue: make(chan request, size),
|
|
destinations: destinations,
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
}
|
|
if len(destinations) > 0 {
|
|
go res.do()
|
|
}
|
|
log.Printf("[INFO] create notifier service, queue size=%d, destinations=%d", size, len(destinations))
|
|
return &res
|
|
}
|
|
|
|
// Submit comment to internal channel if not busy, drop if can't send
|
|
func (s *Service) Submit(comment store.Comment) {
|
|
if len(s.destinations) == 0 || atomic.LoadUint32(&s.closed) != 0 {
|
|
return
|
|
}
|
|
parentComment := store.Comment{}
|
|
if s.dataService != nil {
|
|
if p, err := s.dataService.Get(comment.Locator, comment.ParentID); err == nil {
|
|
parentComment = p
|
|
}
|
|
}
|
|
select {
|
|
case s.queue <- request{comment: comment, parent: parentComment}:
|
|
default:
|
|
log.Printf("[WARN] can't send comment notification to queue, %+v", comment)
|
|
}
|
|
}
|
|
|
|
// Close queue channel and wait for completion
|
|
func (s *Service) Close() {
|
|
if s.queue != nil {
|
|
log.Print("[DEBUG] close notifier")
|
|
close(s.queue)
|
|
s.cancel()
|
|
<-s.ctx.Done()
|
|
}
|
|
atomic.StoreUint32(&s.closed, 1)
|
|
}
|
|
|
|
func (s *Service) do() {
|
|
for c := range s.queue {
|
|
var wg sync.WaitGroup
|
|
wg.Add(len(s.destinations))
|
|
for _, dest := range s.destinations {
|
|
go func(d Destination) {
|
|
if err := d.Send(s.ctx, c); err != nil {
|
|
log.Printf("[WARN] failed to send to %s, %s", d, err)
|
|
}
|
|
wg.Done()
|
|
}(dest)
|
|
}
|
|
wg.Wait()
|
|
}
|
|
log.Print("[WARN] terminated notifier")
|
|
}
|
|
|
|
// NopService is do-nothing notifier, without destinations
|
|
var NopService = &Service{}
|