You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
194 lines
4.7 KiB
194 lines
4.7 KiB
3 years ago
|
package main
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"git.reya.zone/reya/hexmap/server/room"
|
||
|
"git.reya.zone/reya/hexmap/server/state"
|
||
|
"git.reya.zone/reya/hexmap/server/ws"
|
||
|
"github.com/gorilla/websocket"
|
||
|
"go.uber.org/zap"
|
||
|
"google.golang.org/protobuf/proto"
|
||
|
"io/ioutil"
|
||
|
"net/http"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"strconv"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
const SaveDir = "/home/reya/hexmaps"
|
||
|
|
||
|
func save(m state.HexMap, l *zap.Logger) error {
|
||
|
filename := filepath.Join(SaveDir, "map."+strconv.FormatInt(time.Now().Unix(), 16))
|
||
|
l.Debug("Saving to file", zap.String("filename", filename))
|
||
|
marshaled, err := proto.Marshal(m.ToPB())
|
||
|
l.Debug("Marshaled proto")
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
l.Debug("Opening file")
|
||
|
file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0x644)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
l.Debug("Writing to file")
|
||
|
_, err = file.Write(marshaled)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
l.Debug("Closing file")
|
||
|
err = file.Close()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
l.Info("Saved to file", zap.String("filename", filename))
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func load(l *zap.Logger) (*state.HexMap, error) {
|
||
|
filename := filepath.Join(SaveDir, "map.LOAD")
|
||
|
l.Debug("Loading from file", zap.String("filename", filename))
|
||
|
file, err := os.Open(filename)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
l.Debug("Reading file")
|
||
|
marshaled, err := ioutil.ReadAll(file)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
pb := &state.HexMapPB{}
|
||
|
l.Debug("Extracting protobuf from file")
|
||
|
err = proto.Unmarshal(marshaled, pb)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
l.Debug("Closing file")
|
||
|
err = file.Close()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
m, err := pb.ToGo()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
l.Info("Loaded from file", zap.String("filename", filename))
|
||
|
return &m, nil
|
||
|
}
|
||
|
|
||
|
func BackupMap(client *room.Client, l *zap.Logger) {
|
||
|
var err error
|
||
|
myState := &state.Synced{}
|
||
|
l.Info("Starting backup system")
|
||
|
for {
|
||
|
msg := <-client.IncomingChannel()
|
||
|
switch typedMsg := msg.(type) {
|
||
|
case *room.JoinResponse:
|
||
|
myState = typedMsg.CurrentState()
|
||
|
err := save(myState.Map, l)
|
||
|
if err != nil {
|
||
|
l.Error("Failed saving during join response", zap.Error(err))
|
||
|
}
|
||
|
case *room.ActionBroadcast:
|
||
|
err = typedMsg.Action().Apply(myState)
|
||
|
if err == nil {
|
||
|
err = save(myState.Map, l)
|
||
|
if err != nil {
|
||
|
l.Error("Failed saving during action broadcast", zap.Error(err))
|
||
|
}
|
||
|
}
|
||
|
case *room.ShutdownRequest:
|
||
|
client.OutgoingChannel() <- client.AcknowledgeShutdown()
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func ServeWS(logger *zap.Logger) (err error) {
|
||
|
m := http.NewServeMux()
|
||
|
httpLogger := logger.Named("HTTP")
|
||
|
hexes, err := load(logger)
|
||
|
if err != nil {
|
||
|
hexes = state.NewHexMap(state.Layout{
|
||
|
Orientation: state.PointyTop,
|
||
|
IndentedLines: state.EvenLines,
|
||
|
}, 25, 10)
|
||
|
}
|
||
|
rm := room.New(room.NewOptions{
|
||
|
BaseLogger: logger.Named("Room"),
|
||
|
StartingState: &state.Synced{
|
||
|
Map: *hexes,
|
||
|
User: state.UserState{
|
||
|
ActiveColor: state.Color{
|
||
|
R: 0,
|
||
|
G: 0,
|
||
|
B: 0,
|
||
|
A: 255,
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
StartingClientOptions: room.NewClientOptions{
|
||
|
IncomingChannel: nil,
|
||
|
AcceptBroadcasts: true,
|
||
|
RequestStartingState: true,
|
||
|
},
|
||
|
})
|
||
|
go BackupMap(rm, logger.Named("BackupMap"))
|
||
|
m.Handle("/map", &ws.HTTPHandler{
|
||
|
Upgrader: websocket.Upgrader{
|
||
|
Subprotocols: []string{"v1.hexmap.deliciousreya.net"},
|
||
|
CheckOrigin: func(r *http.Request) bool {
|
||
|
return r.Header.Get("Origin") == "https://hexmap.deliciousreya.net"
|
||
|
},
|
||
|
},
|
||
|
Logger: logger.Named("WS"),
|
||
|
Room: rm,
|
||
|
})
|
||
|
srv := http.Server{
|
||
|
Addr: "127.0.0.1:5238",
|
||
|
Handler: m,
|
||
|
ErrorLog: zap.NewStdLog(httpLogger),
|
||
|
}
|
||
|
m.HandleFunc("/exit", func(writer http.ResponseWriter, request *http.Request) {
|
||
|
// Some light dissuasion of accidental probing.
|
||
|
// To keep good people out.
|
||
|
if request.FormValue("superSecretPassword") != "Gesture/Retrial5/Untrained/Countable/Extrude/Jeep/Cheese/Carbon" {
|
||
|
writer.WriteHeader(403)
|
||
|
_, err = writer.Write([]byte("... What are you trying to pull?"))
|
||
|
return
|
||
|
}
|
||
|
writer.WriteHeader(200)
|
||
|
_, err := writer.Write([]byte("OK, shutting down, bye!"))
|
||
|
if err != nil {
|
||
|
logger.Warn("Error while writing goodbye response", zap.Error(err))
|
||
|
}
|
||
|
time.AfterFunc(500*time.Millisecond, func() {
|
||
|
err := srv.Shutdown(context.Background())
|
||
|
if err != nil {
|
||
|
logger.Error("Error while shutting down the server", zap.Error(err))
|
||
|
}
|
||
|
})
|
||
|
})
|
||
|
err = srv.ListenAndServe()
|
||
|
if err != nil && err != http.ErrServerClosed {
|
||
|
return err
|
||
|
}
|
||
|
rm.OutgoingChannel() <- rm.Stop()
|
||
|
for {
|
||
|
msg := <-rm.IncomingChannel()
|
||
|
switch msg.(type) {
|
||
|
case *room.ShutdownRequest:
|
||
|
rm.OutgoingChannel() <- rm.AcknowledgeShutdown()
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func main() {
|
||
|
logger, err := zap.NewDevelopment()
|
||
|
err = ServeWS(logger)
|
||
|
if err != nil {
|
||
|
logger.Fatal("Error while serving HTTP", zap.Error(err))
|
||
|
}
|
||
|
}
|