]> Cypherpunks.ru repositories - goircd.git/blobdiff - room.go
Many fixes and additions
[goircd.git] / room.go
diff --git a/room.go b/room.go
index 76b4c4275a48dcb16c562ebb88cb1213ee2eb6b1..cc2b0d739938e3998a3cc08089c882c4fdf263a3 100644 (file)
--- 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
+}