1
0
mirror of https://github.com/jhillyerd/inbucket.git synced 2025-12-18 01:57:02 +00:00
Files
go-inbucket/msghub/hub.go
2017-01-15 22:06:39 -08:00

117 lines
2.5 KiB
Go

package msghub
import (
"container/ring"
"context"
"sync"
"time"
)
// Message contains the basic header data for a message
type Message struct {
Mailbox string
ID string
From string
To []string
Subject string
Date time.Time
Size int64
}
// Listener receives the contents of the log, followed by new messages
type Listener interface {
Receive(msg Message) error
}
// Hub relays messages on to its listeners
type Hub struct {
// log stores history, points next spot to write. First non-nil entry is oldest Message
log *ring.Ring
logMx sync.RWMutex
// listeners interested in new messages
listeners map[Listener]struct{}
listenersMx sync.RWMutex
// broadcast receives new messages
broadcast chan Message
}
// New constructs a new Hub which will cache logSize messages in memory for playback to future
// listeners. A goroutine is created to handle incoming messages; it will run until the provided
// context is canceled.
func New(ctx context.Context, logSize int) *Hub {
h := &Hub{
log: ring.New(logSize),
listeners: make(map[Listener]struct{}),
broadcast: make(chan Message, 100),
}
go func() {
for {
select {
case <-ctx.Done():
// Shutdown
close(h.broadcast)
h.broadcast = nil
return
case msg := <-h.broadcast:
// Log message
h.logMx.Lock()
h.log.Value = msg
h.log = h.log.Next()
h.logMx.Unlock()
// Deliver message to listeners
h.deliver(msg)
}
}
}()
return h
}
// Broadcast queues a message for processing by the hub. The message will be placed into the
// in-memory log and relayed to all registered listeners.
func (h *Hub) Broadcast(msg Message) {
if h.broadcast != nil {
h.broadcast <- msg
}
}
// AddListener registers a listener to receive broadcasted messages.
func (h *Hub) AddListener(l Listener) {
// Playback log
h.logMx.RLock()
h.log.Do(func(v interface{}) {
if v != nil {
l.Receive(v.(Message))
}
})
h.logMx.RUnlock()
// Add to listeners
h.listenersMx.Lock()
h.listeners[l] = struct{}{}
h.listenersMx.Unlock()
}
// RemoveListener deletes a listener registration, it will cease to receive messages.
func (h *Hub) RemoveListener(l Listener) {
h.listenersMx.Lock()
defer h.listenersMx.Unlock()
if _, ok := h.listeners[l]; ok {
delete(h.listeners, l)
}
}
// deliver message to all listeners, removing listeners if they return an error
func (h *Hub) deliver(msg Message) {
h.listenersMx.RLock()
defer h.listenersMx.RUnlock()
for l := range h.listeners {
if err := l.Receive(msg); err != nil {
h.RemoveListener(l)
}
}
}