X-Git-Url: http://www.git.cypherpunks.ru/?p=goircd.git;a=blobdiff_plain;f=room.go;h=cc2b0d739938e3998a3cc08089c882c4fdf263a3;hp=f92d0e6b08811f89758111420da09ba80204416c;hb=b7fb219307483d2c31b5dad1f559f325f2fd1a5e;hpb=043730c35562365191764faf321424eb7bec0513 diff --git a/room.go b/room.go index f92d0e6..cc2b0d7 100644 --- a/room.go +++ b/room.go @@ -1,11 +1,10 @@ /* goircd -- minimalistic simple Internet Relay Chat (IRC) server -Copyright (C) 2014 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 @@ -15,6 +14,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ + package main import ( @@ -23,148 +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 ( - RE_ROOM = regexp.MustCompile("^#[^\x00\x07\x0a\x0d ,:/]{1,200}$") -) + 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 RE_ROOM.MatchString(name) -} + rooms map[string]*Room = make(map[string]*Room) + roomsLock sync.RWMutex + roomsWG sync.WaitGroup +) -type Room struct { - name string - topic string - key string - members map[*Client]bool - hostname string - log_sink chan LogEvent - state_sink chan StateEvent +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, name string, log_sink chan LogEvent, state_sink chan StateEvent) *Room { - room := Room{name: name} - room.members = make(map[*Client]bool) - room.topic = "" - room.key = "" - room.hostname = hostname - room.log_sink = log_sink - room.state_sink = state_sink - 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, client_to_ignore ...*Client) { - for member := range room.members { - if (len(client_to_ignore) > 0) && member == client_to_ignore[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.state_sink <- 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.event_type { - case EVENT_NEW: - room.members[client] = true - log.Println(client, "joined", room.name) - room.SendTopic(client) - go room.Broadcast(fmt.Sprintf(":%s JOIN %s", client, room.name)) - room.log_sink <- LogEvent{room.name, client.nickname, "joined", true} - nicknames := []string{} - for member := range room.members { - nicknames = append(nicknames, member.nickname) +func (r *Room) Processor(events <-chan ClientEvent) { + for e := range events { + c := e.client + switch e.eventType { + case EventTerm: + roomsWG.Done() + return + case EventNew: + 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") - case EVENT_DEL: - if _, subscribed := room.members[client]; !subscribed { - client.ReplyNicknamed("442", room.name, "You are not on that channel") + 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 := 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) - go room.Broadcast(msg) - room.log_sink <- LogEvent{room.name, client.nickname, "left", true} - case EVENT_TOPIC: - if _, subscribed := room.members[client]; !subscribed { - client.ReplyParts("442", room.name, "You are not on that channel") + 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 := 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.log_sink <- LogEvent{room.name, client.nickname, "set topic to " + room.topic, true} - room.StateSave() - case EVENT_WHO: - for m := range room.members { - client.ReplyNicknamed("352", room.name, m.username, m.conn.RemoteAddr().String(), room.hostname, m.nickname, "H", "0 "+m.realname) + 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: + r.RLock() + for m := range r.members { + c.ReplyNicknamed( + "352", + r.name, + m.username, + m.Host(), + *hostname, + m.nickname, + "H", + "0 "+m.realname, + ) } - client.ReplyNicknamed("315", room.name, "End of /WHO list") - case EVENT_MODE: - if event.text == "" { - mode := "+" - if room.key != "" { + c.ReplyNicknamed("315", r.name, "End of /WHO list") + r.RUnlock() + case EventMode: + 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, "-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, "b") { + c.ReplyNicknamed("368", r.name, "End of channel ban list") + continue + } + 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 msg_log string - if strings.HasPrefix(event.text, "+k") { - cols := strings.Split(event.text, " ") + var msgLog string + 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) - msg_log = "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) - msg_log = "removed channel key" + 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.log_sink <- LogEvent{room.name, client.nickname, msg_log, true} - room.StateSave() - case EVENT_MSG: - sep := strings.Index(event.text, " ") - go room.Broadcast(fmt.Sprintf(":%s %s %s :%s", client, event.text[:sep], room.name, event.text[sep+1:]), client) - room.log_sink <- LogEvent{room.name, client.nickname, event.text[sep+1:], false} + r.Broadcast(msg) + logSink <- LogEvent{r.name, c.nickname, msgLog, true} + r.StateSave() + case EventMsg: + 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 +}