github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/pkg/realtime/topic.go (about) 1 package realtime 2 3 type filter struct { 4 whole bool // true if the events for the whole doctype should be sent 5 ids []string 6 } 7 8 type toWatch struct { 9 sub *Subscriber 10 id string // empty string means the whole doctype 11 } 12 13 type topic struct { 14 broadcast chan *Event // input 15 subs map[*Subscriber]filter // output 16 subscribe chan *toWatch 17 unsubscribe chan *toWatch 18 running chan bool 19 } 20 21 func newTopic() *topic { 22 topic := &topic{ 23 broadcast: make(chan *Event, 10), 24 subs: make(map[*Subscriber]filter), 25 subscribe: make(chan *toWatch), 26 unsubscribe: make(chan *toWatch), 27 running: make(chan bool), 28 } 29 go topic.loop() 30 return topic 31 } 32 33 func (t *topic) loop() { 34 for { 35 select { 36 case e := <-t.broadcast: 37 t.publish(e) 38 case w := <-t.subscribe: 39 t.doSubscribe(w) 40 case w := <-t.unsubscribe: 41 t.doUnsubscribe(w) 42 if len(t.subs) == 0 { 43 close(t.running) 44 return 45 } 46 t.running <- true 47 } 48 } 49 } 50 51 func (t *topic) publish(e *Event) { 52 for s, f := range t.subs { 53 ok := false 54 if f.whole { 55 ok = true 56 } else { 57 for _, id := range f.ids { 58 if e.Doc.ID() == id { 59 ok = true 60 break 61 } 62 } 63 } 64 if ok { 65 select { 66 case s.Channel <- e: 67 case <-s.running: // the subscriber has been closed 68 } 69 } 70 } 71 } 72 73 func (t *topic) doSubscribe(w *toWatch) { 74 f := t.subs[w.sub] 75 if w.id == "" { 76 f.whole = true 77 } else { 78 f.ids = append(f.ids, w.id) 79 } 80 t.subs[w.sub] = f 81 } 82 83 func (t *topic) doUnsubscribe(w *toWatch) { 84 if w.id == "" { 85 delete(t.subs, w.sub) 86 } else if f, ok := t.subs[w.sub]; ok { 87 ids := f.ids[:0] 88 for _, id := range f.ids { 89 if id != w.id { 90 ids = append(ids, id) 91 } 92 } 93 if len(ids) == 0 && !f.whole { 94 delete(t.subs, w.sub) 95 } else { 96 f.ids = ids 97 t.subs[w.sub] = f 98 } 99 } 100 }