X-Git-Url: http://www.git.cypherpunks.ru/?p=goircd.git;a=blobdiff_plain;f=room.go;h=cc2b0d739938e3998a3cc08089c882c4fdf263a3;hp=4bdb4b2f18427c728c762d74b85e6149bc7215f2;hb=b7fb219307483d2c31b5dad1f559f325f2fd1a5e;hpb=ad68cf29f652c5bfa7822a10000e9da658fbf1d5 diff --git a/room.go b/room.go index 4bdb4b2..cc2b0d7 100644 --- a/room.go +++ b/room.go @@ -1,11 +1,10 @@ /* goircd -- minimalistic simple Internet Relay Chat (IRC) server -Copyright (C) 2014-2015 Sergey Matveev +Copyright (C) 2014-2020 Sergey Matveev This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. +the Free Software Foundation, version 3 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -24,185 +23,216 @@ import ( "regexp" "sort" "strings" + "sync" ) +type Room struct { + name string + topic string + key string + members map[*Client]struct{} + events chan ClientEvent + sync.RWMutex +} + var ( RERoom = regexp.MustCompile("^#[^\x00\x07\x0a\x0d ,:/]{1,200}$") -) -// Sanitize room's name. It can consist of 1 to 50 ASCII symbols -// with some exclusions. All room names will have "#" prefix. -func RoomNameValid(name string) bool { - return RERoom.MatchString(name) -} - -type Room struct { - Verbose bool - name string - topic string - key string - members map[*Client]bool - hostname *string - logSink chan<- LogEvent - stateSink chan<- StateEvent -} + rooms map[string]*Room = make(map[string]*Room) + roomsLock sync.RWMutex + roomsWG sync.WaitGroup +) -func (room Room) String() string { - return room.name +func (r *Room) SendTopic(c *Client) { + t := r.topic + if t == "" { + c.ReplyNicknamed("331", r.name, "No topic is set") + } else { + c.ReplyNicknamed("332", r.name, t) + } } -func NewRoom(hostname *string, name string, logSink chan<- LogEvent, stateSink chan<- StateEvent) *Room { - room := Room{name: name} - room.members = make(map[*Client]bool) - room.topic = "" - room.key = "" - room.hostname = hostname - room.logSink = logSink - room.stateSink = stateSink - return &room -} +func (r *Room) SendNames(c *Client) { + allowed := false + if r.key == "" { + allowed = true + } else if _, isMember := r.members[c]; isMember { + allowed = true + } + if !allowed { + c.ReplyNicknamed("475", r.name, "Cannot join channel (+k)") + return + } + r.RLock() + nicknames := make([]string, 0, len(r.members)) + for member := range r.members { + nicknames = append(nicknames, member.nickname) + } + r.RUnlock() + sort.Strings(nicknames) + maxLen := 512 - len(*hostname) - 2 - 2 -func (room *Room) SendTopic(client *Client) { - if room.topic == "" { - client.ReplyNicknamed("331", room.name, "No topic is set") - } else { - client.ReplyNicknamed("332", room.name, room.topic) +MoreNicknames: + lenAll := 0 + lenName := 0 + for i, n := range nicknames { + lenName = len(n) + 1 + if lenAll+lenName >= maxLen { + c.ReplyNicknamed("353", "=", r.name, strings.Join(nicknames[:i-1], " ")) + nicknames = nicknames[i:] + goto MoreNicknames + } + lenAll += lenName + } + if len(nicknames) > 0 { + c.ReplyNicknamed("353", "=", r.name, strings.Join(nicknames, " ")) } + c.ReplyNicknamed("366", r.name, "End of NAMES list") } -// Send message to all room's subscribers, possibly excluding someone -func (room *Room) Broadcast(msg string, clientToIgnore ...*Client) { - for member := range room.members { - if (len(clientToIgnore) > 0) && member == clientToIgnore[0] { +func (r *Room) Broadcast(msg string, excludes ...*Client) { + var exclude *Client + if len(excludes) > 0 { + exclude = excludes[0] + } + r.RLock() + for member := range r.members { + if member == exclude { continue } member.Msg(msg) } + r.RUnlock() } -func (room *Room) StateSave() { - room.stateSink <- StateEvent{room.name, room.topic, room.key} +func (r *Room) StateSave() { + stateSink <- StateEvent{r.name, r.topic, r.key} } -func (room *Room) Processor(events <-chan ClientEvent) { - var client *Client - for event := range events { - client = event.client - switch event.eventType { +func (r *Room) Processor(events <-chan ClientEvent) { + for e := range events { + c := e.client + switch e.eventType { + case EventTerm: + roomsWG.Done() + return case EventNew: - room.members[client] = true - if room.Verbose { - log.Println(client, "joined", room.name) - } - room.SendTopic(client) - room.Broadcast(fmt.Sprintf(":%s JOIN %s", client, room.name)) - room.logSink <- LogEvent{room.name, client.nickname, "joined", true} - nicknames := []string{} - for member := range room.members { - nicknames = append(nicknames, member.nickname) + r.Lock() + r.members[c] = struct{}{} + r.Unlock() + if *verbose { + log.Println(c, "joined", r.name) } - sort.Strings(nicknames) - client.ReplyNicknamed("353", "=", room.name, strings.Join(nicknames, " ")) - client.ReplyNicknamed("366", room.name, "End of NAMES list") + r.SendTopic(c) + r.Broadcast(fmt.Sprintf(":%s JOIN %s", c, r.name)) + logSink <- LogEvent{r.name, c.nickname, "joined", true} + r.SendNames(c) case EventDel: - if _, subscribed := room.members[client]; !subscribed { - client.ReplyNicknamed("442", room.name, "You are not on that channel") + if _, subscribed := r.members[c]; !subscribed { + c.ReplyNicknamed("442", r.name, "You are not on that channel") continue } - delete(room.members, client) - msg := fmt.Sprintf(":%s PART %s :%s", client, room.name, client.nickname) - room.Broadcast(msg) - room.logSink <- LogEvent{room.name, client.nickname, "left", true} + msg := fmt.Sprintf(":%s PART %s :%s", c, r.name, c.nickname) + r.Broadcast(msg) + r.Lock() + delete(r.members, c) + r.Unlock() + logSink <- LogEvent{r.name, c.nickname, "left", true} + if *verbose { + log.Println(c, "left", r.name) + } case EventTopic: - if _, subscribed := room.members[client]; !subscribed { - client.ReplyParts("442", room.name, "You are not on that channel") + if _, subscribed := r.members[c]; !subscribed { + c.ReplyParts("442", r.name, "You are not on that channel") continue } - if event.text == "" { - go room.SendTopic(client) + if e.text == "" { + r.SendTopic(c) continue } - room.topic = strings.TrimLeft(event.text, ":") - msg := fmt.Sprintf(":%s TOPIC %s :%s", client, room.name, room.topic) - go room.Broadcast(msg) - room.logSink <- LogEvent{ - room.name, - client.nickname, - "set topic to " + room.topic, - true, - } - room.StateSave() + topic := strings.TrimLeft(e.text, ":") + r.topic = topic + msg := fmt.Sprintf(":%s TOPIC %s :%s", c, r.name, r.topic) + r.Broadcast(msg) + logSink <- LogEvent{r.name, c.nickname, "set topic to " + r.topic, true} + r.StateSave() case EventWho: - for m := range room.members { - client.ReplyNicknamed( + r.RLock() + for m := range r.members { + c.ReplyNicknamed( "352", - room.name, + r.name, m.username, - m.conn.RemoteAddr().String(), - *room.hostname, + m.Host(), + *hostname, m.nickname, "H", "0 "+m.realname, ) } - client.ReplyNicknamed("315", room.name, "End of /WHO list") + c.ReplyNicknamed("315", r.name, "End of /WHO list") + r.RUnlock() case EventMode: - if event.text == "" { - mode := "+" - if room.key != "" { + if e.text == "" { + mode := "+n" + if r.key != "" { mode = mode + "k" } - client.Msg(fmt.Sprintf("324 %s %s %s", client.nickname, room.name, mode)) + c.Msg(fmt.Sprintf("324 %s %s %s", c.nickname, r.name, mode)) continue } - if strings.HasPrefix(event.text, "b") { - client.ReplyNicknamed("368", room.name, "End of channel ban list") + if strings.HasPrefix(e.text, "b") { + c.ReplyNicknamed("368", r.name, "End of channel ban list") continue } - if strings.HasPrefix(event.text, "-k") || strings.HasPrefix(event.text, "+k") { - if _, subscribed := room.members[client]; !subscribed { - client.ReplyParts("442", room.name, "You are not on that channel") + if strings.HasPrefix(e.text, "-k") || strings.HasPrefix(e.text, "+k") { + if _, subscribed := r.members[c]; !subscribed { + c.ReplyParts("442", r.name, "You are not on that channel") continue } } else { - client.ReplyNicknamed("472", event.text, "Unknown MODE flag") + c.ReplyNicknamed("472", e.text, "Unknown MODE flag") continue } var msg string var msgLog string - if strings.HasPrefix(event.text, "+k") { - cols := strings.Split(event.text, " ") + if strings.HasPrefix(e.text, "+k") { + cols := strings.Split(e.text, " ") if len(cols) == 1 { - client.ReplyNotEnoughParameters("MODE") + c.ReplyNotEnoughParameters("MODE") continue } - room.key = cols[1] - msg = fmt.Sprintf(":%s MODE %s +k %s", client, room.name, room.key) - msgLog = "set channel key to " + room.key - } else if strings.HasPrefix(event.text, "-k") { - room.key = "" - msg = fmt.Sprintf(":%s MODE %s -k", client, room.name) + r.key = cols[1] + msg = fmt.Sprintf(":%s MODE %s +k %s", c, r.name, r.key) + msgLog = "set channel key" + } else { + r.key = "" + msg = fmt.Sprintf(":%s MODE %s -k", c, r.name) msgLog = "removed channel key" } - go room.Broadcast(msg) - room.logSink <- LogEvent{room.name, client.nickname, msgLog, true} - room.StateSave() + r.Broadcast(msg) + logSink <- LogEvent{r.name, c.nickname, msgLog, true} + r.StateSave() case EventMsg: - sep := strings.Index(event.text, " ") - room.Broadcast(fmt.Sprintf( - ":%s %s %s :%s", - client, - event.text[:sep], - room.name, - event.text[sep+1:]), - client, - ) - room.logSink <- LogEvent{ - room.name, - client.nickname, - event.text[sep+1:], - false, - } + sep := strings.Index(e.text, " ") + r.Broadcast(fmt.Sprintf( + ":%s %s %s :%s", c, e.text[:sep], r.name, e.text[sep+1:], + ), c) + logSink <- LogEvent{r.name, c.nickname, e.text[sep+1:], false} } } } + +func RoomRegister(name string) *Room { + r := &Room{ + name: name, + members: make(map[*Client]struct{}), + events: make(chan ClientEvent), + } + roomsLock.Lock() + roomsWG.Add(1) + rooms[name] = r + roomsLock.Unlock() + go r.Processor(r.events) + return r +}