Add channel joining UI, closes #37

This commit is contained in:
Ken-Håvard Lieng 2019-01-23 07:34:39 +01:00
parent f25594e962
commit 24b26aa85f
20 changed files with 1131 additions and 177 deletions

View file

@ -3,6 +3,7 @@ package server
import (
"fmt"
"log"
"strconv"
"strings"
"unicode"
@ -23,6 +24,7 @@ type ircHandler struct {
whois WhoisReply
userBuffers map[string][]string
motdBuffer MOTD
listBuffer storage.ChannelListIndex
handlers map[string]func(*irc.Message)
}
@ -183,6 +185,12 @@ func (i *ircHandler) info(msg *irc.Message) {
New: msg.Params[0],
})
_, needsUpdate := channelIndexes.Get(i.client.Host)
if needsUpdate {
i.listBuffer = storage.NewMapChannelListIndex()
i.client.List()
}
go i.state.user.SetNick(msg.Params[0], i.client.Host)
}
@ -281,6 +289,34 @@ func (i *ircHandler) motdEnd(msg *irc.Message) {
i.motdBuffer = MOTD{}
}
func (i *ircHandler) list(msg *irc.Message) {
if i.listBuffer == nil && i.state.Bool("update_chanlist_"+i.client.Host) {
i.listBuffer = storage.NewMapChannelListIndex()
}
if i.listBuffer != nil {
c, _ := strconv.Atoi(msg.Params[2])
i.listBuffer.Add(&storage.ChannelListItem{
Name: msg.Params[1],
UserCount: c,
Topic: msg.LastParam(),
})
}
}
func (i *ircHandler) listEnd(msg *irc.Message) {
if i.listBuffer != nil {
i.state.Set("update_chanlist_"+i.client.Host, false)
go func(idx storage.ChannelListIndex) {
idx.Finish()
channelIndexes.Set(i.client.Host, idx)
}(i.listBuffer)
i.listBuffer = nil
}
}
func (i *ircHandler) badNick(msg *irc.Message) {
i.state.sendJSON("nick_fail", NickFail{
Server: i.client.Host,
@ -321,6 +357,8 @@ func (i *ircHandler) initHandlers() {
irc.ReplyMotdStart: i.motdStart,
irc.ReplyMotd: i.motd,
irc.ReplyEndOfMotd: i.motdEnd,
irc.ReplyList: i.list,
irc.ReplyListEnd: i.listEnd,
irc.ErrErroneousNickname: i.badNick,
}
}

View file

@ -192,3 +192,14 @@ type Error struct {
Server string
Message string
}
type ChannelSearch struct {
Server string
Q string
Start int
}
type ChannelSearchResult struct {
Results []*storage.ChannelListItem
Start int
}

View file

@ -3001,7 +3001,298 @@ func (v *ClientCert) UnmarshalJSON(data []byte) error {
func (v *ClientCert) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson42239ddeDecodeGithubComKhliengDispatchServer26(l, v)
}
func easyjson42239ddeDecodeGithubComKhliengDispatchServer27(in *jlexer.Lexer, out *Away) {
func easyjson42239ddeDecodeGithubComKhliengDispatchServer27(in *jlexer.Lexer, out *ChannelSearchResult) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
in.Consumed()
}
in.Skip()
return
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeString()
in.WantColon()
if in.IsNull() {
in.Skip()
in.WantComma()
continue
}
switch key {
case "results":
if in.IsNull() {
in.Skip()
out.Results = nil
} else {
in.Delim('[')
if out.Results == nil {
if !in.IsDelim(']') {
out.Results = make([]*storage.ChannelListItem, 0, 8)
} else {
out.Results = []*storage.ChannelListItem{}
}
} else {
out.Results = (out.Results)[:0]
}
for !in.IsDelim(']') {
var v22 *storage.ChannelListItem
if in.IsNull() {
in.Skip()
v22 = nil
} else {
if v22 == nil {
v22 = new(storage.ChannelListItem)
}
easyjson42239ddeDecodeGithubComKhliengDispatchStorage1(in, &*v22)
}
out.Results = append(out.Results, v22)
in.WantComma()
}
in.Delim(']')
}
case "start":
out.Start = int(in.Int())
default:
in.SkipRecursive()
}
in.WantComma()
}
in.Delim('}')
if isTopLevel {
in.Consumed()
}
}
func easyjson42239ddeEncodeGithubComKhliengDispatchServer27(out *jwriter.Writer, in ChannelSearchResult) {
out.RawByte('{')
first := true
_ = first
if len(in.Results) != 0 {
const prefix string = ",\"results\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
{
out.RawByte('[')
for v23, v24 := range in.Results {
if v23 > 0 {
out.RawByte(',')
}
if v24 == nil {
out.RawString("null")
} else {
easyjson42239ddeEncodeGithubComKhliengDispatchStorage1(out, *v24)
}
}
out.RawByte(']')
}
}
if in.Start != 0 {
const prefix string = ",\"start\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Int(int(in.Start))
}
out.RawByte('}')
}
// MarshalJSON supports json.Marshaler interface
func (v ChannelSearchResult) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjson42239ddeEncodeGithubComKhliengDispatchServer27(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v ChannelSearchResult) MarshalEasyJSON(w *jwriter.Writer) {
easyjson42239ddeEncodeGithubComKhliengDispatchServer27(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *ChannelSearchResult) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjson42239ddeDecodeGithubComKhliengDispatchServer27(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *ChannelSearchResult) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson42239ddeDecodeGithubComKhliengDispatchServer27(l, v)
}
func easyjson42239ddeDecodeGithubComKhliengDispatchStorage1(in *jlexer.Lexer, out *storage.ChannelListItem) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
in.Consumed()
}
in.Skip()
return
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeString()
in.WantColon()
if in.IsNull() {
in.Skip()
in.WantComma()
continue
}
switch key {
case "name":
out.Name = string(in.String())
case "userCount":
out.UserCount = int(in.Int())
case "topic":
out.Topic = string(in.String())
default:
in.SkipRecursive()
}
in.WantComma()
}
in.Delim('}')
if isTopLevel {
in.Consumed()
}
}
func easyjson42239ddeEncodeGithubComKhliengDispatchStorage1(out *jwriter.Writer, in storage.ChannelListItem) {
out.RawByte('{')
first := true
_ = first
if in.Name != "" {
const prefix string = ",\"name\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Name))
}
if in.UserCount != 0 {
const prefix string = ",\"userCount\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Int(int(in.UserCount))
}
if in.Topic != "" {
const prefix string = ",\"topic\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Topic))
}
out.RawByte('}')
}
func easyjson42239ddeDecodeGithubComKhliengDispatchServer28(in *jlexer.Lexer, out *ChannelSearch) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
in.Consumed()
}
in.Skip()
return
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeString()
in.WantColon()
if in.IsNull() {
in.Skip()
in.WantComma()
continue
}
switch key {
case "server":
out.Server = string(in.String())
case "q":
out.Q = string(in.String())
case "start":
out.Start = int(in.Int())
default:
in.SkipRecursive()
}
in.WantComma()
}
in.Delim('}')
if isTopLevel {
in.Consumed()
}
}
func easyjson42239ddeEncodeGithubComKhliengDispatchServer28(out *jwriter.Writer, in ChannelSearch) {
out.RawByte('{')
first := true
_ = first
if in.Server != "" {
const prefix string = ",\"server\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Server))
}
if in.Q != "" {
const prefix string = ",\"q\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Q))
}
if in.Start != 0 {
const prefix string = ",\"start\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.Int(int(in.Start))
}
out.RawByte('}')
}
// MarshalJSON supports json.Marshaler interface
func (v ChannelSearch) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjson42239ddeEncodeGithubComKhliengDispatchServer28(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v ChannelSearch) MarshalEasyJSON(w *jwriter.Writer) {
easyjson42239ddeEncodeGithubComKhliengDispatchServer28(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *ChannelSearch) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjson42239ddeDecodeGithubComKhliengDispatchServer28(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *ChannelSearch) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson42239ddeDecodeGithubComKhliengDispatchServer28(l, v)
}
func easyjson42239ddeDecodeGithubComKhliengDispatchServer29(in *jlexer.Lexer, out *Away) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
@ -3034,7 +3325,7 @@ func easyjson42239ddeDecodeGithubComKhliengDispatchServer27(in *jlexer.Lexer, ou
in.Consumed()
}
}
func easyjson42239ddeEncodeGithubComKhliengDispatchServer27(out *jwriter.Writer, in Away) {
func easyjson42239ddeEncodeGithubComKhliengDispatchServer29(out *jwriter.Writer, in Away) {
out.RawByte('{')
first := true
_ = first
@ -3064,23 +3355,23 @@ func easyjson42239ddeEncodeGithubComKhliengDispatchServer27(out *jwriter.Writer,
// MarshalJSON supports json.Marshaler interface
func (v Away) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjson42239ddeEncodeGithubComKhliengDispatchServer27(&w, v)
easyjson42239ddeEncodeGithubComKhliengDispatchServer29(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v Away) MarshalEasyJSON(w *jwriter.Writer) {
easyjson42239ddeEncodeGithubComKhliengDispatchServer27(w, v)
easyjson42239ddeEncodeGithubComKhliengDispatchServer29(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *Away) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjson42239ddeDecodeGithubComKhliengDispatchServer27(&r, v)
easyjson42239ddeDecodeGithubComKhliengDispatchServer29(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *Away) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson42239ddeDecodeGithubComKhliengDispatchServer27(l, v)
easyjson42239ddeDecodeGithubComKhliengDispatchServer29(l, v)
}

View file

@ -15,6 +15,7 @@ import (
)
var channelStore = storage.NewChannelStore()
var channelIndexes = storage.NewChannelIndexManager()
type Dispatch struct {
Store storage.Store

View file

@ -13,10 +13,15 @@ import (
)
const (
// AnonymousUserExpiration is the time to wait before removing an anonymous
// user that has no irc or websocket connections
AnonymousUserExpiration = 1 * time.Minute
)
// State is the live state of a single user
type State struct {
stateData
irc map[string]*irc.Client
connectionState map[string]irc.ConnectionState
ircLock sync.Mutex
@ -33,6 +38,7 @@ type State struct {
func NewState(user *storage.User, srv *Dispatch) *State {
return &State{
stateData: stateData{m: map[string]interface{}{}},
irc: make(map[string]*irc.Client),
connectionState: make(map[string]irc.ConnectionState),
ws: make(map[string]*wsConn),
@ -225,6 +231,36 @@ func (s *State) run() {
}
}
type stateData struct {
m map[string]interface{}
lock sync.Mutex
}
func (s stateData) Get(key string) interface{} {
s.lock.Lock()
v := s.m[key]
s.lock.Unlock()
return v
}
func (s stateData) Set(key string, value interface{}) {
s.lock.Lock()
s.m[key] = value
s.lock.Unlock()
}
func (s stateData) String(key string) string {
return s.Get(key).(string)
}
func (s stateData) Int(key string) int {
return s.Get(key).(int)
}
func (s stateData) Bool(key string) bool {
return s.Get(key).(bool)
}
type stateStore struct {
states map[uint64]*State
sessions map[string]*session.Session

View file

@ -97,6 +97,8 @@ func (h *wsHandler) connect(b []byte) {
var data Server
data.UnmarshalJSON(b)
data.Host = strings.ToLower(data.Host)
if _, ok := h.state.getIRC(data.Host); !ok {
log.Println(h.addr, "[IRC] Add server", data.Host)
@ -281,6 +283,29 @@ func (h *wsHandler) setSettings(b []byte) {
}
}
func (h *wsHandler) channelSearch(b []byte) {
var data ChannelSearch
data.UnmarshalJSON(b)
index, needsUpdate := channelIndexes.Get(data.Server)
if index != nil {
n := 10
if data.Start > 0 {
n = 50
}
h.state.sendJSON("channel_search", ChannelSearchResult{
Results: index.SearchN(data.Q, data.Start, n),
Start: data.Start,
})
}
if i, ok := h.state.getIRC(data.Server); ok && needsUpdate {
h.state.Set("update_chanlist_"+data.Server, true)
i.List()
}
}
func (h *wsHandler) initHandlers() {
h.handlers = map[string]func([]byte){
"connect": h.connect,
@ -301,6 +326,7 @@ func (h *wsHandler) initHandlers() {
"fetch_messages": h.fetchMessages,
"set_server_name": h.setServerName,
"settings_set": h.setSettings,
"channel_search": h.channelSearch,
}
}