mirror of
https://github.com/mumble-voip/grumble.git
synced 2025-12-20 06:10:00 -08:00
Add 'grumble ctl'.
This commit is contained in:
parent
01182d36b3
commit
41f6af2334
10 changed files with 343 additions and 34 deletions
7
Makefile
7
Makefile
|
|
@ -11,6 +11,7 @@ PACKAGES = \
|
|||
pkg/cryptstate \
|
||||
pkg/mumbleproto \
|
||||
pkg/blobstore \
|
||||
pkg/serverconf \
|
||||
pkg/sqlite
|
||||
|
||||
GCFLAGS = \
|
||||
|
|
@ -18,6 +19,7 @@ GCFLAGS = \
|
|||
-Ipkg/packetdatastream/_obj \
|
||||
-Ipkg/mumbleproto/_obj \
|
||||
-Ipkg/blobstore/_obj \
|
||||
-Ipkg/serverconf/_obj \
|
||||
-Ipkg/sqlite/_obj
|
||||
|
||||
LDFLAGS = \
|
||||
|
|
@ -25,6 +27,7 @@ LDFLAGS = \
|
|||
-Lpkg/packetdatastream/_obj \
|
||||
-Lpkg/mumbleproto/_obj \
|
||||
-Lpkg/blobstore/_obj \
|
||||
-Lpkg/serverconf/_obj \
|
||||
-Lpkg/sqlite/_obj
|
||||
|
||||
GOFILES = \
|
||||
|
|
@ -39,7 +42,9 @@ GOFILES = \
|
|||
murmurdb.go \
|
||||
freeze.go \
|
||||
gencert.go \
|
||||
register.go
|
||||
register.go \
|
||||
ctlrpc.go \
|
||||
ctl.go
|
||||
|
||||
.PHONY: grumble
|
||||
grumble: pkg
|
||||
|
|
|
|||
82
ctl.go
Normal file
82
ctl.go
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
// Copyright (c) 2011 The Grumble Authors
|
||||
// The use of this source code is goverened by a BSD-style
|
||||
// license that can be found in the LICENSE-file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"rpc"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var CtlUsage = `grumble ctl
|
||||
|
||||
help
|
||||
Show this help
|
||||
|
||||
start [id]
|
||||
Start a server
|
||||
|
||||
stop [id]
|
||||
Stop a server
|
||||
|
||||
setconf [id] [key] [value]
|
||||
Set a config value for server with id
|
||||
|
||||
getconf [id] [key] [value]
|
||||
Get a config value for server with id
|
||||
`
|
||||
|
||||
func GrumbleCtl(args []string) {
|
||||
log.SetFlags(0)
|
||||
|
||||
if len(args) <= 1 || args[0] == "help" {
|
||||
log.Printf(CtlUsage)
|
||||
return
|
||||
}
|
||||
|
||||
sid, _ := strconv.Atoi64(args[1])
|
||||
|
||||
client, err := rpc.Dial("unix", filepath.Join(os.Getenv("HOME"), ".grumble", "ctl"))
|
||||
if err != nil {
|
||||
log.Fatalf("Could not connect to control socket: %v", err)
|
||||
}
|
||||
|
||||
switch args[0] {
|
||||
case "start":
|
||||
err := client.Call("ctl.Start", sid, nil)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to start: %v", err)
|
||||
}
|
||||
log.Printf("[%v] Started")
|
||||
case "stop":
|
||||
err := client.Call("ctl.Stop", sid, nil)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to stop: %v", err)
|
||||
}
|
||||
log.Printf("[%v] Stopped", sid)
|
||||
case "setconf":
|
||||
if len(args) < 4 {
|
||||
return
|
||||
}
|
||||
result := &ConfigValue{}
|
||||
err := client.Call("ctl.SetConfig", &ConfigValue{sid, args[2], args[3]}, result)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to set config: %v", err)
|
||||
}
|
||||
log.Printf("[%v] %v=%v", result.Id, result.Key, result.Value)
|
||||
case "getconf":
|
||||
if len(args) < 3 {
|
||||
return
|
||||
}
|
||||
result := &ConfigValue{}
|
||||
err := client.Call("ctl.GetConfig", &ConfigValue{sid, args[2], ""}, result)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to get config: %v", err)
|
||||
}
|
||||
log.Printf("[%v] %v=%v", result.Id, result.Key, result.Value)
|
||||
}
|
||||
}
|
||||
46
ctlrpc.go
Normal file
46
ctlrpc.go
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright (c) 2011 The Grumble Authors
|
||||
// The use of this source code is goverened by a BSD-style
|
||||
// license that can be found in the LICENSE-file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
type ControlRPC struct {
|
||||
|
||||
}
|
||||
|
||||
// Start a server
|
||||
func (c *ControlRPC) Start(in *int, out *int) os.Error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop a server
|
||||
func (c *ControlRPC) Stop(in *int, out *int) os.Error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type ConfigValue struct {
|
||||
Id int64
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
// Set a config value
|
||||
func (c *ControlRPC) SetConfig(in *ConfigValue, out *ConfigValue) os.Error {
|
||||
servers[in.Id].SetConfig(in.Key, in.Value)
|
||||
out.Id = in.Id
|
||||
out.Key = in.Key
|
||||
out.Value = in.Value
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get a config value
|
||||
func (c *ControlRPC) GetConfig(in *ConfigValue, out *ConfigValue) os.Error {
|
||||
out.Id = in.Id
|
||||
out.Key = in.Key
|
||||
out.Value = servers[in.Id].GetConfig(in.Key)
|
||||
return nil
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ import (
|
|||
|
||||
type frozenServer struct {
|
||||
Id int "id"
|
||||
MaxUsers int "max_user"
|
||||
Config map[string]string "config"
|
||||
Channels []frozenChannel "channels"
|
||||
Users []frozenUser "users"
|
||||
}
|
||||
|
|
@ -98,7 +98,7 @@ func (server *Server) FreezeToFile(filename string) (err os.Error) {
|
|||
// Freeze a server
|
||||
func (server *Server) Freeze() (fs frozenServer, err os.Error) {
|
||||
fs.Id = int(server.Id)
|
||||
fs.MaxUsers = server.MaxUsers
|
||||
fs.Config = server.cfg
|
||||
|
||||
channels := []frozenChannel{}
|
||||
for _, c := range server.Channels {
|
||||
|
|
@ -224,6 +224,10 @@ func NewServerFromFrozen(filename string) (s *Server, err os.Error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if fs.Config != nil {
|
||||
s.cfg = fs.Config
|
||||
}
|
||||
|
||||
// Add all channels, but don't hook up parent/child relationships
|
||||
// until all of them are loaded.
|
||||
for _, fc := range fs.Channels {
|
||||
|
|
|
|||
40
grumble.go
40
grumble.go
|
|
@ -11,20 +11,24 @@ import (
|
|||
"os"
|
||||
"os/signal"
|
||||
"log"
|
||||
"net"
|
||||
"sqlite"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"rpc"
|
||||
"time"
|
||||
)
|
||||
|
||||
var help *bool = flag.Bool("help", false, "Show this help")
|
||||
var datadir *string = flag.String("datadir", "", "Directory to use for server storage")
|
||||
var blobdir *string = flag.String("blobdir", "", "Directory to use for blob storage")
|
||||
var ctlpath *string = flag.String("ctlpath", "", "File to use for ctl socket")
|
||||
var sqlitedb *string = flag.String("murmurdb", "", "Path to murmur.sqlite to import server structure from")
|
||||
var cleanup *bool = flag.Bool("clean", false, "Clean up existing data dir content before importing Murmur data")
|
||||
var gencert *bool = flag.Bool("gencert", false, "Generate a self-signed certificate for use with Grumble")
|
||||
|
||||
var globalBlobstore *blobstore.BlobStore
|
||||
var servers map[int64]*Server
|
||||
|
||||
func Usage() {
|
||||
fmt.Fprintf(os.Stderr, "usage: grumble [options]\n")
|
||||
|
|
@ -42,16 +46,16 @@ func MurmurImport(filename string) (err os.Error) {
|
|||
panic(err.String())
|
||||
}
|
||||
|
||||
var servers []int64
|
||||
var serverids []int64
|
||||
var sid int64
|
||||
for stmt.Next() {
|
||||
stmt.Scan(&sid)
|
||||
servers = append(servers, sid)
|
||||
serverids = append(serverids, sid)
|
||||
}
|
||||
|
||||
log.Printf("Found servers: %v (%v servers)", servers, len(servers))
|
||||
log.Printf("Found servers: %v (%v servers)", serverids, len(serverids))
|
||||
|
||||
for _, sid := range servers {
|
||||
for _, sid := range serverids {
|
||||
m, err := NewServerFromSQLite(sid, db)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -70,6 +74,12 @@ func MurmurImport(filename string) (err os.Error) {
|
|||
|
||||
func main() {
|
||||
var err os.Error
|
||||
|
||||
if len(os.Args) >= 2 && os.Args[1] == "ctl" {
|
||||
GrumbleCtl(os.Args[2:])
|
||||
return
|
||||
}
|
||||
|
||||
flag.Parse()
|
||||
if *help == true {
|
||||
Usage()
|
||||
|
|
@ -88,6 +98,10 @@ func main() {
|
|||
*blobdir = filepath.Join(os.Getenv("HOME"), ".grumble", "blob")
|
||||
}
|
||||
|
||||
if len(*ctlpath) == 0 {
|
||||
*ctlpath = filepath.Join(os.Getenv("HOME"), ".grumble", "ctl")
|
||||
}
|
||||
|
||||
log.Printf("Using blob directory: %s", *blobdir)
|
||||
globalBlobstore, err = blobstore.NewBlobStore(*blobdir, true)
|
||||
if err != nil {
|
||||
|
|
@ -161,7 +175,7 @@ func main() {
|
|||
log.Fatalf("Murmur import failed: %s", err.String())
|
||||
}
|
||||
|
||||
servers := make(map[int64]*Server)
|
||||
servers = make(map[int64]*Server)
|
||||
for _, name := range names {
|
||||
if matched, _ := regexp.MatchString("^[0-9]+$", name); matched {
|
||||
log.Printf("Loading server %v", name)
|
||||
|
|
@ -184,6 +198,22 @@ func main() {
|
|||
go s.ListenAndMurmur()
|
||||
}
|
||||
|
||||
os.Remove(*ctlpath)
|
||||
|
||||
addr, err := net.ResolveUnixAddr("unix", *ctlpath)
|
||||
if err != nil {
|
||||
log.Panicf("Unable to resolve ctl addr: %v", err)
|
||||
}
|
||||
|
||||
lis, err := net.ListenUnix("unix", addr)
|
||||
if err != nil {
|
||||
log.Panicf("Unable to listen on ctl socket: %v", err)
|
||||
}
|
||||
|
||||
ctl := &ControlRPC{}
|
||||
rpc.RegisterName("ctl", ctl)
|
||||
go rpc.Accept(lis)
|
||||
|
||||
if len(servers) > 0 {
|
||||
ticker := time.NewTicker(10e9) // 10 secs
|
||||
for {
|
||||
|
|
|
|||
6
pkg/serverconf/Makefile
Normal file
6
pkg/serverconf/Makefile
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
include $(GOROOT)/src/Make.inc
|
||||
|
||||
TARG = grumble/serverconf
|
||||
GOFILES = config.go
|
||||
|
||||
include $(GOROOT)/src/Make.pkg
|
||||
64
pkg/serverconf/config.go
Normal file
64
pkg/serverconf/config.go
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
// Copyright (c) 2011 The Grumble Authors
|
||||
// The use of this source code is goverened by a BSD-style
|
||||
// license that can be found in the LICENSE-file.
|
||||
|
||||
package serverconf
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var defaultCfg = map[string]string{
|
||||
"MaxBandwidth": "72000",
|
||||
"MaxUsers": "1000",
|
||||
"MaxUsersPerChannel": "0",
|
||||
"MaxTextMessageLength": "5000",
|
||||
"MaxImageMessageLength": "131072",
|
||||
"AllowHTML": "true",
|
||||
"DefaultChannel": "0",
|
||||
"RememberChannel": "true",
|
||||
"WelcomeText": "Welcome to this server running <b>Grumble</b>.",
|
||||
"SendVersion": "true",
|
||||
}
|
||||
|
||||
type Config map[string]string
|
||||
|
||||
func (cfg Config) Set(key string, value string) {
|
||||
cfg[key] = value
|
||||
}
|
||||
|
||||
func (cfg Config) StringValue(key string) (value string) {
|
||||
value, exists := cfg[key]
|
||||
if exists {
|
||||
return value
|
||||
}
|
||||
|
||||
value, exists = defaultCfg[key]
|
||||
if exists {
|
||||
return value
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (cfg Config) IntValue(key string) (intval int) {
|
||||
str := cfg.StringValue(key)
|
||||
intval, _ = strconv.Atoi(str)
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg Config) Uint32Value(key string) (uint32val uint32) {
|
||||
str := cfg.StringValue(key)
|
||||
uintval, _ := strconv.Atoui(str)
|
||||
return uint32(uintval)
|
||||
}
|
||||
|
||||
func (cfg Config) BoolValue(key string) (boolval bool) {
|
||||
str := cfg.StringValue(key)
|
||||
boolval, _ = strconv.Atob(str)
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg Config) Reset(key string) {
|
||||
cfg[key] = "", false
|
||||
}
|
||||
40
pkg/serverconf/config_test.go
Normal file
40
pkg/serverconf/config_test.go
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright (c) 2011 The Grumble Authors
|
||||
// The use of this source code is goverened by a BSD-style
|
||||
// license that can be found in the LICENSE-file.
|
||||
|
||||
package serverconf
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIntValue(t *testing.T) {
|
||||
cfg := make(Config)
|
||||
cfg.Set("Test", "13")
|
||||
if cfg.IntValue("Test") != 13 {
|
||||
t.Errorf("Expected 13")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFloatAsInt(t *testing.T) {
|
||||
cfg := make(Config)
|
||||
cfg.Set("Test", "13.4")
|
||||
if cfg.IntValue("Test") != 0 {
|
||||
t.Errorf("Expected 0")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultValue(t *testing.T) {
|
||||
cfg := make(Config)
|
||||
if cfg.IntValue("MaxBandwidth") != 72000 {
|
||||
t.Errorf("Expected 72000")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoolValue(t *testing.T) {
|
||||
cfg := make(Config)
|
||||
cfg.Set("DoStuffOnStartup", "true")
|
||||
if cfg.BoolValue("DoStuffOnStartup") != true {
|
||||
t.Errorf("Expected true")
|
||||
}
|
||||
}
|
||||
18
register.go
18
register.go
|
|
@ -74,16 +74,16 @@ func newTLSClientAuthConn(addr string, cfg *tls.Config) (c *http.ClientConn, err
|
|||
// This function is used to determine whether or not to periodically
|
||||
// contact the master server list and update this server's metadata.
|
||||
func (server *Server) IsPublic() bool {
|
||||
if len(server.RegisterName) == 0 {
|
||||
if len(server.cfg.StringValue("RegisterName")) == 0 {
|
||||
return false
|
||||
}
|
||||
if len(server.RegisterHost) == 0 {
|
||||
if len(server.cfg.StringValue("RegisterHost")) == 0 {
|
||||
return false
|
||||
}
|
||||
if len(server.RegisterPassword) == 0 {
|
||||
if len(server.cfg.StringValue("RegisterPassword")) == 0 {
|
||||
return false
|
||||
}
|
||||
if len(server.RegisterWebUrl) == 0 {
|
||||
if len(server.cfg.StringValue("RegisterWebUrl")) == 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
|
@ -121,11 +121,11 @@ func (server *Server) RegisterPublicServer() {
|
|||
return
|
||||
}
|
||||
err = t.Execute(buf, map[string]string{
|
||||
"name": server.RegisterName,
|
||||
"host": server.RegisterHost,
|
||||
"password": server.RegisterPassword,
|
||||
"url": server.RegisterWebUrl,
|
||||
"location": server.RegisterLocation,
|
||||
"name": server.cfg.StringValue("RegisterName"),
|
||||
"host": server.cfg.StringValue("RegisterHost"),
|
||||
"password": server.cfg.StringValue("RegisterPassword"),
|
||||
"url": server.cfg.StringValue("RegisterWebUrl"),
|
||||
"location": server.cfg.StringValue("RegisterLocation"),
|
||||
"port": strconv.Itoa(server.port),
|
||||
"digest": digest,
|
||||
"users": strconv.Itoa(len(server.clients)),
|
||||
|
|
|
|||
60
server.go
60
server.go
|
|
@ -21,6 +21,7 @@ import (
|
|||
"cryptstate"
|
||||
"fmt"
|
||||
"gob"
|
||||
"grumble/serverconf"
|
||||
"hash"
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
|
@ -57,19 +58,14 @@ type Server struct {
|
|||
udpsend chan *Message
|
||||
voicebroadcast chan *VoiceBroadcast
|
||||
freezeRequest chan *freezeRequest
|
||||
configRequest chan *configRequest
|
||||
|
||||
// Signals to the server that a client has been successfully
|
||||
// authenticated.
|
||||
clientAuthenticated chan *Client
|
||||
|
||||
// Config-related
|
||||
MaxUsers int
|
||||
MaxBandwidth uint32
|
||||
RegisterName string
|
||||
RegisterHost string
|
||||
RegisterPassword string
|
||||
RegisterWebUrl string
|
||||
RegisterLocation string
|
||||
// Server configuration
|
||||
cfg serverconf.Config
|
||||
|
||||
// Clients
|
||||
clients map[uint32]*Client
|
||||
|
|
@ -128,6 +124,13 @@ type freezeRequest struct {
|
|||
readCloser io.ReadCloser
|
||||
}
|
||||
|
||||
type configRequest struct {
|
||||
done chan bool
|
||||
set bool
|
||||
key string
|
||||
value string
|
||||
}
|
||||
|
||||
// Allocate a new Murmur instance
|
||||
func NewServer(id int64, addr string, port int) (s *Server, err os.Error) {
|
||||
s = new(Server)
|
||||
|
|
@ -137,6 +140,8 @@ func NewServer(id int64, addr string, port int) (s *Server, err os.Error) {
|
|||
s.port = port
|
||||
s.running = false
|
||||
|
||||
s.cfg = make(map[string]string)
|
||||
|
||||
s.sessions = make(map[uint32]bool)
|
||||
s.clients = make(map[uint32]*Client)
|
||||
s.Users = make(map[uint32]*User)
|
||||
|
|
@ -150,11 +155,9 @@ func NewServer(id int64, addr string, port int) (s *Server, err os.Error) {
|
|||
s.udpsend = make(chan *Message)
|
||||
s.voicebroadcast = make(chan *VoiceBroadcast)
|
||||
s.freezeRequest = make(chan *freezeRequest)
|
||||
s.configRequest = make(chan *configRequest)
|
||||
s.clientAuthenticated = make(chan *Client)
|
||||
|
||||
s.MaxBandwidth = 300000
|
||||
s.MaxUsers = 10
|
||||
|
||||
s.Channels = make(map[int]*Channel)
|
||||
s.root = s.NewChannel(0, "Root")
|
||||
s.aclcache = NewACLCache()
|
||||
|
|
@ -391,6 +394,10 @@ func (server *Server) handler() {
|
|||
}
|
||||
go server.handleFreezeRequest(req, &fs)
|
||||
|
||||
// Synchronzied config get/set
|
||||
case req := <-server.configRequest:
|
||||
server.handleConfigRequest(req)
|
||||
|
||||
// Server registration update
|
||||
// Tick every hour + a minute offset based on the server id.
|
||||
case <-time.Tick((3600 + ((server.Id * 60) % 600)) * 1e9):
|
||||
|
|
@ -426,6 +433,15 @@ func (server *Server) handleFreezeRequest(freq *freezeRequest, fs *frozenServer)
|
|||
}
|
||||
}
|
||||
|
||||
func (server *Server) handleConfigRequest(cfgReq *configRequest) {
|
||||
if cfgReq.set {
|
||||
server.cfg.Set(cfgReq.key, cfgReq.value)
|
||||
} else {
|
||||
cfgReq.value = server.cfg.StringValue(cfgReq.key)
|
||||
}
|
||||
cfgReq.done <- true
|
||||
}
|
||||
|
||||
// Handle an Authenticate protobuf message. This is handled in a separate
|
||||
// goroutine to allow for remote authenticators that are slow to respond.
|
||||
//
|
||||
|
|
@ -639,7 +655,7 @@ func (server *Server) finishAuthenticate(client *Client) {
|
|||
|
||||
sync := &mumbleproto.ServerSync{}
|
||||
sync.Session = proto.Uint32(client.Session)
|
||||
sync.MaxBandwidth = proto.Uint32(server.MaxBandwidth)
|
||||
sync.MaxBandwidth = proto.Uint32(server.cfg.Uint32Value("MaxBandwidth"))
|
||||
if client.IsSuperUser() {
|
||||
sync.Permissions = proto.Uint64(uint64(AllPermissions))
|
||||
} else {
|
||||
|
|
@ -951,8 +967,8 @@ func (server *Server) ListenUDP() {
|
|||
_ = binary.Write(buffer, binary.BigEndian, uint32((1<<16)|(2<<8)|2))
|
||||
_ = binary.Write(buffer, binary.BigEndian, rand)
|
||||
_ = binary.Write(buffer, binary.BigEndian, uint32(len(server.clients)))
|
||||
_ = binary.Write(buffer, binary.BigEndian, uint32(server.MaxUsers))
|
||||
_ = binary.Write(buffer, binary.BigEndian, uint32(server.MaxBandwidth))
|
||||
_ = binary.Write(buffer, binary.BigEndian, server.cfg.Uint32Value("MaxUsers"))
|
||||
_ = binary.Write(buffer, binary.BigEndian, server.cfg.Uint32Value("MaxBandwidth"))
|
||||
|
||||
server.udpsend <- &Message{
|
||||
buf: buffer.Bytes(),
|
||||
|
|
@ -1059,6 +1075,22 @@ func (s *Server) FreezeServer() io.ReadCloser {
|
|||
return fr.readCloser
|
||||
}
|
||||
|
||||
// Set the value of a config key
|
||||
func (s *Server) SetConfig(key string, value string) {
|
||||
cfgReq := &configRequest{make(chan bool), true, key, value}
|
||||
s.configRequest <- cfgReq
|
||||
<-cfgReq.done
|
||||
return
|
||||
}
|
||||
|
||||
// Get the value of a config key
|
||||
func (s *Server) GetConfig(key string) (value string) {
|
||||
cfgReq := &configRequest{make(chan bool), false, key, ""}
|
||||
s.configRequest <- cfgReq
|
||||
<-cfgReq.done
|
||||
return cfgReq.value
|
||||
}
|
||||
|
||||
// Register a client on the server.
|
||||
func (s *Server) RegisterClient(client *Client) (uid uint32) {
|
||||
// Increment nextUserId only if registration succeeded.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue