diff --git a/book.go b/book.go new file mode 100644 index 0000000..6e7f287 --- /dev/null +++ b/book.go @@ -0,0 +1,92 @@ +package main + +import ( + "io" + "log" +) + +type Book struct { + entries map[string]*Repeater + events chan bookEvent +} + + +func NewBook() Book { + return Book{ + entries: make(map[string]*Repeater), + events: make(chan bookEvent, 5), + } +} + +type bookEventType int +const ( + joinEvent = bookEventType(iota) + partEvent + sendEvent +) + +type bookEvent struct { + eventType bookEventType + name string + w io.Writer + p []byte +} + +func (b Book) Join(name string, w io.Writer) { + b.events <- bookEvent{ + eventType: joinEvent, + name: name, + w: w, + } +} + +func (b Book) Part(name string, w io.Writer) { + b.events <- bookEvent{ + eventType: partEvent, + name: name, + w: w, + } +} + +func (b Book) Send(name string, p []byte) { + b.events <- bookEvent{ + eventType: sendEvent, + name: name, + p: p, + } +} + +func (b Book) Run() { + for { + b.loop() + } +} + +func (b Book) loop() { + event := <-b.events + repeater, ok := b.entries[event.name] + + switch event.eventType { + case joinEvent: + if ! ok { + repeater = NewRepeater() + b.entries[event.name] = repeater + } + repeater.Join(event.w) + case partEvent: + if ! ok { + log.Println("WARN: Parting an empty channel:", event.name) + break + } + repeater.Part(event.w) + if len(repeater.subscribers) == 0 { + delete(b.entries, event.name) + } + case sendEvent: + if ! ok { + log.Println("WARN: Sending to an empty channel:", event.name) + break + } + repeater.Send(event.p) + } +} diff --git a/book_test.go b/book_test.go new file mode 100644 index 0000000..20d7819 --- /dev/null +++ b/book_test.go @@ -0,0 +1,59 @@ +package main + +import ( + "bytes" + "testing" +) + +func TestBook(t *testing.T) { + b := NewBook() + + buf1 := bytes.NewBufferString("buf1") + b.Join("moo", buf1) + b.loop() + if len(b.entries) != 1 { + t.Error("Wrong number of entries") + } + + // Send to an empty channel + b.Send("merf", []byte("goober")) + b.loop() + if buf1.String() != "buf1" { + t.Error("Sending to empty channel sent to non-empty channel") + } + + // Send to a non-empty channel! + b.Send("moo", []byte("goober")) + b.loop() + if buf1.String() != "buf1goober" { + t.Error("Sending didn't work") + } + + // Join another client + buf2 := bytes.NewBufferString("buf2") + b.Join("moo", buf2) + b.loop() + + // Send to both + b.Send("moo", []byte("snerk")) + b.loop() + if buf1.String() != "buf1goobersnerk" { + t.Error("Send to 2-member channel busted", buf1) + } + if buf2.String() != "buf2snerk" { + t.Error("Send to 2-member channel busted", buf2) + } + + // Part a client + b.Part("moo", buf1) + b.loop() + + b.Send("moo", []byte("peanut")) + b.loop() + if buf1.String() != "buf1goobersnerk" { + t.Error("Parted channel but still getting messages", buf1) + } + if buf2.String() != "buf2snerkpeanut" { + t.Error("Someone else parting somehow messed up sends", buf2) + } +} diff --git a/main.go b/main.go index 4781e1e..f8ecb61 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,22 @@ import ( "golang.org/x/net/websocket" ) +func (ch Channel) Write(msg Message) { + for i, o := range clients { + if o.active == false { + nclients := len(clients) + clients[i] = clients[nclients - 1] + clients[nclients - 1] = Client{} + clients = clients[:nclients - 1] + } else if o == c { + // Don't send it back to the sending client + } else { + o.ws.Write(msg) + } + } +} + + type Client struct { ws *websocket.Conn active bool @@ -14,6 +30,7 @@ type Client struct { var clients []Client func (c Client) Chat() { + websocket.Message.Receive for c.active { buf := make([]byte, 800) n, err := c.ws.Read(buf) @@ -21,19 +38,6 @@ func (c Client) Chat() { c.active = false } buf = buf[:n] - - for i, o := range clients { - if o.active == false { - nclients := len(clients) - clients[i] = clients[nclients - 1] - clients[nclients - 1] = Client{} - clients = clients[:nclients - 1] - } else if o == c { - // Don't send it back to the sending client - } else { - o.ws.Write(buf) - } - } } } diff --git a/repeater.go b/repeater.go index 0a00af8..1f9003e 100644 --- a/repeater.go +++ b/repeater.go @@ -5,62 +5,31 @@ import ( ) type Repeater struct { - joins chan io.Writer - parts chan io.Writer - sends chan []byte subscribers []io.Writer } func NewRepeater() *Repeater { return &Repeater{ - joins: make(chan io.Writer, 5), - parts: make(chan io.Writer, 5), - sends: make(chan []byte, 5), subscribers: make([]io.Writer, 0, 20), } } func (r *Repeater) Join(w io.Writer) { - r.joins <- w + r.subscribers = append(r.subscribers, w) } func (r *Repeater) Part(w io.Writer) { - r.parts <- w + for i, s := range r.subscribers { + if s == w { + nsubs := len(r.subscribers) + r.subscribers[i] = r.subscribers[nsubs-1] + r.subscribers = r.subscribers[:nsubs-1] + } + } } func (r *Repeater) Send(p []byte) { - r.sends <- p -} - -func (r *Repeater) Close() { - close(r.sends) -} - -func (r *Repeater) Run() { - for r.loop() {} -} - -func (r *Repeater) loop() bool { - select { - case w := <- r.joins: - // Add subscriber - r.subscribers = append(r.subscribers, w) - case w := <- r.parts: - // Remove subscriber - for i, s := range r.subscribers { - if s == w { - nsubs := len(r.subscribers) - r.subscribers[i] = r.subscribers[nsubs-1] - r.subscribers = r.subscribers[:nsubs-1] - } - } - case p, ok := <- r.sends: - if ! ok { - return false - } - for _, s := range r.subscribers { - s.Write(p) - } + for _, s := range r.subscribers { + s.Write(p) } - return true } diff --git a/repeater_test.go b/repeater_test.go index 87e4657..4b6a8f9 100644 --- a/repeater_test.go +++ b/repeater_test.go @@ -10,21 +10,17 @@ func TestRepeater(t *testing.T) { buf1 := bytes.NewBufferString("buf1") r.Join(buf1) - r.loop() if len(r.subscribers) != 1 { t.Error("Joining did nothing") } r.Send([]byte("moo")) - r.loop() if buf1.String() != "buf1moo" { t.Error("Client 1 not repeating", buf1) } buf2 := bytes.NewBufferString("buf2") r.Join(buf2) - r.loop() r.Send([]byte("bar")) - r.loop() if buf1.String() != "buf1moobar" { t.Error("Client 1 not repeating", buf1) } @@ -33,21 +29,11 @@ func TestRepeater(t *testing.T) { } r.Part(buf1) - r.loop() r.Send([]byte("baz")) - r.loop() if buf1.String() != "buf1moobar" { t.Error("Client 1 still getting data after part", buf1) } if buf2.String() != "buf2barbaz" { t.Error("Client 2 not getting data after part", buf2) } - - r.Close() - if r.loop() { - t.Error("Closed send didn't terminate loop") - } - if r.loop() { - t.Error("Second loop in terminated channel didn't terminate") - } } diff --git a/static/1280px-Morse-code-tree.svg.png b/static/1280px-Morse-code-tree.svg.png deleted file mode 100644 index 77a2917..0000000 Binary files a/static/1280px-Morse-code-tree.svg.png and /dev/null differ diff --git a/static/index.html b/static/index.html index 83a191d..afb97b9 100644 --- a/static/index.html +++ b/static/index.html @@ -7,7 +7,7 @@
- +