diff --git a/irc/client_lookup_set.go b/irc/client_lookup_set.go new file mode 100644 index 00000000..24a2d43c --- /dev/null +++ b/irc/client_lookup_set.go @@ -0,0 +1,164 @@ +package irc + +import ( + "database/sql" + "errors" + "log" + "strings" +) + +type ClientLookupSet struct { + byNick map[string]*Client + db *ClientDB +} + +func NewClientLookupSet() *ClientLookupSet { + return &ClientLookupSet{ + byNick: make(map[string]*Client), + db: NewClientDB(), + } +} + +var ( + ErrNickMissing = errors.New("nick missing") + ErrNicknameInUse = errors.New("nickname in use") + ErrNicknameMismatch = errors.New("nickname mismatch") +) + +func (clients *ClientLookupSet) Get(nick string) *Client { + return clients.byNick[strings.ToLower(nick)] +} + +func (clients *ClientLookupSet) Add(client *Client) error { + if !client.HasNick() { + return ErrNickMissing + } + if clients.Get(client.nick) != nil { + return ErrNicknameInUse + } + clients.byNick[strings.ToLower(client.nick)] = client + clients.db.Add(client) + return nil +} + +func (clients *ClientLookupSet) Remove(client *Client) error { + if !client.HasNick() { + return ErrNickMissing + } + if clients.Get(client.nick) != client { + return ErrNicknameMismatch + } + delete(clients.byNick, strings.ToLower(client.nick)) + clients.db.Remove(client) + return nil +} + +func ExpandUserHost(userhost string) (expanded string) { + expanded = userhost + // fill in missing wildcards for nicks + if !strings.Contains(expanded, "!") { + expanded += "!*" + } + if !strings.Contains(expanded, "@") { + expanded += "@*" + } + return +} + +func (clients *ClientLookupSet) FindAll(userhost string) (set ClientSet) { + userhost = ExpandUserHost(userhost) + set = make(ClientSet) + rows, err := clients.db.db.Query( + `SELECT nickname FROM client + WHERE userhost LIKE ? ESCAPE '\'`, + QuoteLike(userhost)) + if err != nil { + return + } + for rows.Next() { + var nickname string + err := rows.Scan(&nickname) + if err != nil { + return + } + client := clients.Get(nickname) + if client != nil { + set.Add(client) + } + } + return +} + +func (clients *ClientLookupSet) Find(userhost string) *Client { + userhost = ExpandUserHost(userhost) + row := clients.db.db.QueryRow( + `SELECT nickname FROM client + WHERE userhost LIKE ? ESCAPE \ + LIMIT 1`, + QuoteLike(userhost)) + var nickname string + err := row.Scan(&nickname) + if err != nil { + log.Println("ClientLookupSet.Find: ", err) + return nil + } + return clients.Get(nickname) +} + +// +// client db +// + +type ClientDB struct { + db *sql.DB +} + +func NewClientDB() *ClientDB { + db := &ClientDB{ + db: OpenDB(":memory:"), + } + _, err := db.db.Exec(` + CREATE TABLE client ( + nickname TEXT NOT NULL UNIQUE, + userhost TEXT NOT NULL)`) + if err != nil { + log.Fatal(err) + } + _, err = db.db.Exec(` + CREATE UNIQUE INDEX nickname_index ON client (nickname)`) + if err != nil { + log.Fatal(err) + } + return db +} + +func (db *ClientDB) Add(client *Client) { + _, err := db.db.Exec(`INSERT INTO client (nickname, userhost) VALUES (?, ?)`, + client.Nick(), client.UserHost()) + if err != nil { + log.Println(err) + } +} + +func (db *ClientDB) Remove(client *Client) { + _, err := db.db.Exec(`DELETE FROM client WHERE nickname = ?`, + client.Nick()) + if err != nil { + log.Println(err) + } +} + +func QuoteLike(userhost string) (like string) { + like = userhost + // escape escape char + like = strings.Replace(like, `\`, `\\`, -1) + // escape meta-many + like = strings.Replace(like, `%`, `\%`, -1) + // escape meta-one + like = strings.Replace(like, `_`, `\_`, -1) + // swap meta-many + like = strings.Replace(like, `*`, `%`, -1) + // swap meta-one + like = strings.Replace(like, `?`, `_`, -1) + return +} diff --git a/irc/server.go b/irc/server.go index d31b0eac..16ee7b11 100644 --- a/irc/server.go +++ b/irc/server.go @@ -3,7 +3,6 @@ package irc import ( "bufio" "database/sql" - "errors" "fmt" "log" "net" @@ -852,163 +851,3 @@ func (msg *WhoWasCommand) HandleServer(server *Server) { client.RplEndOfWhoWas(nickname) } } - -// -// keeping track of clients -// - -type ClientLookupSet struct { - byNick map[string]*Client - db *ClientDB -} - -func NewClientLookupSet() *ClientLookupSet { - return &ClientLookupSet{ - byNick: make(map[string]*Client), - db: NewClientDB(), - } -} - -var ( - ErrNickMissing = errors.New("nick missing") - ErrNicknameInUse = errors.New("nickname in use") - ErrNicknameMismatch = errors.New("nickname mismatch") -) - -func (clients *ClientLookupSet) Get(nick string) *Client { - return clients.byNick[strings.ToLower(nick)] -} - -func (clients *ClientLookupSet) Add(client *Client) error { - if !client.HasNick() { - return ErrNickMissing - } - if clients.Get(client.nick) != nil { - return ErrNicknameInUse - } - clients.byNick[strings.ToLower(client.nick)] = client - clients.db.Add(client) - return nil -} - -func (clients *ClientLookupSet) Remove(client *Client) error { - if !client.HasNick() { - return ErrNickMissing - } - if clients.Get(client.nick) != client { - return ErrNicknameMismatch - } - delete(clients.byNick, strings.ToLower(client.nick)) - clients.db.Remove(client) - return nil -} - -func ExpandUserHost(userhost string) (expanded string) { - expanded = userhost - // fill in missing wildcards for nicks - if !strings.Contains(expanded, "!") { - expanded += "!*" - } - if !strings.Contains(expanded, "@") { - expanded += "@*" - } - return -} - -func (clients *ClientLookupSet) FindAll(userhost string) (set ClientSet) { - userhost = ExpandUserHost(userhost) - set = make(ClientSet) - rows, err := clients.db.db.Query( - `SELECT nickname FROM client - WHERE userhost LIKE ? ESCAPE '\'`, - QuoteLike(userhost)) - if err != nil { - return - } - for rows.Next() { - var nickname string - err := rows.Scan(&nickname) - if err != nil { - return - } - client := clients.Get(nickname) - if client != nil { - set.Add(client) - } - } - return -} - -func (clients *ClientLookupSet) Find(userhost string) *Client { - userhost = ExpandUserHost(userhost) - row := clients.db.db.QueryRow( - `SELECT nickname FROM client - WHERE userhost LIKE ? ESCAPE \ - LIMIT 1`, - QuoteLike(userhost)) - var nickname string - err := row.Scan(&nickname) - if err != nil { - log.Println("ClientLookupSet.Find: ", err) - return nil - } - return clients.Get(nickname) -} - -// -// client db -// - -type ClientDB struct { - db *sql.DB -} - -func NewClientDB() *ClientDB { - db := &ClientDB{ - db: OpenDB(":memory:"), - } - _, err := db.db.Exec(` - CREATE TABLE client ( - nickname TEXT NOT NULL UNIQUE, - userhost TEXT NOT NULL)`) - if err != nil { - log.Fatal(err) - } - _, err = db.db.Exec(` - CREATE UNIQUE INDEX nickname_index ON client (nickname)`) - if err != nil { - log.Fatal(err) - } - return db -} - -func (db *ClientDB) Add(client *Client) { - _, err := db.db.Exec(`INSERT INTO client (nickname, userhost) VALUES (?, ?)`, - client.Nick(), client.UserHost()) - if err != nil { - log.Println(err) - } -} - -func (db *ClientDB) Remove(client *Client) { - _, err := db.db.Exec(`DELETE FROM client WHERE nickname = ?`, - client.Nick()) - if err != nil { - log.Println(err) - } -} - -func QuoteLike(userhost string) (like string) { - like = userhost - // escape escape char - like = strings.Replace(like, `\`, `\\`, -1) - // escape meta-many - like = strings.Replace(like, `%`, `\%`, -1) - // escape meta-one - like = strings.Replace(like, `_`, `\_`, -1) - // swap meta-many - like = strings.Replace(like, `*`, `%`, -1) - // swap meta-one - like = strings.Replace(like, `?`, `_`, -1) - return -}