Add channel joining UI, closes #37
This commit is contained in:
parent
f25594e962
commit
24b26aa85f
20 changed files with 1131 additions and 177 deletions
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
)
|
||||
|
||||
var channelStore = storage.NewChannelStore()
|
||||
var channelIndexes = storage.NewChannelIndexManager()
|
||||
|
||||
type Dispatch struct {
|
||||
Store storage.Store
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue