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  }