commit 0ba374b

delthas  ·  2022-08-10 11:06:54 +0000 UTC
parent 9fe1d36
Add support for soju.im/bouncer-networks-notify

This enables dynamic discovery of new and deleted networks.

Fixes: https://todo.sr.ht/~taiite/senpai/71
5 files changed,  +89, -19
M app.go
M app.go
+51, -7
  1@@ -8,6 +8,7 @@ import (
  2 	"os"
  3 	"os/exec"
  4 	"strings"
  5+	"sync"
  6 	"time"
  7 	"unicode"
  8 
  9@@ -84,7 +85,7 @@ type boundKey struct {
 10 
 11 type App struct {
 12 	win      *ui.UI
 13-	sessions map[string]*irc.Session
 14+	sessions map[string]*irc.Session // map of network IDs to their current session
 15 	pasting  bool
 16 	events   chan event
 17 
 18@@ -99,12 +100,18 @@ type App struct {
 19 
 20 	monitor map[string]map[string]struct{} // set of targets we want to monitor per netID, best-effort. netID->target->{}
 21 
 22+	networkLock sync.RWMutex        // locks networks
 23+	networks    map[string]struct{} // set of network IDs we want to connect to; to be locked with networkLock
 24+
 25 	lastMessageTime time.Time
 26 	lastCloseTime   time.Time
 27 }
 28 
 29 func NewApp(cfg Config) (app *App, err error) {
 30 	app = &App{
 31+		networks: map[string]struct{}{
 32+			"": {}, // add the master network by default
 33+		},
 34 		sessions:      map[string]*irc.Session{},
 35 		events:        make(chan event, eventChanSize),
 36 		cfg:           cfg,
 37@@ -262,6 +269,16 @@ func (app *App) handleEvent(ev event) bool {
 38 	return true
 39 }
 40 
 41+func (app *App) wantsNetwork(netID string) bool {
 42+	if app.win.ShouldExit() {
 43+		return false
 44+	}
 45+	app.networkLock.RLock()
 46+	_, ok := app.networks[netID]
 47+	app.networkLock.RUnlock()
 48+	return ok
 49+}
 50+
 51 // ircLoop maintains a connection to the IRC server by connecting and then
 52 // forwarding IRC events to app.events repeatedly.
 53 func (app *App) ircLoop(netID string) {
 54@@ -279,8 +296,11 @@ func (app *App) ircLoop(netID string) {
 55 		NetID:    netID,
 56 		Auth:     auth,
 57 	}
 58-	for !app.win.ShouldExit() {
 59+	for app.wantsNetwork(netID) {
 60 		conn := app.connect(netID)
 61+		if conn == nil {
 62+			break
 63+		}
 64 		in, out := irc.ChanInOut(conn)
 65 		if app.cfg.Debug {
 66 			out = app.debugOutputMessages(netID, out)
 67@@ -320,7 +340,7 @@ func (app *App) ircLoop(netID string) {
 68 			HeadColor: tcell.ColorRed,
 69 			Body:      ui.PlainString("Connection lost"),
 70 		})
 71-		if app.win.ShouldExit() {
 72+		if !app.wantsNetwork(netID) {
 73 			break
 74 		}
 75 		time.Sleep(10 * time.Second)
 76@@ -328,7 +348,7 @@ func (app *App) ircLoop(netID string) {
 77 }
 78 
 79 func (app *App) connect(netID string) net.Conn {
 80-	for {
 81+	for app.wantsNetwork(netID) {
 82 		app.queueStatusLine(netID, ui.Line{
 83 			Head: "--",
 84 			Body: ui.PlainSprintf("Connecting to %s...", app.cfg.Addr),
 85@@ -344,6 +364,7 @@ func (app *App) connect(netID string) net.Conn {
 86 		})
 87 		time.Sleep(1 * time.Minute)
 88 	}
 89+	return nil
 90 }
 91 
 92 func (app *App) tryConnect() (conn net.Conn, err error) {
 93@@ -663,6 +684,12 @@ func (app *App) handleIRCEvent(netID string, ev interface{}) {
 94 		if s, ok := app.sessions[netID]; ok {
 95 			s.Close()
 96 		}
 97+		if !app.wantsNetwork(netID) {
 98+			delete(app.sessions, netID)
 99+			delete(app.monitor, netID)
100+			s.Close()
101+			return
102+		}
103 		app.sessions[netID] = s
104 		if _, ok := app.monitor[netID]; !ok {
105 			app.monitor[netID] = make(map[string]struct{})
106@@ -921,9 +948,26 @@ func (app *App) handleIRCEvent(netID string, ev interface{}) {
107 	case irc.ReadEvent:
108 		app.win.SetRead(netID, ev.Target, ev.Timestamp)
109 	case irc.BouncerNetworkEvent:
110-		_, added := app.win.AddBuffer(ev.ID, ev.Name, "")
111-		if added {
112-			go app.ircLoop(ev.ID)
113+		if !ev.Delete {
114+			_, added := app.win.AddBuffer(ev.ID, ev.Name, "")
115+			if added {
116+				app.networkLock.Lock()
117+				app.networks[ev.ID] = struct{}{}
118+				app.networkLock.Unlock()
119+				go app.ircLoop(ev.ID)
120+			}
121+		} else {
122+			app.networkLock.Lock()
123+			delete(app.networks, ev.ID)
124+			app.networkLock.Unlock()
125+			// if a session was already opened, close it now.
126+			// otherwise, we'll close it when it sends a new session event.
127+			if s, ok := app.sessions[ev.ID]; ok {
128+				s.Close()
129+				delete(app.sessions, ev.ID)
130+				delete(app.monitor, ev.ID)
131+			}
132+			app.win.RemoveNetworkBuffers(ev.ID)
133 		}
134 	case irc.ErrorEvent:
135 		if isBlackListed(msg.Command) {
+3, -2
 1@@ -104,6 +104,7 @@ type SearchEvent struct {
 2 }
 3 
 4 type BouncerNetworkEvent struct {
 5-	ID   string
 6-	Name string
 7+	ID     string
 8+	Name   string
 9+	Delete bool
10 }
+16, -10
 1@@ -62,11 +62,12 @@ var SupportedCapabilities = map[string]struct{}{
 2 	"sasl":          {},
 3 	"setname":       {},
 4 
 5-	"draft/chathistory":        {},
 6-	"draft/event-playback":     {},
 7-	"draft/read-marker":        {},
 8-	"soju.im/bouncer-networks": {},
 9-	"soju.im/search":           {},
10+	"draft/chathistory":               {},
11+	"draft/event-playback":            {},
12+	"draft/read-marker":               {},
13+	"soju.im/bouncer-networks-notify": {},
14+	"soju.im/bouncer-networks":        {},
15+	"soju.im/search":                  {},
16 }
17 
18 // Values taken by the "@+typing=" client tag.  TypingUnspec means the value or
19@@ -1336,11 +1337,16 @@ func (s *Session) handleMessageRegistered(msg Message, playback bool) (Event, er
20 			break
21 		}
22 		id := msg.Params[1]
23-		attrs := parseTags(msg.Params[2])
24-		return BouncerNetworkEvent{
25-			ID:   id,
26-			Name: attrs["name"],
27-		}, nil
28+		event := BouncerNetworkEvent{
29+			ID: id,
30+		}
31+		if msg.Params[2] != "*" {
32+			attrs := parseTags(msg.Params[2])
33+			event.Name = attrs["name"]
34+		} else {
35+			event.Delete = true
36+		}
37+		return event, nil
38 	case "PING":
39 		var payload string
40 		if err := msg.ParseParams(&payload); err != nil {
+14, -0
 1@@ -342,6 +342,20 @@ func (bs *BufferList) Remove(netID, title string) bool {
 2 	return true
 3 }
 4 
 5+func (bs *BufferList) RemoveNetwork(netID string) {
 6+	for idx := 0; idx < len(bs.list); idx++ {
 7+		b := &bs.list[idx]
 8+		if b.netID != netID {
 9+			continue
10+		}
11+		bs.list = append(bs.list[:idx], bs.list[idx+1:]...)
12+		if len(bs.list) <= bs.current {
13+			bs.current--
14+		}
15+		idx--
16+	}
17+}
18+
19 func (bs *BufferList) mergeLine(former *Line, addition Line) (keepLine bool) {
20 	bs.doMergeLine(former, addition)
21 	if former.Body.string == "" {
+5, -0
 1@@ -260,6 +260,11 @@ func (ui *UI) RemoveBuffer(netID, title string) {
 2 	ui.memberOffset = 0
 3 }
 4 
 5+func (ui *UI) RemoveNetworkBuffers(netID string) {
 6+	ui.bs.RemoveNetwork(netID)
 7+	ui.memberOffset = 0
 8+}
 9+
10 func (ui *UI) AddLine(netID, buffer string, notify NotifyType, line Line) {
11 	ui.bs.AddLine(netID, buffer, notify, line)
12 }