From 8e4a45a2b935843f42b592adac9b7923513b5d86 Mon Sep 17 00:00:00 2001 From: mappu Date: Wed, 4 May 2016 19:03:36 +1200 Subject: [PATCH] split off all other files, too --- ConnectionState.go | 40 +++++ HubConnection.go | 277 +++++++++++++++++++++++++++++ HubConnectionOptions.go | 54 ++++++ HubEvent.go | 23 +++ libnmdc.go | 382 +--------------------------------------- 5 files changed, 395 insertions(+), 381 deletions(-) create mode 100644 ConnectionState.go create mode 100644 HubConnection.go create mode 100644 HubConnectionOptions.go create mode 100644 HubEvent.go diff --git a/ConnectionState.go b/ConnectionState.go new file mode 100644 index 0000000..d7c0cd1 --- /dev/null +++ b/ConnectionState.go @@ -0,0 +1,40 @@ +package libnmdc + +import ( + "net" +) + +type ConnectionState int + +const ( + CONNECTIONSTATE_DISCONNECTED = 1 + CONNECTIONSTATE_CONNECTING = 2 // Handshake in progress + CONNECTIONSTATE_CONNECTED = 3 +) + +func (cs ConnectionState) Format() string { + switch cs { + case CONNECTIONSTATE_DISCONNECTED: + return "Disconnected" + case CONNECTIONSTATE_CONNECTING: + return "Connecting" + case CONNECTIONSTATE_CONNECTED: + return "Connected" + default: + return "?" + } +} + +func CheckIsNetTimeout(err error) bool { + if err == nil { + return false + } + + switch err.(type) { + case net.Error: + return err.(net.Error).Timeout() + + default: + return false + } +} diff --git a/HubConnection.go b/HubConnection.go new file mode 100644 index 0000000..31a9022 --- /dev/null +++ b/HubConnection.go @@ -0,0 +1,277 @@ +package libnmdc + +import ( + "crypto/tls" + "net" + "strings" + "time" +) + +type HubConnection struct { + // Supplied parameters + Hco *HubConnectionOptions + + // Current remote status + HubName string + State ConnectionState + Users map[string]UserInfo + + // Streamed events + processEvent func(HubEvent) + OnEvent chan HubEvent + + // Private state + conn net.Conn // this is an interface + connValid bool + sentOurHello bool +} + +func (this *HubConnection) SayPublic(message string) { + this.SayRaw("<" + this.Hco.Self.Nick + "> " + NMDCEscape(message) + "|") +} + +func (this *HubConnection) SayPrivate(recipient string, message string) { + this.SayRaw("$To: " + recipient + " From: " + this.Hco.Self.Nick + " $<" + this.Hco.Self.Nick + "> " + NMDCEscape(message) + "|") +} + +func (this *HubConnection) SayInfo() { + this.SayRaw(this.Hco.Self.toMyINFO() + "|") +} + +func (this *HubConnection) userJoined_NameOnly(nick string) { + _, already_existed := this.Users[nick] + if !already_existed { + this.Users[nick] = *NewUserInfo(nick) + this.processEvent(HubEvent{EventType: EVENT_USER_JOINED, Nick: nick}) + } +} + +func (this *HubConnection) userJoined_Full(uinf *UserInfo) { + _, already_existed := this.Users[uinf.Nick] + if !already_existed { + this.Users[uinf.Nick] = *uinf + this.processEvent(HubEvent{EventType: EVENT_USER_JOINED, Nick: uinf.Nick}) + } +} + +// Note that protocol messages are transmitted on the caller thread, not from +// any internal libnmdc thread. +func (this *HubConnection) SayRaw(protocolCommand string) error { + if this.connValid { + _, err := this.conn.Write([]byte(protocolCommand)) + return err + } else { + return ErrNotConnected + } +} + +func (this *HubConnection) processProtocolMessage(message string) { + + // Zero-length protocol message + // ```````````````````````````` + if len(message) == 0 { + return + } + + // Public chat + // ``````````` + if rx_publicChat.MatchString(message) { + pubchat_parts := rx_publicChat.FindStringSubmatch(message) + this.processEvent(HubEvent{EventType: EVENT_PUBLIC, Nick: pubchat_parts[1], Message: NMDCUnescape(pubchat_parts[2])}) + return + } + + // System messages + // ``````````````` + if message[0] != '$' { + this.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_HUB, Nick: this.HubName, Message: NMDCUnescape(message)}) + return + } + + // Protocol messages + // ````````````````` + + commandParts := strings.SplitN(message, " ", 2) + switch commandParts[0] { + + case "$Lock": + this.SayRaw("$Supports NoGetINFO UserCommand UserIP2|" + + "$Key " + NMDCUnlock([]byte(commandParts[1])) + "|" + + "$ValidateNick " + NMDCEscape(this.Hco.Self.Nick) + "|") + this.sentOurHello = false + + case "$Hello": + if commandParts[1] == this.Hco.Self.Nick && !this.sentOurHello { + this.SayRaw("$Version 1,0091|") + this.SayRaw("$GetNickList|") + this.SayInfo() + this.sentOurHello = true + + } else { + this.userJoined_NameOnly(commandParts[1]) + + } + + case "$HubName": + this.HubName = commandParts[1] + this.processEvent(HubEvent{EventType: EVENT_HUBNAME_CHANGED, Nick: commandParts[1]}) + + case "$ValidateDenide": // sic + if len(this.Hco.NickPassword) > 0 { + this.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "Incorrect password."}) + } else { + this.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "Nick already in use."}) + } + + case "$HubIsFull": + this.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "Hub is full."}) + + case "$BadPass": + this.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "Incorrect password."}) + + case "$GetPass": + this.SayRaw("$MyPass " + NMDCEscape(this.Hco.NickPassword) + "|") + + case "$Quit": + delete(this.Users, commandParts[1]) + this.processEvent(HubEvent{EventType: EVENT_USER_PART, Nick: commandParts[1]}) + + case "$MyINFO": + u := UserInfo{} + err := u.fromMyINFO(commandParts[1]) + if err == nil { + this.userJoined_Full(&u) + } else { + this.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: err.Error()}) + } + + case "$NickList": + nicklist := strings.Split(commandParts[1], "$$") + for _, nick := range nicklist { + if len(nick) > 0 { + this.userJoined_NameOnly(nick) + } + } + + case "$To:": + valid := false + if rx_incomingTo.MatchString(commandParts[1]) { + txparts := rx_incomingTo.FindStringSubmatch(commandParts[1]) + if txparts[1] == this.Hco.Self.Nick && txparts[2] == txparts[3] { + this.processEvent(HubEvent{EventType: EVENT_PRIVATE, Nick: txparts[2], Message: txparts[4]}) + valid = true + } + } + + if !valid { + this.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Malformed private message '" + commandParts[1] + "'"}) + } + + case "$UserIP": + // Final message in PtokaX connection handshake - trigger connection callback. + // This might not be the case for other hubsofts, though + if this.State != CONNECTIONSTATE_CONNECTED { + this.processEvent(HubEvent{EventType: EVENT_CONNECTION_STATE_CHANGED, StateChange: CONNECTIONSTATE_CONNECTED}) + this.State = CONNECTIONSTATE_CONNECTED + } + + case "$ForceMove": + this.Hco.Address = HubAddress(commandParts[1]) + this.conn.Close() // we'll reconnect onto the new address + + // IGNORABLE COMMANDS + case "$Supports": + case "$UserCommand": // TODO $UserCommand 1 1 Group chat\New group chat$<%[mynick]> !groupchat_new|| + case "$UserList": + case "$OpList": + case "$HubTopic": + case "$Search": + case "$ConnectToMe": + + default: + this.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Unhandled protocol command '" + commandParts[0] + "'"}) + + } +} + +func (this *HubConnection) worker() { + var fullBuffer string + var err error = nil + var nbytes int = 0 + + for { + + // If we're not connected, attempt reconnect + if this.conn == nil { + + fullBuffer = "" // clear + + if this.Hco.Address.IsSecure() { + this.conn, err = tls.Dial("tcp", this.Hco.Address.GetHostOnly(), &tls.Config{ + InsecureSkipVerify: this.Hco.SkipVerifyTLS, + }) + } else { + this.conn, err = net.Dial("tcp", this.Hco.Address.GetHostOnly()) + } + + if err != nil { + this.State = CONNECTIONSTATE_DISCONNECTED + this.connValid = false + + } else { + this.State = CONNECTIONSTATE_CONNECTING + this.connValid = true + this.processEvent(HubEvent{EventType: EVENT_CONNECTION_STATE_CHANGED, StateChange: CONNECTIONSTATE_CONNECTING}) + + } + } + + // Read from socket into our local buffer (blocking) + if this.connValid { + readBuff := make([]byte, 1024) + this.conn.SetReadDeadline(time.Now().Add(SEND_KEEPALIVE_EVERY)) + nbytes, err = this.conn.Read(readBuff) + + if CheckIsNetTimeout(err) { + // No data before read deadline + err = nil + + // Send KA packet + _, err = this.conn.Write([]byte("|")) + } + + if nbytes > 0 { + fullBuffer += string(readBuff[0:nbytes]) + } + } + + // Attempt to parse a message block + for len(fullBuffer) > 0 { + for len(fullBuffer) > 0 && fullBuffer[0] == '|' { + fullBuffer = fullBuffer[1:] + } + protocolMessage := rx_protocolMessage.FindString(fullBuffer) + if len(protocolMessage) > 0 { + this.processProtocolMessage(protocolMessage[:len(protocolMessage)-1]) + fullBuffer = fullBuffer[len(protocolMessage):] + } else { + break + } + } + + // Maybe we disconnected + // Perform this check *last*, to ensure we've had a final shot at + // clearing out any queued messages + if err != nil { + this.State = CONNECTIONSTATE_DISCONNECTED + this.conn = nil + this.connValid = false + this.processEvent(HubEvent{EventType: EVENT_CONNECTION_STATE_CHANGED, StateChange: CONNECTIONSTATE_DISCONNECTED, Message: err.Error()}) + + time.Sleep(30 * time.Second) // Wait before reconnect + continue + } + + } + +} diff --git a/HubConnectionOptions.go b/HubConnectionOptions.go new file mode 100644 index 0000000..092fa9c --- /dev/null +++ b/HubConnectionOptions.go @@ -0,0 +1,54 @@ +package libnmdc + +type HubConnectionOptions struct { + Address HubAddress + SkipVerifyTLS bool // using a negative verb, because bools default to false + Self UserInfo + NickPassword string + + // Returning messages in async mode + NumEventsToBuffer uint + + // Returning messages in sync mode + OnEventSync func(HubEvent) +} + +func (this *HubConnectionOptions) prepareConnection() *HubConnection { + if this.Self.ClientTag == "" { + this.Self.ClientTag = DEFAULT_CLIENT_TAG + } + + hc := HubConnection{ + Hco: this, + HubName: DEFAULT_HUB_NAME, + State: CONNECTIONSTATE_DISCONNECTED, + Users: make(map[string]UserInfo), + } + + return &hc +} + +// Connects to an NMDC server, and spawns a background goroutine to handle +// protocol messages. Client code should select on all the interface channels. +func (this *HubConnectionOptions) Connect() *HubConnection { + + if this.NumEventsToBuffer < 1 { + this.NumEventsToBuffer = 1 + } + + hc := this.prepareConnection() + hc.OnEvent = make(chan HubEvent, this.NumEventsToBuffer) + hc.processEvent = func(ev HubEvent) { + hc.OnEvent <- ev + } + + go hc.worker() + return hc +} + +// Connects to an NMDC server, and blocks forever to handle protocol messages. +// Client code should supply an event handling function. +func (this *HubConnectionOptions) ConnectSync() { + hc := this.prepareConnection() + hc.worker() +} diff --git a/HubEvent.go b/HubEvent.go new file mode 100644 index 0000000..5f66de3 --- /dev/null +++ b/HubEvent.go @@ -0,0 +1,23 @@ +package libnmdc + +const ( + EVENT_PUBLIC = 1 + EVENT_PRIVATE = 2 + EVENT_SYSTEM_MESSAGE_FROM_HUB = 3 + EVENT_SYSTEM_MESSAGE_FROM_CONN = 4 + EVENT_USER_JOINED = 5 + EVENT_USER_PART = 6 + EVENT_USER_UPDATED_INFO = 7 + EVENT_CONNECTION_STATE_CHANGED = 8 + EVENT_HUBNAME_CHANGED = 9 + EVENT_DEBUG_MESSAGE = 10 +) + +type HubEventType int + +type HubEvent struct { + EventType HubEventType + Nick string + Message string + StateChange ConnectionState +} diff --git a/libnmdc.go b/libnmdc.go index 74f2527..ca4496e 100644 --- a/libnmdc.go +++ b/libnmdc.go @@ -2,10 +2,8 @@ package libnmdc import ( - "crypto/tls" "errors" "fmt" - "net" "regexp" "strings" "time" @@ -17,28 +15,6 @@ const ( SEND_KEEPALIVE_EVERY time.Duration = 29 * time.Second ) -type ConnectionState int -type HubEventType int - -const ( - CONNECTIONSTATE_DISCONNECTED = 1 - CONNECTIONSTATE_CONNECTING = 2 // Handshake in progress - CONNECTIONSTATE_CONNECTED = 3 -) - -const ( - EVENT_PUBLIC = 1 - EVENT_PRIVATE = 2 - EVENT_SYSTEM_MESSAGE_FROM_HUB = 3 - EVENT_SYSTEM_MESSAGE_FROM_CONN = 4 - EVENT_USER_JOINED = 5 - EVENT_USER_PART = 6 - EVENT_USER_UPDATED_INFO = 7 - EVENT_CONNECTION_STATE_CHANGED = 8 - EVENT_HUBNAME_CHANGED = 9 - EVENT_DEBUG_MESSAGE = 10 -) - var rx_protocolMessage *regexp.Regexp var rx_publicChat *regexp.Regexp var rx_incomingTo *regexp.Regexp @@ -50,58 +26,6 @@ func init() { rx_incomingTo = regexp.MustCompile("(?ms)^([^ ]+) From: ([^ ]+) \\$<([^>]*)> (.*)") } -type HubConnectionOptions struct { - Address HubAddress - SkipVerifyTLS bool // using a negative verb, because bools default to false - Self UserInfo - NickPassword string - - // Returning messages in async mode - NumEventsToBuffer uint - - // Returning messages in sync mode - OnEventSync func(HubEvent) -} - -type HubConnection struct { - // Supplied parameters - Hco *HubConnectionOptions - - // Current remote status - HubName string - State ConnectionState - Users map[string]UserInfo - - // Streamed events - processEvent func(HubEvent) - OnEvent chan HubEvent - - // Private state - conn net.Conn // this is an interface - connValid bool - sentOurHello bool -} - -type HubEvent struct { - EventType HubEventType - Nick string - Message string - StateChange ConnectionState -} - -func (cs ConnectionState) Format() string { - switch cs { - case CONNECTIONSTATE_DISCONNECTED: - return "Disconnected" - case CONNECTIONSTATE_CONNECTING: - return "Connecting" - case CONNECTIONSTATE_CONNECTED: - return "Connected" - default: - return "?" - } -} - func NMDCUnescape(encoded string) string { v1 := strings.Replace(encoded, "$", "$", -1) v2 := strings.Replace(v1, "|", "|", -1) @@ -114,46 +38,7 @@ func NMDCEscape(plaintext string) string { return strings.Replace(v2, "$", "$", -1) } -func (this *HubConnection) SayPublic(message string) { - this.SayRaw("<" + this.Hco.Self.Nick + "> " + NMDCEscape(message) + "|") -} - -func (this *HubConnection) SayPrivate(recipient string, message string) { - this.SayRaw("$To: " + recipient + " From: " + this.Hco.Self.Nick + " $<" + this.Hco.Self.Nick + "> " + NMDCEscape(message) + "|") -} - -func (this *HubConnection) SayInfo() { - this.SayRaw(this.Hco.Self.toMyINFO() + "|") -} - -func (this *HubConnection) userJoined_NameOnly(nick string) { - _, already_existed := this.Users[nick] - if !already_existed { - this.Users[nick] = *NewUserInfo(nick) - this.processEvent(HubEvent{EventType: EVENT_USER_JOINED, Nick: nick}) - } -} - -func (this *HubConnection) userJoined_Full(uinf *UserInfo) { - _, already_existed := this.Users[uinf.Nick] - if !already_existed { - this.Users[uinf.Nick] = *uinf - this.processEvent(HubEvent{EventType: EVENT_USER_JOINED, Nick: uinf.Nick}) - } -} - -// Note that protocol messages are transmitted on the caller thread, not from -// any internal libnmdc thread. -func (this *HubConnection) SayRaw(protocolCommand string) error { - if this.connValid { - _, err := this.conn.Write([]byte(protocolCommand)) - return err - } else { - return ErrNotConnected - } -} - -func parseLock(lock []byte) string { +func NMDCUnlock(lock []byte) string { nibble_swap := func(b byte) byte { return ((b << 4) & 0xF0) | ((b >> 4) & 0x0F) @@ -174,268 +59,3 @@ func parseLock(lock []byte) string { return key } - -func (this *HubConnection) processProtocolMessage(message string) { - - // Zero-length protocol message - // ```````````````````````````` - if len(message) == 0 { - return - } - - // Public chat - // ``````````` - if rx_publicChat.MatchString(message) { - pubchat_parts := rx_publicChat.FindStringSubmatch(message) - this.processEvent(HubEvent{EventType: EVENT_PUBLIC, Nick: pubchat_parts[1], Message: NMDCUnescape(pubchat_parts[2])}) - return - } - - // System messages - // ``````````````` - if message[0] != '$' { - this.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_HUB, Nick: this.HubName, Message: NMDCUnescape(message)}) - return - } - - // Protocol messages - // ````````````````` - - commandParts := strings.SplitN(message, " ", 2) - switch commandParts[0] { - - case "$Lock": - this.SayRaw("$Supports NoGetINFO UserCommand UserIP2|" + - "$Key " + parseLock([]byte(commandParts[1])) + "|" + - "$ValidateNick " + NMDCEscape(this.Hco.Self.Nick) + "|") - this.sentOurHello = false - - case "$Hello": - if commandParts[1] == this.Hco.Self.Nick && !this.sentOurHello { - this.SayRaw("$Version 1,0091|") - this.SayRaw("$GetNickList|") - this.SayInfo() - this.sentOurHello = true - - } else { - this.userJoined_NameOnly(commandParts[1]) - - } - - case "$HubName": - this.HubName = commandParts[1] - this.processEvent(HubEvent{EventType: EVENT_HUBNAME_CHANGED, Nick: commandParts[1]}) - - case "$ValidateDenide": // sic - if len(this.Hco.NickPassword) > 0 { - this.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "Incorrect password."}) - } else { - this.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "Nick already in use."}) - } - - case "$HubIsFull": - this.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "Hub is full."}) - - case "$BadPass": - this.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "Incorrect password."}) - - case "$GetPass": - this.SayRaw("$MyPass " + NMDCEscape(this.Hco.NickPassword) + "|") - - case "$Quit": - delete(this.Users, commandParts[1]) - this.processEvent(HubEvent{EventType: EVENT_USER_PART, Nick: commandParts[1]}) - - case "$MyINFO": - u := UserInfo{} - err := u.fromMyINFO(commandParts[1]) - if err == nil { - this.userJoined_Full(&u) - } else { - this.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: err.Error()}) - } - - case "$NickList": - nicklist := strings.Split(commandParts[1], "$$") - for _, nick := range nicklist { - if len(nick) > 0 { - this.userJoined_NameOnly(nick) - } - } - - case "$To:": - valid := false - if rx_incomingTo.MatchString(commandParts[1]) { - txparts := rx_incomingTo.FindStringSubmatch(commandParts[1]) - if txparts[1] == this.Hco.Self.Nick && txparts[2] == txparts[3] { - this.processEvent(HubEvent{EventType: EVENT_PRIVATE, Nick: txparts[2], Message: txparts[4]}) - valid = true - } - } - - if !valid { - this.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Malformed private message '" + commandParts[1] + "'"}) - } - - case "$UserIP": - // Final message in PtokaX connection handshake - trigger connection callback. - // This might not be the case for other hubsofts, though - if this.State != CONNECTIONSTATE_CONNECTED { - this.processEvent(HubEvent{EventType: EVENT_CONNECTION_STATE_CHANGED, StateChange: CONNECTIONSTATE_CONNECTED}) - this.State = CONNECTIONSTATE_CONNECTED - } - - case "$ForceMove": - this.Hco.Address = HubAddress(commandParts[1]) - this.conn.Close() // we'll reconnect onto the new address - - // IGNORABLE COMMANDS - case "$Supports": - case "$UserCommand": // TODO $UserCommand 1 1 Group chat\New group chat$<%[mynick]> !groupchat_new|| - case "$UserList": - case "$OpList": - case "$HubTopic": - case "$Search": - case "$ConnectToMe": - - default: - this.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Unhandled protocol command '" + commandParts[0] + "'"}) - - } -} - -func CheckIsNetTimeout(err error) bool { - if err == nil { - return false - } - - switch err.(type) { - case net.Error: - return err.(net.Error).Timeout() - - default: - return false - } -} - -func (this *HubConnection) worker() { - var fullBuffer string - var err error = nil - var nbytes int = 0 - - for { - - // If we're not connected, attempt reconnect - if this.conn == nil { - - fullBuffer = "" // clear - - if this.Hco.Address.IsSecure() { - this.conn, err = tls.Dial("tcp", this.Hco.Address.GetHostOnly(), &tls.Config{ - InsecureSkipVerify: this.Hco.SkipVerifyTLS, - }) - } else { - this.conn, err = net.Dial("tcp", this.Hco.Address.GetHostOnly()) - } - - if err != nil { - this.State = CONNECTIONSTATE_DISCONNECTED - this.connValid = false - - } else { - this.State = CONNECTIONSTATE_CONNECTING - this.connValid = true - this.processEvent(HubEvent{EventType: EVENT_CONNECTION_STATE_CHANGED, StateChange: CONNECTIONSTATE_CONNECTING}) - - } - } - - // Read from socket into our local buffer (blocking) - if this.connValid { - readBuff := make([]byte, 1024) - this.conn.SetReadDeadline(time.Now().Add(SEND_KEEPALIVE_EVERY)) - nbytes, err = this.conn.Read(readBuff) - - if CheckIsNetTimeout(err) { - // No data before read deadline - err = nil - - // Send KA packet - _, err = this.conn.Write([]byte("|")) - } - - if nbytes > 0 { - fullBuffer += string(readBuff[0:nbytes]) - } - } - - // Attempt to parse a message block - for len(fullBuffer) > 0 { - for len(fullBuffer) > 0 && fullBuffer[0] == '|' { - fullBuffer = fullBuffer[1:] - } - protocolMessage := rx_protocolMessage.FindString(fullBuffer) - if len(protocolMessage) > 0 { - this.processProtocolMessage(protocolMessage[:len(protocolMessage)-1]) - fullBuffer = fullBuffer[len(protocolMessage):] - } else { - break - } - } - - // Maybe we disconnected - // Perform this check *last*, to ensure we've had a final shot at - // clearing out any queued messages - if err != nil { - this.State = CONNECTIONSTATE_DISCONNECTED - this.conn = nil - this.connValid = false - this.processEvent(HubEvent{EventType: EVENT_CONNECTION_STATE_CHANGED, StateChange: CONNECTIONSTATE_DISCONNECTED, Message: err.Error()}) - - time.Sleep(30 * time.Second) // Wait before reconnect - continue - } - - } - -} - -func (this *HubConnectionOptions) prepareConnection() *HubConnection { - if this.Self.ClientTag == "" { - this.Self.ClientTag = DEFAULT_CLIENT_TAG - } - - hc := HubConnection{ - Hco: this, - HubName: DEFAULT_HUB_NAME, - State: CONNECTIONSTATE_DISCONNECTED, - Users: make(map[string]UserInfo), - } - - return &hc -} - -// Connects to an NMDC server, and spawns a background goroutine to handle -// protocol messages. Client code should select on all the interface channels. -func (this *HubConnectionOptions) Connect() *HubConnection { - - if this.NumEventsToBuffer < 1 { - this.NumEventsToBuffer = 1 - } - - hc := this.prepareConnection() - hc.OnEvent = make(chan HubEvent, this.NumEventsToBuffer) - hc.processEvent = func(ev HubEvent) { - hc.OnEvent <- ev - } - - go hc.worker() - return hc -} - -// Connects to an NMDC server, and blocks forever to handle protocol messages. -// Client code should supply an event handling function. -func (this *HubConnectionOptions) ConnectSync() { - hc := this.prepareConnection() - hc.worker() -}