X-Git-Url: http://www.git.cypherpunks.ru/?p=goircd.git;a=blobdiff_plain;f=room.go;fp=room.go;h=cc2b0d739938e3998a3cc08089c882c4fdf263a3;hp=76b4c4275a48dcb16c562ebb88cb1213ee2eb6b1;hb=b7fb219307483d2c31b5dad1f559f325f2fd1a5e;hpb=def58d0f4944397faa8cdd4a9cf3515125e0b548 diff --git a/room.go b/room.go index 76b4c42..cc2b0d7 100644 --- a/room.go +++ b/room.go @@ -26,225 +26,213 @@ import ( "sync" ) -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 { - 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() (name string) { - room.RLock() - name = *room.name - room.RUnlock() - return -} +var ( + RERoom = regexp.MustCompile("^#[^\x00\x07\x0a\x0d ,:/]{1,200}$") -func NewRoom(name string) *Room { - topic := "" - key := "" - return &Room{ - name: &name, - topic: &topic, - key: &key, - 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) { - room.RLock() - if *room.topic == "" { - client.ReplyNicknamed("331", room.String(), "No topic is set") - } else { - client.ReplyNicknamed("332", room.String(), *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 } - room.RUnlock() + 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) { - room.RLock() - 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) } - room.RUnlock() + r.RUnlock() } -func (room *Room) StateSave() { - room.RLock() - stateSink <- StateEvent{room.String(), *room.topic, *room.key} - room.RUnlock() +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.Lock() - room.members[client] = struct{}{} + r.Lock() + r.members[c] = struct{}{} + r.Unlock() if *verbose { - log.Println(client, "joined", room.name) + log.Println(c, "joined", r.name) } - room.Unlock() - room.SendTopic(client) - room.Broadcast(fmt.Sprintf(":%s JOIN %s", client, room.String())) - logSink <- LogEvent{room.String(), *client.nickname, "joined", true} - nicknames := make([]string, 0) - room.RLock() - for member := range room.members { - nicknames = append(nicknames, *member.nickname) - } - room.RUnlock() - sort.Strings(nicknames) - client.ReplyNicknamed("353", "=", room.String(), strings.Join(nicknames, " ")) - client.ReplyNicknamed("366", room.String(), "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: - room.RLock() - if _, subscribed := room.members[client]; !subscribed { - client.ReplyNicknamed("442", room.String(), "You are not on that channel") - room.RUnlock() + if _, subscribed := r.members[c]; !subscribed { + c.ReplyNicknamed("442", r.name, "You are not on that channel") continue } - room.RUnlock() - room.Lock() - delete(room.members, client) - room.Unlock() - room.RLock() - msg := fmt.Sprintf(":%s PART %s :%s", client, room.String(), *client.nickname) - room.Broadcast(msg) - logSink <- LogEvent{room.String(), *client.nickname, "left", true} - room.RUnlock() + 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: - room.RLock() - if _, subscribed := room.members[client]; !subscribed { - client.ReplyParts("442", room.String(), "You are not on that channel") - room.RUnlock() + if _, subscribed := r.members[c]; !subscribed { + c.ReplyParts("442", r.name, "You are not on that channel") continue } - if event.text == "" { - room.SendTopic(client) - room.RUnlock() + if e.text == "" { + r.SendTopic(c) continue } - room.RUnlock() - topic := strings.TrimLeft(event.text, ":") - room.Lock() - room.topic = &topic - room.Unlock() - room.RLock() - msg := fmt.Sprintf(":%s TOPIC %s :%s", client, room.String(), *room.topic) - room.Broadcast(msg) - logSink <- LogEvent{ - room.String(), - *client.nickname, - "set topic to " + *room.topic, - true, - } - room.RUnlock() - 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: - room.RLock() - for m := range room.members { - client.ReplyNicknamed( + r.RLock() + for m := range r.members { + c.ReplyNicknamed( "352", - room.String(), - *m.username, + r.name, + m.username, m.Host(), *hostname, - *m.nickname, + m.nickname, "H", - "0 "+*m.realname, + "0 "+m.realname, ) } - client.ReplyNicknamed("315", room.String(), "End of /WHO list") - room.RUnlock() + c.ReplyNicknamed("315", r.name, "End of /WHO list") + r.RUnlock() case EventMode: - room.RLock() - 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.String(), mode)) - room.RUnlock() + c.Msg(fmt.Sprintf("324 %s %s %s", c.nickname, r.name, mode)) continue } - if strings.HasPrefix(event.text, "b") { - client.ReplyNicknamed("368", room.String(), "End of channel ban list") - room.RUnlock() + 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.String(), "You are not on that channel") - room.RUnlock() + 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") - room.RUnlock() + c.ReplyNicknamed("472", e.text, "Unknown MODE flag") continue } - room.RUnlock() 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.Lock() - room.key = &cols[1] - msg = fmt.Sprintf(":%s MODE %s +k %s", client, *room.name, *room.key) - msgLog = "set channel key to " + *room.key - room.Unlock() + r.key = cols[1] + msg = fmt.Sprintf(":%s MODE %s +k %s", c, r.name, r.key) + msgLog = "set channel key" } else { - key := "" - room.Lock() - room.key = &key - msg = fmt.Sprintf(":%s MODE %s -k", client, *room.name) - room.Unlock() + r.key = "" + msg = fmt.Sprintf(":%s MODE %s -k", c, r.name) msgLog = "removed channel key" } - room.Broadcast(msg) - logSink <- LogEvent{room.String(), *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.String(), - event.text[sep+1:]), - client, - ) - logSink <- LogEvent{ - room.String(), - *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 +}