master sewn/kohai / irc / typing.go
  1package irc
  2
  3import (
  4	"sync"
  5	"time"
  6
  7	"golang.org/x/time/rate"
  8)
  9
 10// Typing is an event of Name actively typing in Target.
 11type Typing struct {
 12	Target string
 13	Name   string
 14}
 15
 16// Typings keeps track of typing notification timeouts.
 17type Typings struct {
 18	l        sync.Mutex
 19	closed   bool                 // whether Close has been called
 20	targets  map[Typing]time.Time // @+typing TAGMSG timestamps.
 21	timeouts chan Typing          // transmits unfiltered timeout notifications.
 22	stops    chan Typing          // transmits filtered timeout notifications.
 23}
 24
 25// NewTypings initializes the Typings structures and filtering coroutine.
 26func NewTypings() *Typings {
 27	ts := &Typings{
 28		targets:  map[Typing]time.Time{},
 29		timeouts: make(chan Typing, 16),
 30		stops:    make(chan Typing, 16),
 31	}
 32	go func() {
 33		for t := range ts.timeouts {
 34			now := time.Now()
 35			ts.l.Lock()
 36			oldT, ok := ts.targets[t]
 37			if ok && 6.0 < now.Sub(oldT).Seconds() {
 38				delete(ts.targets, t)
 39				ts.l.Unlock()
 40				ts.stops <- t
 41			} else {
 42				ts.l.Unlock()
 43			}
 44		}
 45	}()
 46	return ts
 47}
 48
 49// Close cleanly closes all channels and stops all goroutines.
 50func (ts *Typings) Close() {
 51	ts.l.Lock()
 52	defer ts.l.Unlock()
 53
 54	close(ts.timeouts)
 55	close(ts.stops)
 56	ts.closed = true
 57}
 58
 59// Stops is a channel that transmits typing timeouts.
 60func (ts *Typings) Stops() <-chan Typing {
 61	return ts.stops
 62}
 63
 64// Active should be called when a user is typing to some target.
 65func (ts *Typings) Active(target, name string) {
 66	ts.l.Lock()
 67	t := Typing{target, name}
 68	ts.targets[t] = time.Now()
 69	ts.l.Unlock()
 70
 71	go func() {
 72		time.Sleep(6 * time.Second)
 73
 74		ts.l.Lock()
 75		closed := ts.closed
 76		ts.l.Unlock()
 77
 78		if !closed {
 79			ts.timeouts <- t
 80		}
 81	}()
 82}
 83
 84// Done should be called when a user is done typing to some target.
 85func (ts *Typings) Done(target, name string) {
 86	ts.l.Lock()
 87	delete(ts.targets, Typing{target, name})
 88	ts.l.Unlock()
 89}
 90
 91func (ts *Typings) List(target string) []string {
 92	ts.l.Lock()
 93	defer ts.l.Unlock()
 94
 95	var res []string
 96	for t := range ts.targets {
 97		if target == t.Target {
 98			res = append(res, t.Name)
 99		}
100	}
101	return res
102}
103
104type typingStamp struct {
105	Last  time.Time
106	Type  int
107	Limit *rate.Limiter
108}