mirror of
https://github.com/jhillyerd/inbucket.git
synced 2025-12-18 01:57:02 +00:00
117 lines
2.5 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|