150 lines
2.8 KiB
Go
150 lines
2.8 KiB
Go
package events
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/Silo-Server/silo-server/internal/cache"
|
|
"github.com/oklog/ulid/v2"
|
|
)
|
|
|
|
type subscriber struct {
|
|
ch chan Envelope
|
|
}
|
|
|
|
// Hub fans out multiplexed events locally and across nodes.
|
|
type Hub struct {
|
|
mu sync.RWMutex
|
|
subscribers map[*subscriber]struct{}
|
|
sourceID string
|
|
eventBus cache.EventBus
|
|
drops map[EventChannel]uint64
|
|
}
|
|
|
|
func NewHub(sourceID string, eventBus cache.EventBus) *Hub {
|
|
return &Hub{
|
|
subscribers: make(map[*subscriber]struct{}),
|
|
sourceID: sourceID,
|
|
eventBus: eventBus,
|
|
drops: make(map[EventChannel]uint64),
|
|
}
|
|
}
|
|
|
|
func (h *Hub) Start(ctx context.Context) error {
|
|
if h == nil || h.eventBus == nil || ctx == nil {
|
|
return nil
|
|
}
|
|
return h.eventBus.Subscribe(ctx, cache.ChannelEvents, h.handleEventBusMessage)
|
|
}
|
|
|
|
func (h *Hub) Subscribe() (<-chan Envelope, func()) {
|
|
sub := &subscriber{ch: make(chan Envelope, 64)}
|
|
h.mu.Lock()
|
|
h.subscribers[sub] = struct{}{}
|
|
h.mu.Unlock()
|
|
|
|
return sub.ch, func() {
|
|
h.mu.Lock()
|
|
if _, ok := h.subscribers[sub]; ok {
|
|
delete(h.subscribers, sub)
|
|
close(sub.ch)
|
|
}
|
|
h.mu.Unlock()
|
|
}
|
|
}
|
|
|
|
func (h *Hub) Publish(ctx context.Context, env Envelope) error {
|
|
if h == nil {
|
|
return nil
|
|
}
|
|
|
|
if env.Timestamp.IsZero() {
|
|
env.Timestamp = time.Now().UTC()
|
|
}
|
|
if env.SourceID == "" {
|
|
env.SourceID = h.sourceID
|
|
}
|
|
if env.EventID == "" {
|
|
env.EventID = ulid.Make().String()
|
|
}
|
|
|
|
h.publishLocal(env)
|
|
|
|
if h.eventBus == nil {
|
|
return nil
|
|
}
|
|
|
|
payload, err := json.Marshal(env)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return h.eventBus.Publish(ctx, cache.ChannelEvents, cache.Event{
|
|
Type: cache.EventEventsNotification,
|
|
Payload: string(payload),
|
|
})
|
|
}
|
|
|
|
func (h *Hub) PublishJSON(
|
|
ctx context.Context,
|
|
channel EventChannel,
|
|
event string,
|
|
data any,
|
|
opts PublishOptions,
|
|
) error {
|
|
var payload json.RawMessage
|
|
if data != nil {
|
|
encoded, err := json.Marshal(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
payload = encoded
|
|
}
|
|
|
|
return h.Publish(ctx, Envelope{
|
|
Channel: channel,
|
|
Event: event,
|
|
EventID: opts.EventID,
|
|
Data: payload,
|
|
UserID: opts.UserID,
|
|
ProfileID: opts.ProfileID,
|
|
AdminOnly: opts.AdminOnly,
|
|
})
|
|
}
|
|
|
|
func (h *Hub) Dropped(channel EventChannel) uint64 {
|
|
h.mu.RLock()
|
|
defer h.mu.RUnlock()
|
|
return h.drops[channel]
|
|
}
|
|
|
|
func (h *Hub) publishLocal(env Envelope) {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
for sub := range h.subscribers {
|
|
select {
|
|
case sub.ch <- env:
|
|
default:
|
|
h.drops[env.Channel]++
|
|
}
|
|
}
|
|
}
|
|
|
|
func (h *Hub) handleEventBusMessage(event cache.Event) {
|
|
if event.Type != cache.EventEventsNotification || event.Payload == "" {
|
|
return
|
|
}
|
|
|
|
var env Envelope
|
|
if err := json.Unmarshal([]byte(event.Payload), &env); err != nil {
|
|
return
|
|
}
|
|
if env.SourceID != "" && env.SourceID == h.sourceID {
|
|
return
|
|
}
|
|
|
|
h.publishLocal(env)
|
|
}
|