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)) } }