events.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. // Package events provides a small in-process publish/subscribe broker used
  2. // to stream LocalGo activity (pending transfers, transfer progress,
  3. // completed transfers, discovered devices) to IPC/SSE consumers.
  4. //
  5. // It lives in its own package so low-level packages (server handlers) can
  6. // publish without importing the IPC layer, which depends on them.
  7. package events
  8. import (
  9. "sync"
  10. "sync/atomic"
  11. "time"
  12. )
  13. // Event types published by the broker.
  14. const (
  15. TypeTransferPending = "transfer_pending"
  16. TypeTransferProgress = "transfer_progress"
  17. TypeTransferComplete = "transfer_complete"
  18. TypeTransferRejected = "transfer_rejected"
  19. TypeDeviceDiscovered = "device_discovered"
  20. )
  21. // Event is a single broker message.
  22. type Event struct {
  23. // ID is a monotonically increasing sequence number, used for SSE
  24. // Last-Event-ID resumption.
  25. ID uint64 `json:"id"`
  26. // Time is the Unix-millisecond publish time.
  27. Time int64 `json:"time"`
  28. Type string `json:"type"`
  29. Data interface{} `json:"data,omitempty"`
  30. }
  31. const (
  32. // defaultHistory is how many recent events are retained for replay.
  33. defaultHistory = 64
  34. // defaultSubscriberBuffer is the per-subscriber queue depth. Slow
  35. // consumers miss events rather than stalling the publisher.
  36. defaultSubscriberBuffer = 256
  37. )
  38. // Broker fans events out to subscribers. The zero value is not usable; call
  39. // NewBroker.
  40. type Broker struct {
  41. mu sync.RWMutex
  42. subs map[chan Event]struct{}
  43. seq atomic.Uint64
  44. history []Event
  45. limit int
  46. buffer int
  47. }
  48. // NewBroker creates a broker retaining the default replay history.
  49. func NewBroker() *Broker {
  50. return &Broker{
  51. subs: make(map[chan Event]struct{}),
  52. history: make([]Event, 0, defaultHistory),
  53. limit: defaultHistory,
  54. buffer: defaultSubscriberBuffer,
  55. }
  56. }
  57. // Publish delivers an event to all current subscribers and records it in the
  58. // replay history. It never blocks: a subscriber whose queue is full misses
  59. // the event.
  60. func (b *Broker) Publish(eventType string, data interface{}) {
  61. if b == nil {
  62. return
  63. }
  64. evt := Event{
  65. ID: b.seq.Add(1),
  66. Time: time.Now().UnixMilli(),
  67. Type: eventType,
  68. Data: data,
  69. }
  70. b.mu.Lock()
  71. b.history = append(b.history, evt)
  72. if len(b.history) > b.limit {
  73. b.history = b.history[len(b.history)-b.limit:]
  74. }
  75. subs := make([]chan Event, 0, len(b.subs))
  76. for ch := range b.subs {
  77. subs = append(subs, ch)
  78. }
  79. b.mu.Unlock()
  80. for _, ch := range subs {
  81. select {
  82. case ch <- evt:
  83. default: // slow consumer: drop rather than block the publisher
  84. }
  85. }
  86. }
  87. // Subscribe returns a channel of future events and a function to unsubscribe.
  88. // The channel is closed by the unsubscribe function.
  89. func (b *Broker) Subscribe() (<-chan Event, func()) {
  90. if b == nil {
  91. ch := make(chan Event)
  92. close(ch)
  93. return ch, func() {}
  94. }
  95. ch := make(chan Event, b.buffer)
  96. b.mu.Lock()
  97. b.subs[ch] = struct{}{}
  98. b.mu.Unlock()
  99. var once sync.Once
  100. return ch, func() {
  101. once.Do(func() {
  102. b.mu.Lock()
  103. if _, ok := b.subs[ch]; ok {
  104. delete(b.subs, ch)
  105. close(ch)
  106. }
  107. b.mu.Unlock()
  108. })
  109. }
  110. }
  111. // SubscriberCount reports how many live subscribers the broker has. It is
  112. // primarily useful for tests and diagnostics.
  113. func (b *Broker) SubscriberCount() int {
  114. if b == nil {
  115. return 0
  116. }
  117. b.mu.RLock()
  118. defer b.mu.RUnlock()
  119. return len(b.subs)
  120. }
  121. // HistorySince returns retained events with an ID greater than afterID, for
  122. // SSE Last-Event-ID resumption. Pass 0 to replay the whole retained history.
  123. func (b *Broker) HistorySince(afterID uint64) []Event {
  124. if b == nil {
  125. return nil
  126. }
  127. b.mu.RLock()
  128. defer b.mu.RUnlock()
  129. out := make([]Event, 0, len(b.history))
  130. for _, evt := range b.history {
  131. if evt.ID > afterID {
  132. out = append(out, evt)
  133. }
  134. }
  135. return out
  136. }