nmdc-webfrontend/main.go

245 lines
5.3 KiB
Go

package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
"github.com/googollee/go-socket.io"
"libnmdc"
)
type ActiveConnection struct {
s *socketio.Socket
h *libnmdc.HubConnection
}
type App struct {
cfg *Config
}
func NewApp(ConfigFilePath string) (*App, error) {
cfgData, err := ioutil.ReadFile(ConfigFilePath)
if err != nil {
return nil, err
}
cfg := Config{}
err = json.Unmarshal(cfgData, &cfg)
if err != nil {
return nil, err
}
return &App{cfg: &cfg}, nil
}
type UserMessageStruct struct {
User string `json:"user"`
Message string `json:"message,omitempty"`
}
func (this *App) HubWorker(Nick, Pass string, so socketio.Socket, done chan struct{}) {
log.Printf("[%s] Connecting to hub\n", so.Id())
selfUser := libnmdc.NewUserInfo(Nick)
selfUser.ClientTag = this.cfg.Hub.Tag
hco := libnmdc.HubConnectionOptions{
Address: libnmdc.HubAddress(fmt.Sprintf("%s:%d", this.cfg.Hub.Address, this.cfg.Hub.Port)),
Self: *selfUser,
NickPassword: Pass,
NumEventsToBuffer: 0,
}
hub := hco.Connect()
defer func() {
hub.Disconnect()
so.Emit("disconnect") // necessary? https://github.com/googollee/go-socket.io/issues/117
log.Printf("[%s] Leaving worker\n", so.Id())
}()
// Register after-connected SIO handlers
so.On("pub", func(data map[string]string) {
hub.SayPublic(data["message"])
})
so.On("priv", func(data map[string]string) {
hub.SayPrivate(data["user"], data["message"])
})
so.On("raw", func(data map[string]string) {
hub.SayRaw(data["message"])
})
// Loop hub connection
for {
select {
case hev, ok := <-hub.OnEvent:
if !ok {
log.Printf("[%s] hub chan closed\n", so.Id())
return // abandon
}
switch hev.EventType {
case libnmdc.EVENT_SYSTEM_MESSAGE_FROM_CONN, libnmdc.EVENT_SYSTEM_MESSAGE_FROM_HUB:
so.Emit("sys", hev.Message)
case libnmdc.EVENT_PUBLIC:
so.Emit("pub", UserMessageStruct{User: hev.Nick, Message: hev.Message})
case libnmdc.EVENT_PRIVATE:
so.Emit("priv", UserMessageStruct{User: hev.Nick, Message: hev.Message})
case libnmdc.EVENT_USER_JOINED:
so.Emit("join", UserMessageStruct{User: hev.Nick})
case libnmdc.EVENT_USER_PART:
so.Emit("part", UserMessageStruct{User: hev.Nick})
case libnmdc.EVENT_CONNECTION_STATE_CHANGED:
so.Emit("sys", fmt.Sprintf("Connection: %s", hev.StateChange.Format()))
if hev.StateChange == libnmdc.CONNECTIONSTATE_CONNECTED {
so.Emit("hello")
} else {
so.Emit("close")
}
case libnmdc.EVENT_HUBNAME_CHANGED:
so.Emit("hubname", hev.Nick)
// FIXME no usercommand support in golang libnmdc??
default:
if this.cfg.App.Debug {
log.Printf("[%s] %v\n", so.Id(), hev)
}
}
case <-done:
log.Printf("[%s] done chan closed\n", so.Id())
return // abandon
}
}
}
func (this *App) SocketIOServer(so socketio.Socket) {
log.Printf("[%s] Client connected", so.Id())
so.Emit("cls")
so.Emit("raw", this.cfg.App.MotdHTML+"<br>")
so.Emit("sys", "Enter a name to connect as (or name:pass for a registered nick)")
doneChan := make(chan struct{}, 0)
so.On("hello", func(data map[string]string) {
log.Printf("[%s] Connection request", so.Id())
go this.HubWorker(data["nick"], data["pass"], so, doneChan)
})
so.On("disconnection", func() { // n.b. not 'disconnect' (??)
log.Printf("[%s] Client dropped", so.Id())
close(doneChan)
})
}
func (this *App) ConfigRequestHandler(w http.ResponseWriter, r *http.Request) {
confStruct := struct {
Extern string `json:"extern"`
Title string `json:"title"`
}{
Extern: this.cfg.Web.Extern,
Title: this.cfg.Web.Title,
}
confBytes, _ := json.Marshal(confStruct)
//
w.Header().Set("Content-Type", "text/javascript")
w.WriteHeader(200)
fmt.Fprintf(w, "var DCWEBUI_CONF = %s;\n", string(confBytes))
}
func (this *App) StaticRequestHandler(w http.ResponseWriter, r *http.Request) {
fileName := r.URL.Path[1:]
data, err := Asset(fileName)
if err != nil {
w.WriteHeader(404)
return
}
knownContentTypes := map[string]string{
".htm": "text/html",
".css": "text/css",
".js": "application/javascript",
".png": "image/png",
}
foundMime := false
for ext, mimeType := range knownContentTypes {
if strings.HasSuffix(fileName, ext) {
w.Header().Set("Content-Type", mimeType)
foundMime = true
break
}
}
if !foundMime {
w.Header().Set("Content-Type", "application/x-octet-stream")
}
dataInfo, _ := AssetInfo(fileName)
w.Header().Set("Content-Length", fmt.Sprintf("%d", dataInfo.Size()))
w.Write(data)
}
func (this *App) RunServer() {
// Socket.io handler
server, err := socketio.NewServer(nil)
if err != nil {
log.Fatal(err)
}
server.On("connection", this.SocketIOServer)
server.On("error", func(so socketio.Socket, err error) {
log.Println("error:", err)
})
http.Handle("/socket.io/", server)
// Configuration handler
http.HandleFunc("/conf", this.ConfigRequestHandler)
// Other files: asset handler
http.HandleFunc("/", this.StaticRequestHandler)
// Listen and serve
bindAddr := fmt.Sprintf("%s:%d", this.cfg.Web.BindTo, this.cfg.Web.Port)
log.Printf("Serving at %s...", bindAddr)
log.Fatal(http.ListenAndServe(bindAddr, nil))
}
func main() {
a, err := NewApp("nmdc-webfrontend.conf")
if err != nil {
log.Fatal(err.Error())
return
}
a.RunServer()
}