Add support for detached channels

Channels can now be detached by leaving them with the reason "detach",
and re-attached by joining them again. Upon detaching the channel is
no longer forwarded to downstream connections. Upon re-attaching the
history buffer is sent.
This commit is contained in:
Simon Ser 2020-04-28 15:27:41 +02:00
parent a99d6dd019
commit d9186e994d
No known key found for this signature in database
GPG key ID: 0FDE7BE0E88F5E48
5 changed files with 120 additions and 31 deletions

19
db.go
View file

@ -46,6 +46,7 @@ type Channel struct {
ID int64
Name string
Key string
Detached bool
}
const schema = `
@ -76,6 +77,7 @@ CREATE TABLE Channel (
network INTEGER NOT NULL,
name VARCHAR(255) NOT NULL,
key VARCHAR(255),
detached INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY(network) REFERENCES Network(id),
UNIQUE(network, name)
);
@ -84,6 +86,7 @@ CREATE TABLE Channel (
var migrations = []string{
"", // migration #0 is reserved for schema initialization
"ALTER TABLE Network ADD COLUMN connect_commands VARCHAR(1023)",
"ALTER TABLE Channel ADD COLUMN detached INTEGER NOT NULL DEFAULT 0",
}
type DB struct {
@ -346,7 +349,9 @@ func (db *DB) ListChannels(networkID int64) ([]Channel, error) {
db.lock.RLock()
defer db.lock.RUnlock()
rows, err := db.db.Query("SELECT id, name, key FROM Channel WHERE network = ?", networkID)
rows, err := db.db.Query(`SELECT id, name, key, detached
FROM Channel
WHERE network = ?`, networkID)
if err != nil {
return nil, err
}
@ -356,7 +361,7 @@ func (db *DB) ListChannels(networkID int64) ([]Channel, error) {
for rows.Next() {
var ch Channel
var key *string
if err := rows.Scan(&ch.ID, &ch.Name, &key); err != nil {
if err := rows.Scan(&ch.ID, &ch.Name, &key, &ch.Detached); err != nil {
return nil, err
}
ch.Key = fromStringPtr(key)
@ -378,12 +383,14 @@ func (db *DB) StoreChannel(networkID int64, ch *Channel) error {
var err error
if ch.ID != 0 {
_, err = db.db.Exec(`UPDATE Channel
SET network = ?, name = ?, key = ?
WHERE id = ?`, networkID, ch.Name, key, ch.ID)
SET network = ?, name = ?, key = ?, detached = ?
WHERE id = ?`,
networkID, ch.Name, key, ch.Detached, ch.ID)
} else {
var res sql.Result
res, err = db.db.Exec(`INSERT INTO Channel(network, name, key)
VALUES (?, ?, ?)`, networkID, ch.Name, key)
res, err = db.db.Exec(`INSERT INTO Channel(network, name, key, detached)
VALUES (?, ?, ?, ?)`,
networkID, ch.Name, key, ch.Detached)
if err != nil {
return err
}

View file

@ -23,7 +23,8 @@ behalf of the user to provide extra features.
When joining a channel, the channel will be saved and automatically joined on
the next connection. When registering or authenticating with NickServ, the
credentials will be saved and automatically used on the next connection if the
server supports SASL.
server supports SASL. When parting a channel with the reason "detach", the
channel will be detached instead of being left.
When all clients are disconnected from the bouncer, the user is automatically
marked as away.

View file

@ -767,7 +767,13 @@ func (dc *downstreamConn) welcome() error {
dc.forEachUpstream(func(uc *upstreamConn) {
for _, ch := range uc.channels {
if ch.complete {
if !ch.complete {
continue
}
if record, ok := uc.network.channels[ch.Name]; ok && record.Detached {
continue
}
dc.SendMessage(&irc.Message{
Prefix: dc.prefix(),
Command: "JOIN",
@ -776,7 +782,6 @@ func (dc *downstreamConn) welcome() error {
forwardChannel(dc, ch)
}
}
})
dc.forEachNetwork(func(net *network) {
@ -793,6 +798,10 @@ func (dc *downstreamConn) welcome() error {
func (dc *downstreamConn) sendNetworkHistory(net *network) {
for target, history := range net.history {
if ch, ok := net.channels[target]; ok && ch.Detached {
continue
}
seq, ok := history.offlineClients[dc.clientName]
if !ok {
continue
@ -945,7 +954,7 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
Params: params,
})
ch := &Channel{Name: upstreamName, Key: key}
ch := &Channel{Name: upstreamName, Key: key, Detached: false}
if err := uc.network.createUpdateChannel(ch); err != nil {
dc.logger.Printf("failed to create or update channel %q: %v", upstreamName, err)
}
@ -967,6 +976,12 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
return err
}
if strings.EqualFold(reason, "detach") {
ch := &Channel{Name: upstreamName, Detached: true}
if err := uc.network.createUpdateChannel(ch); err != nil {
dc.logger.Printf("failed to detach channel %q: %v", upstreamName, err)
}
} else {
params := []string{upstreamName}
if reason != "" {
params = append(params, reason)
@ -980,6 +995,7 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
dc.logger.Printf("failed to delete channel %q: %v", upstreamName, err)
}
}
}
case "KICK":
var channelStr, userStr string
if err := parseMessageParams(msg, &channelStr, &userStr); err != nil {

View file

@ -1405,8 +1405,13 @@ func (uc *upstreamConn) appendLog(entity string, msg *irc.Message) {
// appendHistory appends a message to the history. entity can be empty.
func (uc *upstreamConn) appendHistory(entity string, msg *irc.Message) {
detached := false
if ch, ok := uc.network.channels[entity]; ok {
detached = ch.Detached
}
// If no client is offline, no need to append the message to the buffer
if len(uc.network.offlineClients) == 0 {
if len(uc.network.offlineClients) == 0 && !detached {
return
}
@ -1421,6 +1426,14 @@ func (uc *upstreamConn) appendHistory(entity string, msg *irc.Message) {
for clientName, _ := range uc.network.offlineClients {
history.offlineClients[clientName] = 0
}
if detached {
// If the channel is detached, online clients act as offline
// clients too
uc.forEachDownstream(func(dc *downstreamConn) {
history.offlineClients[dc.clientName] = 0
})
}
}
history.ring.Produce(msg)
@ -1438,6 +1451,11 @@ func (uc *upstreamConn) produce(target string, msg *irc.Message, origin *downstr
uc.appendHistory(target, msg)
// Don't forward messages if it's a detached channel
if ch, ok := uc.network.channels[target]; ok && ch.Detached {
return
}
uc.forEachDownstream(func(dc *downstreamConn) {
if dc != origin || dc.caps["echo-message"] {
dc.SendMessage(dc.marshalMessage(msg, uc.network))

49
user.go
View file

@ -150,7 +150,51 @@ func (net *network) createUpdateChannel(ch *Channel) error {
if err := net.user.srv.db.StoreChannel(net.ID, ch); err != nil {
return err
}
prev := net.channels[ch.Name]
net.channels[ch.Name] = ch
if prev != nil && prev.Detached != ch.Detached {
history := net.history[ch.Name]
if ch.Detached {
net.user.srv.Logger.Printf("network %q: detaching channel %q", net.GetName(), ch.Name)
net.forEachDownstream(func(dc *downstreamConn) {
net.offlineClients[dc.clientName] = struct{}{}
if history != nil {
history.offlineClients[dc.clientName] = history.ring.Cur()
}
dc.SendMessage(&irc.Message{
Prefix: dc.prefix(),
Command: "PART",
Params: []string{dc.marshalEntity(net, ch.Name), "Detach"},
})
})
} else {
net.user.srv.Logger.Printf("network %q: attaching channel %q", net.GetName(), ch.Name)
var uch *upstreamChannel
if net.conn != nil {
uch = net.conn.channels[ch.Name]
}
net.forEachDownstream(func(dc *downstreamConn) {
dc.SendMessage(&irc.Message{
Prefix: dc.prefix(),
Command: "JOIN",
Params: []string{dc.marshalEntity(net, ch.Name)},
})
if uch != nil {
forwardChannel(dc, uch)
}
if history != nil {
dc.sendNetworkHistory(net)
}
})
}
}
return nil
}
@ -342,7 +386,10 @@ func (u *user) run() {
}
net.offlineClients[dc.clientName] = struct{}{}
for _, history := range net.history {
for target, history := range net.history {
if ch, ok := net.channels[target]; ok && ch.Detached {
continue
}
history.offlineClients[dc.clientName] = history.ring.Cur()
}
})