X-Git-Url: http://www.git.cypherpunks.ru/?p=goircd.git;a=blobdiff_plain;f=room.go;h=cc2b0d739938e3998a3cc08089c882c4fdf263a3;hp=3da5a3de2cbafc592ba3faf9f9d141aa593ed232;hb=b7fb219307483d2c31b5dad1f559f325f2fd1a5e;hpb=e657ffd2ab2cd5fae8c1d19e39b2268fa758153e diff --git a/room.go b/room.go index 3da5a3d..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,191 +23,216 @@ import ( "regexp" "sort" "strings" + "sync" ) -var ( - RERoom = regexp.MustCompile("^#[^\x00\x07\x0a\x0d ,:/]{1,200}$") - - rooms map[string]*Room = make(map[string]*Room) - - roomSinks map[*Room]chan ClientEvent = make(map[*Room]chan ClientEvent) -) - -// 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 { - name *string - topic *string - key *string + name string + topic string + key string members map[*Client]struct{} + events chan ClientEvent + sync.RWMutex } -func (room Room) String() string { - return *room.name -} +var ( + RERoom = regexp.MustCompile("^#[^\x00\x07\x0a\x0d ,:/]{1,200}$") -func NewRoom(name string) *Room { - topic := "" - return &Room{ - name: &name, - topic: &topic, - members: make(map[*Client]struct{}), + rooms map[string]*Room = make(map[string]*Room) + roomsLock sync.RWMutex + roomsWG sync.WaitGroup +) + +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 (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) +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 + +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() { - var key string - if room.key != nil { - key = *room.key - } - stateSink <- StateEvent{*room.name, *room.topic, 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: - roomsGroup.Done() + roomsWG.Done() return case EventNew: - room.members[client] = struct{}{} + r.Lock() + r.members[c] = struct{}{} + r.Unlock() if *verbose { - log.Println(client, "joined", room.name) - } - room.SendTopic(client) - room.Broadcast(fmt.Sprintf(":%s JOIN %s", client, *room.name)) - logSink <- LogEvent{*room.name, *client.nickname, "joined", true} - nicknames := make([]string, 0) - for member := range room.members { - nicknames = append(nicknames, *member.nickname) + 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) - 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 == "" { - room.SendTopic(client) + if e.text == "" { + r.SendTopic(c) continue } - topic := strings.TrimLeft(event.text, ":") - room.topic = &topic - msg := fmt.Sprintf(":%s TOPIC %s :%s", client, *room.name, *room.topic) - room.Broadcast(msg) - 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, - *m.username, - m.conn.RemoteAddr().String(), + r.name, + m.username, + m.Host(), *hostname, - *m.nickname, + m.nickname, "H", - "0 "+*m.realname, + "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 != nil { + 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 + r.key = cols[1] + msg = fmt.Sprintf(":%s MODE %s +k %s", c, r.name, r.key) + msgLog = "set channel key" } else { - room.key = nil - msg = fmt.Sprintf(":%s MODE %s -k", client, *room.name) + r.key = "" + msg = fmt.Sprintf(":%s MODE %s -k", c, r.name) msgLog = "removed channel key" } - room.Broadcast(msg) - 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, - ) - 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 +}