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:
parent
a99d6dd019
commit
d9186e994d
19
db.go
19
db.go
@ -46,6 +46,7 @@ type Channel struct {
|
|||||||
ID int64
|
ID int64
|
||||||
Name string
|
Name string
|
||||||
Key string
|
Key string
|
||||||
|
Detached bool
|
||||||
}
|
}
|
||||||
|
|
||||||
const schema = `
|
const schema = `
|
||||||
@ -76,6 +77,7 @@ CREATE TABLE Channel (
|
|||||||
network INTEGER NOT NULL,
|
network INTEGER NOT NULL,
|
||||||
name VARCHAR(255) NOT NULL,
|
name VARCHAR(255) NOT NULL,
|
||||||
key VARCHAR(255),
|
key VARCHAR(255),
|
||||||
|
detached INTEGER NOT NULL DEFAULT 0,
|
||||||
FOREIGN KEY(network) REFERENCES Network(id),
|
FOREIGN KEY(network) REFERENCES Network(id),
|
||||||
UNIQUE(network, name)
|
UNIQUE(network, name)
|
||||||
);
|
);
|
||||||
@ -84,6 +86,7 @@ CREATE TABLE Channel (
|
|||||||
var migrations = []string{
|
var migrations = []string{
|
||||||
"", // migration #0 is reserved for schema initialization
|
"", // migration #0 is reserved for schema initialization
|
||||||
"ALTER TABLE Network ADD COLUMN connect_commands VARCHAR(1023)",
|
"ALTER TABLE Network ADD COLUMN connect_commands VARCHAR(1023)",
|
||||||
|
"ALTER TABLE Channel ADD COLUMN detached INTEGER NOT NULL DEFAULT 0",
|
||||||
}
|
}
|
||||||
|
|
||||||
type DB struct {
|
type DB struct {
|
||||||
@ -346,7 +349,9 @@ func (db *DB) ListChannels(networkID int64) ([]Channel, error) {
|
|||||||
db.lock.RLock()
|
db.lock.RLock()
|
||||||
defer db.lock.RUnlock()
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -356,7 +361,7 @@ func (db *DB) ListChannels(networkID int64) ([]Channel, error) {
|
|||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var ch Channel
|
var ch Channel
|
||||||
var key *string
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
ch.Key = fromStringPtr(key)
|
ch.Key = fromStringPtr(key)
|
||||||
@ -378,12 +383,14 @@ func (db *DB) StoreChannel(networkID int64, ch *Channel) error {
|
|||||||
var err error
|
var err error
|
||||||
if ch.ID != 0 {
|
if ch.ID != 0 {
|
||||||
_, err = db.db.Exec(`UPDATE Channel
|
_, err = db.db.Exec(`UPDATE Channel
|
||||||
SET network = ?, name = ?, key = ?
|
SET network = ?, name = ?, key = ?, detached = ?
|
||||||
WHERE id = ?`, networkID, ch.Name, key, ch.ID)
|
WHERE id = ?`,
|
||||||
|
networkID, ch.Name, key, ch.Detached, ch.ID)
|
||||||
} else {
|
} else {
|
||||||
var res sql.Result
|
var res sql.Result
|
||||||
res, err = db.db.Exec(`INSERT INTO Channel(network, name, key)
|
res, err = db.db.Exec(`INSERT INTO Channel(network, name, key, detached)
|
||||||
VALUES (?, ?, ?)`, networkID, ch.Name, key)
|
VALUES (?, ?, ?, ?)`,
|
||||||
|
networkID, ch.Name, key, ch.Detached)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -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
|
When joining a channel, the channel will be saved and automatically joined on
|
||||||
the next connection. When registering or authenticating with NickServ, the
|
the next connection. When registering or authenticating with NickServ, the
|
||||||
credentials will be saved and automatically used on the next connection if 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
|
When all clients are disconnected from the bouncer, the user is automatically
|
||||||
marked as away.
|
marked as away.
|
||||||
|
@ -767,7 +767,13 @@ func (dc *downstreamConn) welcome() error {
|
|||||||
|
|
||||||
dc.forEachUpstream(func(uc *upstreamConn) {
|
dc.forEachUpstream(func(uc *upstreamConn) {
|
||||||
for _, ch := range uc.channels {
|
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{
|
dc.SendMessage(&irc.Message{
|
||||||
Prefix: dc.prefix(),
|
Prefix: dc.prefix(),
|
||||||
Command: "JOIN",
|
Command: "JOIN",
|
||||||
@ -776,7 +782,6 @@ func (dc *downstreamConn) welcome() error {
|
|||||||
|
|
||||||
forwardChannel(dc, ch)
|
forwardChannel(dc, ch)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
dc.forEachNetwork(func(net *network) {
|
dc.forEachNetwork(func(net *network) {
|
||||||
@ -793,6 +798,10 @@ func (dc *downstreamConn) welcome() error {
|
|||||||
|
|
||||||
func (dc *downstreamConn) sendNetworkHistory(net *network) {
|
func (dc *downstreamConn) sendNetworkHistory(net *network) {
|
||||||
for target, history := range net.history {
|
for target, history := range net.history {
|
||||||
|
if ch, ok := net.channels[target]; ok && ch.Detached {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
seq, ok := history.offlineClients[dc.clientName]
|
seq, ok := history.offlineClients[dc.clientName]
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
@ -945,7 +954,7 @@ func (dc *downstreamConn) handleMessageRegistered(msg *irc.Message) error {
|
|||||||
Params: params,
|
Params: params,
|
||||||
})
|
})
|
||||||
|
|
||||||
ch := &Channel{Name: upstreamName, Key: key}
|
ch := &Channel{Name: upstreamName, Key: key, Detached: false}
|
||||||
if err := uc.network.createUpdateChannel(ch); err != nil {
|
if err := uc.network.createUpdateChannel(ch); err != nil {
|
||||||
dc.logger.Printf("failed to create or update channel %q: %v", upstreamName, err)
|
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
|
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}
|
params := []string{upstreamName}
|
||||||
if reason != "" {
|
if reason != "" {
|
||||||
params = append(params, 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)
|
dc.logger.Printf("failed to delete channel %q: %v", upstreamName, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
case "KICK":
|
case "KICK":
|
||||||
var channelStr, userStr string
|
var channelStr, userStr string
|
||||||
if err := parseMessageParams(msg, &channelStr, &userStr); err != nil {
|
if err := parseMessageParams(msg, &channelStr, &userStr); err != nil {
|
||||||
|
20
upstream.go
20
upstream.go
@ -1405,8 +1405,13 @@ func (uc *upstreamConn) appendLog(entity string, msg *irc.Message) {
|
|||||||
|
|
||||||
// appendHistory appends a message to the history. entity can be empty.
|
// appendHistory appends a message to the history. entity can be empty.
|
||||||
func (uc *upstreamConn) appendHistory(entity string, msg *irc.Message) {
|
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 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1421,6 +1426,14 @@ func (uc *upstreamConn) appendHistory(entity string, msg *irc.Message) {
|
|||||||
for clientName, _ := range uc.network.offlineClients {
|
for clientName, _ := range uc.network.offlineClients {
|
||||||
history.offlineClients[clientName] = 0
|
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)
|
history.ring.Produce(msg)
|
||||||
@ -1438,6 +1451,11 @@ func (uc *upstreamConn) produce(target string, msg *irc.Message, origin *downstr
|
|||||||
|
|
||||||
uc.appendHistory(target, msg)
|
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) {
|
uc.forEachDownstream(func(dc *downstreamConn) {
|
||||||
if dc != origin || dc.caps["echo-message"] {
|
if dc != origin || dc.caps["echo-message"] {
|
||||||
dc.SendMessage(dc.marshalMessage(msg, uc.network))
|
dc.SendMessage(dc.marshalMessage(msg, uc.network))
|
||||||
|
49
user.go
49
user.go
@ -150,7 +150,51 @@ func (net *network) createUpdateChannel(ch *Channel) error {
|
|||||||
if err := net.user.srv.db.StoreChannel(net.ID, ch); err != nil {
|
if err := net.user.srv.db.StoreChannel(net.ID, ch); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
prev := net.channels[ch.Name]
|
||||||
net.channels[ch.Name] = ch
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -342,7 +386,10 @@ func (u *user) run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
net.offlineClients[dc.clientName] = struct{}{}
|
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()
|
history.offlineClients[dc.clientName] = history.ring.Cur()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user