diff --git a/server/config/config.go b/server/config/config.go index 4c31787b..250c62bc 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -2,7 +2,6 @@ package config import ( "os" - "sync" "github.com/joho/godotenv" ) @@ -16,11 +15,6 @@ type Config struct { EntryMap string } -var ( - config *Config - once sync.Once -) - func getEnvWithFallBack(envName, fallback string) (env string) { env = os.Getenv(envName) if env == "" { @@ -29,20 +23,14 @@ func getEnvWithFallBack(envName, fallback string) (env string) { return } -func Init() { +func NewConfiFromEnv() Config { godotenv.Load(".env") - once.Do(func() { - config = &Config{ - ServerHost: getEnvWithFallBack("POKETE_SERVER_HOST", "localhost"), - ServerPort: getEnvWithFallBack("POKETE_SERVER_PORT", "9988"), - APIPort: getEnvWithFallBack("POKETE_API_PORT", "9989"), - ServerType: getEnvWithFallBack("POKETE_SERVER_TYPE", "tcp"), - ClientVersion: getEnvWithFallBack("POKETE_SERVER_CLIENT_VERSION", "0.9.1"), - EntryMap: getEnvWithFallBack("POKETE_SERVER_CLIENT_ENTRYMAP", "playmap_1"), - } - }) -} - -func Get() Config { - return *config + return Config{ + ServerHost: getEnvWithFallBack("POKETE_SERVER_HOST", "localhost"), + ServerPort: getEnvWithFallBack("POKETE_SERVER_PORT", "9988"), + APIPort: getEnvWithFallBack("POKETE_API_PORT", "9989"), + ServerType: getEnvWithFallBack("POKETE_SERVER_TYPE", "tcp"), + ClientVersion: getEnvWithFallBack("POKETE_SERVER_CLIENT_VERSION", "0.9.1"), + EntryMap: getEnvWithFallBack("POKETE_SERVER_CLIENT_ENTRYMAP", "playmap_1"), + } } diff --git a/server/main.go b/server/main.go index 691ec6f9..f83324f7 100644 --- a/server/main.go +++ b/server/main.go @@ -1,110 +1,16 @@ package main import ( - "encoding/json" - "github.com/lxgr-linux/pokete/server/config" - "github.com/lxgr-linux/pokete/server/responses" - "github.com/lxgr-linux/pokete/server/status" - "github.com/lxgr-linux/pokete/server/user_repository" + "github.com/lxgr-linux/pokete/server/config" + "github.com/lxgr-linux/pokete/server/server" "log" - "net" - "os" - - "github.com/lxgr-linux/pokete/server/map_repository" - "github.com/lxgr-linux/pokete/server/requests" ) func main() { - config.Init() - user_repository.Init() - err := map_repository.Read() - if err != nil { - panic(err.Error()) - } - log.Print("Server Running...") - server, err := net.Listen(config.Get().ServerType, config.Get().ServerHost+":"+config.Get().ServerPort) - if err != nil { - log.Print("Error listening:", err.Error()) - os.Exit(1) - } - defer server.Close() - go status.HandleRequests() - log.Print("Listening on " + config.Get().ServerHost + ":" + config.Get().ServerPort) - log.Print("Waiting for client...") - for { - connection, err := server.Accept() - if err != nil { - log.Print("Error accepting: ", err.Error()) - os.Exit(1) - } - log.Print("client connected") - go processClient(connection) - } -} - -func unmarshallRequest[T requests.RequestBody](res []byte, genericResponseObject *requests.Request[requests.RequestBody]) error { - responseObject := requests.Request[T]{} - err := json.Unmarshal(res, &responseObject) - if err != nil { - return err - } - - genericResponseObject.Body = responseObject.Body - - return nil -} - -func handleRequests(res []byte, connection *net.Conn) error { - genericResponseObject := requests.Request[requests.RequestBody]{} - err := json.Unmarshal(res, &genericResponseObject) - - switch genericResponseObject.Type { - case requests.RequestType_POSITION_UPDATE: - err = unmarshallRequest[requests.RequestPosition](res, &genericResponseObject) - if err != nil { - return err - } - case requests.RequestType_HANDSHAKE: - err = unmarshallRequest[requests.RequestHandshake](res, &genericResponseObject) - if err != nil { - return err - } - } - - log.Printf("%#v\n", genericResponseObject) - err = genericResponseObject.Body.Handle(connection) - if err != nil { - return err - } - return nil -} - -func removeUser(connection *net.Conn) error { - thisUser, err := user_repository.GetByConn(connection) - err = user_repository.RemoveByConn(connection) - for _, user := range user_repository.GetAllUsers() { - err = responses.WriteUserRemovedResponse(user.Conn, thisUser.Name) - } - err = (*connection).Close() - return err -} - -func processClient(connection net.Conn) { - for { - buffer := make([]byte, 1024) - mLen, err := connection.Read(buffer) - if err != nil { - log.Print("Error reading:", err) - break - } - err = handleRequests(buffer[:mLen], &connection) - if err != nil { - log.Print("Error handeling:", err) - break - } - } - err := removeUser(&connection) + pokete, err := server.NewServer(config.NewConfiFromEnv()) if err != nil { - log.Print("Error closing:", err) + log.Fatal(err) } + + pokete.Start() } diff --git a/server/map_repository/map_repository.go b/server/map_repository/map_repository.go index 38b903b3..170173d8 100644 --- a/server/map_repository/map_repository.go +++ b/server/map_repository/map_repository.go @@ -3,68 +3,64 @@ package map_repository import ( "encoding/json" "os" - "sync" ) -var ( - obmaps *Obmaps - maps *Maps - npcs *NPCs - trainers *Trainers - once sync.Once -) +type MapRepo struct { + obmaps *Obmaps + maps *Maps + npcs *NPCs + trainers *Trainers +} -func readFile[T any](fileName string) (temp T, err error) { - content, err := os.ReadFile(fileName) - if err != nil { - return - } +func (m MapRepo)GetObmaps() Obmaps { + return *m.obmaps +} - err = json.Unmarshal(content, &temp) +func (m MapRepo)GetMaps() Maps { + return *m.maps +} + +func (m MapRepo)GetNPCs() NPCs { + return *m.npcs +} - return +func (m MapRepo)GetTrainers() Trainers { + return *m.trainers } -func Read() error { +func NewMapRepo() (mapRepo MapRepo, err error) { tempObmaps, err := readFile[Obmaps]("res/map_data.json") if err != nil { - return err + return } tempMaps, err := readFile[Maps]("res/maps.json") if err != nil { - return err + return } tempNPCs, err := readFile[NPCs]("res/npcs.json") if err != nil { - return err + return } tempTrainers, err := readFile[Trainers]("res/trainers.json") if err != nil { - return err + return } - once.Do(func() { - obmaps = &tempObmaps - maps = &tempMaps - npcs = &tempNPCs - trainers = &tempTrainers - }) - - return nil + return MapRepo { + obmaps: &tempObmaps, + maps: &tempMaps, + npcs: &tempNPCs, + trainers: &tempTrainers, + }, nil } -func GetObmaps() Obmaps { - return *obmaps -} - -func GetMaps() Maps { - return *maps -} +func readFile[T any](fileName string) (temp T, err error) { + content, err := os.ReadFile(fileName) + if err != nil { + return + } -func GetNPCs() NPCs { - return *npcs -} + err = json.Unmarshal(content, &temp) -func GetTrainers() Trainers { - return *trainers + return } diff --git a/server/provider/provider.go b/server/provider/provider.go new file mode 100644 index 00000000..2ee871f2 --- /dev/null +++ b/server/provider/provider.go @@ -0,0 +1,13 @@ +package provider + +import ( + "github.com/lxgr-linux/pokete/server/config" + "github.com/lxgr-linux/pokete/server/map_repository" + "github.com/lxgr-linux/pokete/server/user_repository" +) + +type Provider struct { + Config config.Config + MapRepo map_repository.MapRepo + UserRepo user_repository.UserRepo +} \ No newline at end of file diff --git a/server/requests/handler/handler.go b/server/requests/handler/handler.go new file mode 100644 index 00000000..eaaef8d5 --- /dev/null +++ b/server/requests/handler/handler.go @@ -0,0 +1,37 @@ +package handler + +import ( + "encoding/json" + + "github.com/lxgr-linux/pokete/server/requests" +) + +func unmarshallRequest[T requests.RequestBody](res []byte, genericResponseObject *requests.Request[requests.RequestBody]) error { + responseObject := requests.Request[T]{} + err := json.Unmarshal(res, &responseObject) + if err != nil { + return err + } + + genericResponseObject.Body = responseObject.Body + + return nil +} + +func Handle(res []byte) (genericResponseObject requests.Request[requests.RequestBody], err error) { + err = json.Unmarshal(res, &genericResponseObject) + + switch genericResponseObject.Type { + case requests.RequestType_POSITION_UPDATE: + err = unmarshallRequest[requests.RequestPosition](res, &genericResponseObject) + if err != nil { + return + } + case requests.RequestType_HANDSHAKE: + err = unmarshallRequest[requests.RequestHandshake](res, &genericResponseObject) + if err != nil { + return + } + } + return +} diff --git a/server/requests/requests.go b/server/requests/requests.go index 2ded9ff9..ddfeab2f 100644 --- a/server/requests/requests.go +++ b/server/requests/requests.go @@ -1,11 +1,13 @@ package requests import ( - "fmt" - "github.com/lxgr-linux/pokete/server/config" + "fmt" + "net" + + "github.com/lxgr-linux/pokete/server/config" + "github.com/lxgr-linux/pokete/server/provider" "github.com/lxgr-linux/pokete/server/responses" "github.com/lxgr-linux/pokete/server/user_repository" - "net" ) type RequestType int32 @@ -16,7 +18,7 @@ const ( ) type RequestBody interface { - Handle(connection *net.Conn) error + Handle(connection *net.Conn, p provider.Provider) error } type Request[T RequestBody] struct { @@ -25,31 +27,35 @@ type Request[T RequestBody] struct { } type RequestPosition struct { - user_repository.Position + user_repository.Position } -func (r RequestPosition) Handle(connection *net.Conn) error { - users := user_repository.GetAllUsers() - thisUser, err := user_repository.GetByConn(connection); if err != nil { - return err - } - err = user_repository.SetNewPositionToUser(thisUser.Name, r.Position); if err != nil { - err = responses.WritePositionImplausibleResponse(connection, err.Error()); if err != nil { - return err - } - return fmt.Errorf("connection closed") - } - thisUser, err = user_repository.GetByConn(connection); if err != nil { - return err - } - for _, user := range users { - if user.Conn != connection { - err := responses.WritePositionChangeResponse(user.Conn, thisUser) - if err != nil { - return err - } - } - } +func (r RequestPosition) Handle(connection *net.Conn, p provider.Provider) error { + users := p.UserRepo.GetAllUsers() + thisUser, err := p.UserRepo.GetByConn(connection) + if err != nil { + return err + } + err = p.UserRepo.SetNewPositionToUser(thisUser.Name, r.Position) + if err != nil { + err = responses.WritePositionImplausibleResponse(connection, err.Error()) + if err != nil { + return err + } + return fmt.Errorf("connection closed") + } + thisUser, err = p.UserRepo.GetByConn(connection) + if err != nil { + return err + } + for _, user := range users { + if user.Conn != connection { + err := responses.WritePositionChangeResponse(user.Conn, thisUser) + if err != nil { + return err + } + } + } return nil } @@ -59,31 +65,31 @@ type RequestHandshake struct { Version string } -func (r RequestHandshake) Handle(connection *net.Conn) error { - position := user_repository.GetStartPosition() - users := user_repository.GetAllUsers() +func (r RequestHandshake) Handle(connection *net.Conn, p provider.Provider) error { + position := getStartPosition(p.Config) + users := p.UserRepo.GetAllUsers() newUser := user_repository.User{ Name: r.UserName, Conn: connection, Position: position, } - if r.Version != config.Get().ClientVersion { - err := responses.WriteVersionMismatchResponse(connection) + if r.Version != p.Config.ClientVersion { + err := responses.WriteVersionMismatchResponse(connection, p.Config) if err != nil { return err } - return fmt.Errorf("connection closed") + return fmt.Errorf("connection closed") } - err := user_repository.Add(newUser) + err := p.UserRepo.Add(newUser) if err != nil { err = responses.WriteUserAllreadyTakenResponse(connection) if err != nil { return err } - return fmt.Errorf("connection closed") + return fmt.Errorf("connection closed") } for _, user := range users { @@ -93,10 +99,18 @@ func (r RequestHandshake) Handle(connection *net.Conn) error { } } - err = responses.WriteMapResponse(connection, position, users) + err = responses.WriteMapResponse(connection, position, users, p.MapRepo) if err != nil { return err } return nil } + +func getStartPosition(cfg config.Config) user_repository.Position { + return user_repository.Position{ + Map: cfg.EntryMap, + X: 2, + Y: 9, + } +} diff --git a/server/responses/responses.go b/server/responses/responses.go index dbd0f3d6..37dee351 100644 --- a/server/responses/responses.go +++ b/server/responses/responses.go @@ -3,10 +3,11 @@ package responses import ( "encoding/json" "fmt" + "net" + "github.com/lxgr-linux/pokete/server/config" "github.com/lxgr-linux/pokete/server/map_repository" "github.com/lxgr-linux/pokete/server/user_repository" - "net" ) type ResponseType int32 @@ -77,12 +78,12 @@ func WritePositionImplausibleResponse(connection *net.Conn, message string) erro ) } -func WriteVersionMismatchResponse(connection *net.Conn) error { +func WriteVersionMismatchResponse(connection *net.Conn, cfg config.Config) error { return writeResponse( connection, Response{ Type: ResponseType_VERSION_MISMATCH, - Body: fmt.Sprintf("Required version is %s", config.Get().ClientVersion), + Body: fmt.Sprintf("Required version is %s", cfg.ClientVersion), }, ) } @@ -97,16 +98,16 @@ func WriteUserRemovedResponse(connection *net.Conn, userName string) error { ) } -func WriteMapResponse(connection *net.Conn, position user_repository.Position, users []user_repository.User) error { +func WriteMapResponse(connection *net.Conn, position user_repository.Position, users []user_repository.User, mapRepo map_repository.MapRepo) error { return writeResponse( connection, Response{ Type: ResponseType_MAP, Body: MapResponse{ - Obmaps: map_repository.GetObmaps(), - Maps: map_repository.GetMaps(), - NPCs: map_repository.GetNPCs(), - Trainers: map_repository.GetTrainers(), + Obmaps: mapRepo.GetObmaps(), + Maps: mapRepo.GetMaps(), + NPCs: mapRepo.GetNPCs(), + Trainers: mapRepo.GetTrainers(), Position: position, Users: users, }, diff --git a/server/server/server.go b/server/server/server.go new file mode 100644 index 00000000..fcd4eb2a --- /dev/null +++ b/server/server/server.go @@ -0,0 +1,92 @@ +package server + +import ( + "github.com/lxgr-linux/pokete/server/provider" + "log" + "net" + + "github.com/lxgr-linux/pokete/server/config" + "github.com/lxgr-linux/pokete/server/map_repository" + "github.com/lxgr-linux/pokete/server/requests/handler" + "github.com/lxgr-linux/pokete/server/responses" + "github.com/lxgr-linux/pokete/server/status" + "github.com/lxgr-linux/pokete/server/user_repository" +) + +func NewServer(cfg config.Config) (server Server, err error) { + mapRepo, err := map_repository.NewMapRepo() + if err != nil { + return + } + return Server{ + provider.Provider{ + Config: cfg, + MapRepo: mapRepo, + UserRepo: user_repository.NewUserRepo(), + }, + }, nil +} + +type Server struct { + provider.Provider +} + +func (s Server) Start() { + log.Print("Server Running...") + server, err := net.Listen(s.Config.ServerType, s.Config.ServerHost+":"+s.Config.ServerPort) + if err != nil { + log.Fatal("Error listening:", err.Error()) + } + defer server.Close() + go status.NewStatusHandler(s.Provider).HandleRequests() + log.Print("Listening on " + s.Config.ServerHost + ":" + s.Config.ServerPort) + log.Print("Waiting for client...") + for { + connection, err := server.Accept() + if err != nil { + log.Fatal("Error accepting: ", err.Error()) + } + log.Print("client connected") + go s.processClient(connection) + } +} + +func (s Server) handleRequests(res []byte, connection *net.Conn) error { + genericResponseObject, err := handler.Handle(res) + log.Printf("%#v\n", genericResponseObject) + err = genericResponseObject.Body.Handle(connection, s.Provider) + if err != nil { + return err + } + return nil +} + +func (s Server) removeUser(connection *net.Conn) error { + thisUser, err := s.UserRepo.GetByConn(connection) + err = s.UserRepo.RemoveByConn(connection) + for _, user := range s.UserRepo.GetAllUsers() { + err = responses.WriteUserRemovedResponse(user.Conn, thisUser.Name) + } + err = (*connection).Close() + return err +} + +func (s Server) processClient(connection net.Conn) { + for { + buffer := make([]byte, 1024) + mLen, err := connection.Read(buffer) + if err != nil { + log.Print("Error reading:", err) + break + } + err = s.handleRequests(buffer[:mLen], &connection) + if err != nil { + log.Print("Error handeling:", err) + break + } + } + err := s.removeUser(&connection) + if err != nil { + log.Print("Error closing:", err) + } +} diff --git a/server/status/status.go b/server/status/status.go index d64c14d9..8fb9d4e7 100644 --- a/server/status/status.go +++ b/server/status/status.go @@ -1,26 +1,34 @@ package status import ( - "encoding/json" - "github.com/lxgr-linux/pokete/server/config" - "github.com/lxgr-linux/pokete/server/user_repository" + "encoding/json" "log" - "net/http" + "net/http" + + "github.com/lxgr-linux/pokete/server/provider" ) type status struct { - UsersOnline []string + UsersOnline []string +} + +type statusHandler struct { + p provider.Provider } -func HandleRequests() { - http.HandleFunc("/status", handleStatus) - log.Print("Serving status API at " + config.Get().ServerHost + ":" + config.Get().APIPort + "/status") - log.Fatal(http.ListenAndServe(":" + config.Get().APIPort, nil)) +func NewStatusHandler(p provider.Provider) statusHandler { + return statusHandler{p} } -func handleStatus(w http.ResponseWriter, r *http.Request) { - log.Print("Endpoint Hit: status") - json.NewEncoder(w).Encode(status{ - UsersOnline: user_repository.GetAllUserNames(), - }) -} \ No newline at end of file +func (s statusHandler) HandleRequests() { + http.HandleFunc("/status", s.handleStatus) + log.Print("Serving status API at " + s.p.Config.ServerHost + ":" + s.p.Config.APIPort + "/status") + log.Fatal(http.ListenAndServe(":"+s.p.Config.APIPort, nil)) +} + +func (s statusHandler) handleStatus(w http.ResponseWriter, r *http.Request) { + log.Print("Endpoint Hit: status") + json.NewEncoder(w).Encode(status{ + UsersOnline: s.p.UserRepo.GetAllUserNames(), + }) +} diff --git a/server/user_repository/user_repository.go b/server/user_repository/user_repository.go index 5317ad9d..7345ab96 100644 --- a/server/user_repository/user_repository.go +++ b/server/user_repository/user_repository.go @@ -2,80 +2,71 @@ package user_repository import ( "fmt" - "github.com/lxgr-linux/pokete/server/config" "net" - "sync" ) -var users *map[string]User -var once sync.Once - -func Init() { - var tempUsers = make(map[string]User) - once.Do(func() { - users = &tempUsers - }) +type UserRepo struct { + users *map[string]User } -func Add(user User) error { - for name, _ := range *users { +func (u UserRepo) Add(user User) error { + for name, _ := range *u.users { if name == user.Name { return fmt.Errorf("user already present") } } - (*users)[user.Name] = user + (*u.users)[user.Name] = user return nil } -func Remove(name string) { - delete(*users, name) +func (u UserRepo) Remove(name string) { + delete(*u.users, name) } -func GetByConn(conn *net.Conn) (User, error) { - for _, user := range *users { +func (u UserRepo) GetByConn(conn *net.Conn) (User, error) { + for _, user := range *u.users { if user.Conn == conn { return user, nil } } - return User{}, fmt.Errorf("user with given connection was not found, somebody fucked up badly") + return User{}, fmt.Errorf("user with given connection was not found, somebody fucked up badly") } -func RemoveByConn(conn *net.Conn) error { - user, err := GetByConn(conn) +func (u UserRepo) RemoveByConn(conn *net.Conn) error { + user, err := u.GetByConn(conn) if err != nil { return err } - Remove(user.Name) + u.Remove(user.Name) return nil } -func GetAllUsers() (retUsers []User) { - for _, user := range *users { +func (u UserRepo) GetAllUsers() (retUsers []User) { + for _, user := range *u.users { retUsers = append(retUsers, user) } return } -func GetAllUserNames() (names []string) { - for _, user := range *users { - names = append(names, user.Name) - } - return +func (u UserRepo) GetAllUserNames() (names []string) { + for _, user := range *u.users { + names = append(names, user.Name) + } + return } -func SetNewPositionToUser(name string, newPosition Position) error { - user := (*users)[name] +func (u UserRepo) SetNewPositionToUser(name string, newPosition Position) error { + user := (*u.users)[name] err := user.Position.Change(newPosition) - (*users)[name] = user + (*u.users)[name] = user return err } -func GetStartPosition() Position { - return Position{ - Map: config.Get().EntryMap, - X: 2, - Y: 9, +func NewUserRepo() UserRepo { + var tempUsers = make(map[string]User) + return UserRepo{ + users: &tempUsers, } }