wip: add server and client logic
This commit is contained in:
parent
c24ae92a81
commit
65caea328b
106
app/app.go
106
app/app.go
@ -1 +1,107 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/haleyrom/skia-go"
|
||||
"image/color"
|
||||
"log"
|
||||
"runtime"
|
||||
|
||||
"github.com/go-gl/gl/v3.3-core/gl"
|
||||
"github.com/go-gl/glfw/v3.3/glfw"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// GLFW event handling must run on the main OS thread
|
||||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Initialize GLFW
|
||||
if err := glfw.Init(); err != nil {
|
||||
log.Fatalf("failed to initialize GLFW: %v", err)
|
||||
}
|
||||
defer glfw.Terminate()
|
||||
|
||||
// Create GLFW window
|
||||
glfw.WindowHint(glfw.ContextVersionMajor, 3)
|
||||
glfw.WindowHint(glfw.ContextVersionMinor, 3)
|
||||
glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile)
|
||||
glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True)
|
||||
|
||||
window, err := glfw.CreateWindow(800, 600, "GLFW + Skia Demo", nil, nil)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create GLFW window: %v", err)
|
||||
}
|
||||
window.MakeContextCurrent()
|
||||
|
||||
// Initialize OpenGL
|
||||
if err := gl.Init(); err != nil {
|
||||
log.Fatalf("failed to initialize OpenGL: %v", err)
|
||||
}
|
||||
|
||||
// Set up Skia surface
|
||||
glInterface := skia.NewNativeGrGlinterface()
|
||||
if glInterface == nil {
|
||||
log.Fatalf("failed to create Skia OpenGL interface")
|
||||
}
|
||||
grContext := skia.NewGLGrContext(glInterface)
|
||||
if grContext == nil {
|
||||
log.Fatalf("failed to create Skia GrContext")
|
||||
}
|
||||
|
||||
// Get framebuffer info
|
||||
var framebufferInfo skia.GrGlFramebufferinfo
|
||||
var fboID int32
|
||||
gl.GetIntegerv(gl.FRAMEBUFFER_BINDING, &fboID)
|
||||
framebufferInfo.FFBOID = uint32(fboID)
|
||||
framebufferInfo.FFormat = gl.RGBA8
|
||||
|
||||
width, height := window.GetSize()
|
||||
renderTarget := skia.NewGlGrBackendrendertarget(int32(width), int32(height), 1, 8, &framebufferInfo)
|
||||
if renderTarget == nil {
|
||||
log.Fatalf("failed to create Skia render target")
|
||||
}
|
||||
|
||||
surface := skia.NewSurfaceBackendRenderTarget(grContext, renderTarget, skia.GR_SURFACE_ORIGIN_BOTTOM_LEFT, skia.SK_COLORTYPE_RGBA_8888, nil, nil)
|
||||
if surface == nil {
|
||||
log.Fatalf("failed to create Skia surface")
|
||||
}
|
||||
defer surface.Unref()
|
||||
|
||||
c := color.RGBA{R: 255, G: 100, B: 50, A: 255}
|
||||
f := skia.NewColor4f(c)
|
||||
colorWhite := f.ToColor()
|
||||
|
||||
for !window.ShouldClose() {
|
||||
gl.ClearColor(0.3, 0.3, 0.3, 1.0)
|
||||
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
|
||||
|
||||
canvas := surface.GetCanvas()
|
||||
|
||||
paint := skia.NewPaint()
|
||||
paint.SetColor(0xffff0000)
|
||||
|
||||
// Clear the canvas with white color
|
||||
canvas.Clear(colorWhite)
|
||||
|
||||
// Create a rectangle and draw it
|
||||
rect := skia.Rect{
|
||||
Left: 100,
|
||||
Top: 100,
|
||||
Right: 300,
|
||||
Bottom: 300,
|
||||
}
|
||||
canvas.DrawRect(&rect, paint)
|
||||
grContext.Flush()
|
||||
|
||||
// Swap OpenGL buffers
|
||||
window.SwapBuffers()
|
||||
|
||||
// Poll for GLFW events
|
||||
glfw.PollEvents()
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
surface.Unref()
|
||||
grContext.Unref()
|
||||
}
|
||||
|
60
app/logger.go
Normal file
60
app/logger.go
Normal file
@ -0,0 +1,60 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
var AppLogger = NewCustomLogger()
|
||||
|
||||
// CustomLogger wraps the standard logger
|
||||
type CustomLogger struct {
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
// NewCustomLogger initializes the custom logger
|
||||
func NewCustomLogger() *CustomLogger {
|
||||
// Create a logger that writes to stdout with no flags
|
||||
return &CustomLogger{
|
||||
logger: log.New(os.Stdout, "", 0), // we will handle formatting manually
|
||||
}
|
||||
}
|
||||
|
||||
// logFormat retrieves the function name, file, and line number
|
||||
func logFormat() string {
|
||||
// Use the runtime.Caller to retrieve caller info
|
||||
pc, file, line, ok := runtime.Caller(2)
|
||||
if !ok {
|
||||
file = "unknown"
|
||||
line = 0
|
||||
}
|
||||
// Get function name
|
||||
funcName := runtime.FuncForPC(pc).Name()
|
||||
|
||||
// Format timestamp
|
||||
timestamp := time.Now().Format("2006-01-02 15:04:05")
|
||||
|
||||
// Return formatted log prefix (timestamp, file, line, and function name)
|
||||
return fmt.Sprintf("%s - %s:%d - %s: ", timestamp, file, line, funcName)
|
||||
}
|
||||
|
||||
// Info logs informational messages
|
||||
func (c *CustomLogger) Info(msg string) {
|
||||
c.logger.Println(logFormat() + "INFO: " + msg)
|
||||
}
|
||||
|
||||
// Error logs error messages
|
||||
func (c *CustomLogger) Error(msg string) {
|
||||
c.logger.Println(logFormat() + "ERROR: " + msg)
|
||||
}
|
||||
|
||||
// Example usage of the custom logger
|
||||
func main() {
|
||||
logger := NewCustomLogger()
|
||||
|
||||
logger.Info("This is an info message")
|
||||
logger.Error("This is an error message")
|
||||
}
|
161
app/main.go
161
app/main.go
@ -1,7 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
netclient "hudly/client"
|
||||
"hudly/hypixel"
|
||||
"hudly/mcfetch"
|
||||
"log"
|
||||
@ -9,8 +12,8 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var key = "9634ea92-80f0-482f-aebd-b082c6ed6f19"
|
||||
var uuid = "5328930e-d411-49cb-90ad-4e5c7b27dd86"
|
||||
var key = "ccebff0f-939a-4afe-b5b3-30a7a665ee38"
|
||||
var UUID = "5328930e-d411-49cb-90ad-4e5c7b27dd86"
|
||||
|
||||
func demo() {
|
||||
// Ensure a username is provided as a command-line argument
|
||||
@ -73,3 +76,157 @@ func demo() {
|
||||
}
|
||||
fmt.Println(fmt.Sprintf("%+v", res))
|
||||
}
|
||||
|
||||
func main() {
|
||||
var cmd string
|
||||
fmt.Print(" | CREATE\n | JOIN\nEnter Choice:\n>")
|
||||
fmt.Scanln(&cmd)
|
||||
|
||||
if cmd != "CREATE" && cmd != "JOIN" {
|
||||
fmt.Println("Invalid command.")
|
||||
return
|
||||
}
|
||||
|
||||
client, err := netclient.NewClient("0.0.0.0", uuid.New().String())
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create client: %v", err)
|
||||
return
|
||||
}
|
||||
var code string
|
||||
if cmd == "CREATE" {
|
||||
var err error
|
||||
code, err = client.CreateRoom("")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return
|
||||
}
|
||||
fmt.Println("Created room:", code)
|
||||
|
||||
err = client.JoinRoom(code, "password")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return
|
||||
}
|
||||
println("Connected to room")
|
||||
} else if cmd == "JOIN" {
|
||||
var password string
|
||||
fmt.Print("Enter Room Code:\n>")
|
||||
fmt.Scanln(&code)
|
||||
|
||||
fmt.Print("Enter Room Password:\n>")
|
||||
fmt.Scanln(&password)
|
||||
|
||||
err := client.JoinRoom(code, password)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return
|
||||
}
|
||||
fmt.Println("Joined room:", code)
|
||||
}
|
||||
|
||||
client.ListenForData()
|
||||
// Handle received data in a separate goroutine
|
||||
go func() {
|
||||
for data := range client.DataChannel {
|
||||
fmt.Printf("Received data: %s\n", data)
|
||||
}
|
||||
}()
|
||||
|
||||
if cmd == "CREATE" {
|
||||
println("Sleeping...")
|
||||
time.Sleep(time.Second * 20)
|
||||
println("Continuing!")
|
||||
|
||||
thing := hypixel.NewAPIKey(key)
|
||||
api := hypixel.NewAPI(*thing)
|
||||
|
||||
memCache := &mcfetch.MemoryCache{}
|
||||
memCache.Init()
|
||||
|
||||
resultChan := make(chan map[string]interface{})
|
||||
errorChan := make(chan error)
|
||||
|
||||
players := []string{"illyum", "ergopunch"}
|
||||
stuff := []map[string]interface{}{} // List to hold player JSON objects
|
||||
|
||||
println("Getting UUIDs")
|
||||
for _, player := range players {
|
||||
asyncFetcher := mcfetch.NewAsyncPlayerFetcher(
|
||||
player,
|
||||
memCache,
|
||||
2,
|
||||
2*time.Second,
|
||||
5*time.Second,
|
||||
)
|
||||
asyncFetcher.FetchPlayerData(resultChan, errorChan)
|
||||
}
|
||||
|
||||
var userID string
|
||||
for i := 0; i < len(players); i++ {
|
||||
select {
|
||||
case data := <-resultChan:
|
||||
if uuid, ok := data["id"].(string); ok {
|
||||
userID = uuid
|
||||
} else {
|
||||
fmt.Println(fmt.Sprintf("%+v", data))
|
||||
log.Fatal("UUID not found or invalid for player")
|
||||
}
|
||||
|
||||
// Fetch player stats
|
||||
res, err := api.GetPlayerResponse(userID)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get player data: %v", err)
|
||||
}
|
||||
|
||||
// Calculate derived stats
|
||||
res.Player.Stats.Bedwars.WLR = calculateRatio(res.Player.Stats.Bedwars.Wins, res.Player.Stats.Bedwars.Losses)
|
||||
res.Player.Stats.Bedwars.KDR = calculateRatio(res.Player.Stats.Bedwars.Kills, res.Player.Stats.Bedwars.Deaths)
|
||||
res.Player.Stats.Bedwars.FKDR = calculateRatio(res.Player.Stats.Bedwars.FinalKills, res.Player.Stats.Bedwars.FinalDeaths)
|
||||
res.Player.Stats.Bedwars.BBLR = calculateRatio(res.Player.Stats.Bedwars.BedsBroken, res.Player.Stats.Bedwars.BedsLost)
|
||||
|
||||
// Convert player struct to JSON
|
||||
playerJSON, err := json.Marshal(res)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to marshal player data: %v", err)
|
||||
}
|
||||
|
||||
// Append the player JSON to the stuff list
|
||||
var playerMap map[string]interface{}
|
||||
json.Unmarshal(playerJSON, &playerMap)
|
||||
stuff = append(stuff, playerMap)
|
||||
|
||||
case err := <-errorChan:
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
println("UUID Done!")
|
||||
println("Sending data...")
|
||||
|
||||
// Send the list of JSON objects to the client
|
||||
message, err := json.Marshal(stuff)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to marshal stuff: %v", err)
|
||||
}
|
||||
err = client.SendData(string(message))
|
||||
if err != nil {
|
||||
log.Printf("Error sending data: %v", err)
|
||||
}
|
||||
fmt.Println("Sent stuff:", stuff)
|
||||
println("Sending Done!")
|
||||
}
|
||||
|
||||
select {
|
||||
case <-client.DataChannel: // This blocks the main goroutine until data is received
|
||||
fmt.Println("Data received, exiting...")
|
||||
}
|
||||
|
||||
fmt.Println("Closing app")
|
||||
}
|
||||
|
||||
// calculateRatio is a helper function to calculate ratios (e.g., WLR, KDR, etc.)
|
||||
func calculateRatio(numerator, denominator int) float64 {
|
||||
if denominator == 0 {
|
||||
return float64(numerator)
|
||||
}
|
||||
return float64(numerator) / float64(denominator)
|
||||
}
|
||||
|
270
client/client.go
270
client/client.go
@ -1,75 +1,239 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Connect to the chat server
|
||||
func connectToServer(address string) (net.Conn, error) {
|
||||
conn, err := net.Dial("tcp", address)
|
||||
const PORT = ":5518"
|
||||
|
||||
type PacketID int
|
||||
|
||||
const (
|
||||
CONNECT_REQUEST PacketID = iota
|
||||
CONNECT_RESPONSE
|
||||
JOIN_REQUEST
|
||||
JOIN_RESPONSE
|
||||
CREATE_REQUEST
|
||||
CREATE_RESPONSE
|
||||
READ_DATA
|
||||
SEND_DATA_REQUEST
|
||||
SEND_DATA_RESPONSE
|
||||
LEAVE_ROOM
|
||||
)
|
||||
|
||||
// Client represents a client connection to the server.
|
||||
type Client struct {
|
||||
Conn net.Conn
|
||||
ClientID string
|
||||
RoomCode string
|
||||
DataChannel chan string
|
||||
}
|
||||
|
||||
// NewClient creates a new client and connects to the server.
|
||||
func NewClient(serverAddr, clientID string) (*Client, error) {
|
||||
conn, err := net.Dial("tcp", serverAddr+PORT)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not connect to server: %v", err)
|
||||
return nil, fmt.Errorf("failed to connect to server: %w", err)
|
||||
}
|
||||
return conn, nil
|
||||
return &Client{
|
||||
Conn: conn,
|
||||
ClientID: clientID,
|
||||
DataChannel: make(chan string),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Read input from the terminal and send it to the server
|
||||
func readInputAndSend(conn net.Conn) {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
for {
|
||||
fmt.Print("> ")
|
||||
text, _ := reader.ReadString('\n')
|
||||
text = strings.TrimSpace(text)
|
||||
|
||||
// Send the command to the server
|
||||
_, err := conn.Write([]byte(text + "\n"))
|
||||
if err != nil {
|
||||
fmt.Println("Error sending message:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// If the user types 'quit', exit the program
|
||||
if text == "quit" {
|
||||
fmt.Println("Goodbye!")
|
||||
return
|
||||
}
|
||||
// CreateRoom creates a new room on the server.
|
||||
func (c *Client) CreateRoom(password string) (string, error) {
|
||||
request := CreateRoomRequestPacket{
|
||||
UserID: c.ClientID,
|
||||
Password: password,
|
||||
}
|
||||
|
||||
if err := c.sendPacket(CREATE_REQUEST, request); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var response CreateRoomResponsePacket
|
||||
if err := c.receivePacket(CREATE_RESPONSE, &response); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !response.Success {
|
||||
return "", errors.New(response.Reason)
|
||||
}
|
||||
|
||||
c.RoomCode = response.RoomCode
|
||||
return response.RoomCode, nil
|
||||
}
|
||||
|
||||
// Listen for incoming messages from the server
|
||||
func listenForMessages(conn net.Conn) {
|
||||
reader := bufio.NewReader(conn)
|
||||
for {
|
||||
message, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
fmt.Println("Disconnected from server.")
|
||||
return
|
||||
}
|
||||
fmt.Print(message)
|
||||
// JoinRoom joins an existing room on the server.
|
||||
func (c *Client) JoinRoom(roomCode, password string) error {
|
||||
request := JoinRequestPacket{
|
||||
UserID: c.ClientID,
|
||||
RoomCode: roomCode,
|
||||
Password: password,
|
||||
}
|
||||
|
||||
if err := c.sendPacket(JOIN_REQUEST, request); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var response JoinRequestResponsePacket
|
||||
if err := c.receivePacket(JOIN_RESPONSE, &response); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !response.Success {
|
||||
return errors.New(response.Reason)
|
||||
}
|
||||
|
||||
c.RoomCode = roomCode
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Println("Usage: go run client.go <server-address>")
|
||||
return
|
||||
func (c *Client) ListenForData() {
|
||||
go func() {
|
||||
for {
|
||||
var dataPacket SendDataRequestPacket
|
||||
err := c.receivePacket(READ_DATA, &dataPacket)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
fmt.Println("Connection closed by the server")
|
||||
close(c.DataChannel)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Error receiving data: %v\n", err)
|
||||
close(c.DataChannel)
|
||||
return
|
||||
}
|
||||
|
||||
// Send the received data to the channel
|
||||
c.DataChannel <- dataPacket.Data
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// SendData sends a message to all other clients in the room.
|
||||
func (c *Client) SendData(data string) error {
|
||||
request := SendDataRequestPacket{
|
||||
UserID: c.ClientID,
|
||||
Data: data,
|
||||
}
|
||||
|
||||
serverAddress := os.Args[1]
|
||||
conn, err := connectToServer(serverAddress)
|
||||
if err := c.sendPacket(SEND_DATA_REQUEST, request); err != nil {
|
||||
return fmt.Errorf("failed to send data packet: %w", err)
|
||||
}
|
||||
|
||||
var response SendDataResponsePacket
|
||||
if err := c.receivePacket(SEND_DATA_RESPONSE, &response); err != nil {
|
||||
return fmt.Errorf("failed to receive response for sent data: %w", err)
|
||||
}
|
||||
|
||||
if !response.Success {
|
||||
return errors.New("server failed to process the data request: " + response.Reason)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LeaveRoom disconnects the client from the current room.
|
||||
func (c *Client) LeaveRoom() error {
|
||||
return c.sendPacket(LEAVE_ROOM, nil)
|
||||
}
|
||||
|
||||
// Close closes the connection to the server.
|
||||
func (c *Client) Close() {
|
||||
c.Conn.Close()
|
||||
}
|
||||
|
||||
// sendPacket sends a packet with a given ID and data to the server.
|
||||
func (c *Client) sendPacket(packetID PacketID, data interface{}) error {
|
||||
packetData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
return fmt.Errorf("failed to encode packet: %w", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Start a goroutine to listen for incoming messages from the server
|
||||
go listenForMessages(conn)
|
||||
|
||||
// Read input from the terminal and send it to the server
|
||||
readInputAndSend(conn)
|
||||
return writePacket(c.Conn, byte(packetID), packetData)
|
||||
}
|
||||
|
||||
// receivePacket reads a response from the server for a given packet ID.
|
||||
func (c *Client) receivePacket(expected PacketID, v interface{}) error {
|
||||
packetID, data, err := readPacket(c.Conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if packetID != expected {
|
||||
return fmt.Errorf("unexpected packet ID: got %v, want %v", packetID, expected)
|
||||
}
|
||||
return json.Unmarshal(data, v)
|
||||
}
|
||||
|
||||
// readPacket reads a packet from the connection.
|
||||
func readPacket(conn net.Conn) (PacketID, []byte, error) {
|
||||
var length uint32
|
||||
if err := binary.Read(conn, binary.BigEndian, &length); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
var messageType byte
|
||||
if err := binary.Read(conn, binary.BigEndian, &messageType); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
data := make([]byte, length-5)
|
||||
if _, err := io.ReadFull(conn, data); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
return PacketID(messageType), data, nil
|
||||
}
|
||||
|
||||
// writePacket writes a packet to the connection.
|
||||
func writePacket(conn net.Conn, messageType byte, data []byte) error {
|
||||
length := uint32(5 + len(data))
|
||||
if err := binary.Write(conn, binary.BigEndian, length); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := binary.Write(conn, binary.BigEndian, messageType); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := conn.Write(data)
|
||||
return err
|
||||
}
|
||||
|
||||
type CreateRoomRequestPacket struct {
|
||||
UserID string
|
||||
Password string
|
||||
}
|
||||
|
||||
type CreateRoomResponsePacket struct {
|
||||
Success bool
|
||||
Reason string
|
||||
RoomCode string
|
||||
}
|
||||
|
||||
type JoinRequestPacket struct {
|
||||
UserID string
|
||||
RoomCode string
|
||||
Password string
|
||||
}
|
||||
|
||||
type JoinRequestResponsePacket struct {
|
||||
Success bool
|
||||
Reason string
|
||||
CurrentData string
|
||||
}
|
||||
|
||||
type SendDataRequestPacket struct {
|
||||
UserID string
|
||||
Data string
|
||||
}
|
||||
|
||||
type SendDataResponsePacket struct {
|
||||
Success bool
|
||||
Reason string
|
||||
}
|
||||
|
11
go.mod
11
go.mod
@ -1 +1,12 @@
|
||||
module hudly
|
||||
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/haleyrom/skia-go v0.0.0-20240328095045-3f321a6a6e4d
|
||||
)
|
||||
|
||||
require github.com/mattn/go-pointer v0.0.0-20190911064623-a0a44394634f // indirect
|
||||
|
573
server/server.go
573
server/server.go
@ -1,201 +1,97 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const PORT = ":5518"
|
||||
|
||||
type PacketID int
|
||||
|
||||
const (
|
||||
CONNECT_REQUEST PacketID = iota
|
||||
CONNECT_RESPONSE
|
||||
JOIN_REQUEST
|
||||
JOIN_RESPONSE
|
||||
CREATE_REQUEST
|
||||
CREATE_RESPONSE
|
||||
READ_DATA
|
||||
SEND_DATA_REQUEST
|
||||
SEND_DATA_RESPONSE
|
||||
LEAVE_ROOM
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
conn net.Conn
|
||||
username string
|
||||
room *Room
|
||||
Conn net.Conn
|
||||
ClientID string
|
||||
RoomCode string
|
||||
}
|
||||
|
||||
type Room struct {
|
||||
code string
|
||||
password string
|
||||
clients map[*Client]bool
|
||||
lock sync.Mutex
|
||||
Code string
|
||||
Password string
|
||||
Clients []*Client
|
||||
}
|
||||
|
||||
var (
|
||||
rooms = make(map[string]*Room)
|
||||
mu sync.Mutex
|
||||
)
|
||||
var rooms = map[string]*Room{}
|
||||
var clients = map[string]*Client{}
|
||||
var mu sync.Mutex
|
||||
|
||||
// Helper function to generate a 4-hexadecimal room code
|
||||
func generateRoomCode() string {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
return fmt.Sprintf("%04x", rand.Intn(0xFFFF))
|
||||
func readPacket(conn net.Conn) (PacketID, []byte, error) {
|
||||
// First, read the length of the packet (4 bytes)
|
||||
var length uint32
|
||||
if err := binary.Read(conn, binary.BigEndian, &length); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
// Then read the packet ID (1 byte)
|
||||
var messageType byte
|
||||
if err := binary.Read(conn, binary.BigEndian, &messageType); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
// Read the remaining data (length - 5 bytes, since 4 bytes for length and 1 byte for messageType)
|
||||
data := make([]byte, length-5)
|
||||
if _, err := io.ReadFull(conn, data); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
return PacketID(int(messageType)), data, nil
|
||||
}
|
||||
|
||||
// Handle client connection
|
||||
func handleClient(client *Client) {
|
||||
defer client.conn.Close()
|
||||
func writePacket(conn net.Conn, messageType byte, data []byte) error {
|
||||
// Calculate the total length of the packet
|
||||
// 4 bytes for length, 1 byte for messageType
|
||||
length := uint32(5 + len(data))
|
||||
|
||||
reader := bufio.NewReader(client.conn)
|
||||
|
||||
for {
|
||||
line, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
if client.room != nil {
|
||||
leaveRoom(client)
|
||||
}
|
||||
fmt.Println("Client disconnected:", client.conn.RemoteAddr())
|
||||
return
|
||||
}
|
||||
processCommand(client, strings.TrimSpace(line))
|
||||
// Write the length and the message type
|
||||
if err := binary.Write(conn, binary.BigEndian, length); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := binary.Write(conn, binary.BigEndian, messageType); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write the data
|
||||
_, err := conn.Write(data)
|
||||
return err
|
||||
}
|
||||
|
||||
// Process client commands
|
||||
func processCommand(client *Client, input string) {
|
||||
parts := strings.SplitN(input, " ", 2)
|
||||
if len(parts) < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
cmd := strings.ToUpper(parts[0])
|
||||
args := ""
|
||||
if len(parts) > 1 {
|
||||
args = parts[1]
|
||||
}
|
||||
|
||||
switch cmd {
|
||||
case "CREATE":
|
||||
createRoom(client, args)
|
||||
case "JOIN":
|
||||
joinRoom(client, args)
|
||||
case "LEAVE":
|
||||
leaveRoom(client)
|
||||
case "MESSAGE":
|
||||
sendMessage(client, args)
|
||||
case "SYNC":
|
||||
handleSync(client, args)
|
||||
default:
|
||||
client.conn.Write([]byte("Unknown command\n"))
|
||||
}
|
||||
}
|
||||
|
||||
// Create a room with an optional password
|
||||
func createRoom(client *Client, args string) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
password := ""
|
||||
if args != "" {
|
||||
password = args // Treat all input after CREATE as a password
|
||||
}
|
||||
|
||||
roomCode := generateRoomCode() // Room code is generated automatically
|
||||
room := &Room{
|
||||
code: roomCode,
|
||||
password: password,
|
||||
clients: make(map[*Client]bool),
|
||||
}
|
||||
|
||||
rooms[roomCode] = room
|
||||
client.conn.Write([]byte(fmt.Sprintf("Room created with code: %s\n", roomCode)))
|
||||
}
|
||||
|
||||
// Join a room using its 4-hexadecimal code and optional password
|
||||
func joinRoom(client *Client, args string) {
|
||||
parts := strings.SplitN(args, " ", 2)
|
||||
roomCode := parts[0]
|
||||
password := ""
|
||||
if len(parts) == 2 {
|
||||
password = parts[1]
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
room, exists := rooms[roomCode]
|
||||
mu.Unlock()
|
||||
|
||||
if !exists {
|
||||
client.conn.Write([]byte("Room not found\n"))
|
||||
return
|
||||
}
|
||||
|
||||
if room.password != "" && room.password != password {
|
||||
client.conn.Write([]byte("Incorrect password\n"))
|
||||
return
|
||||
}
|
||||
|
||||
room.lock.Lock()
|
||||
room.clients[client] = true
|
||||
client.room = room
|
||||
room.lock.Unlock()
|
||||
|
||||
client.conn.Write([]byte(fmt.Sprintf("Joined room: %s\n", roomCode)))
|
||||
broadcastMessage(client.room, fmt.Sprintf("%s has joined the room\n", client.username))
|
||||
}
|
||||
|
||||
// Leave the current room
|
||||
func leaveRoom(client *Client) {
|
||||
if client.room == nil {
|
||||
client.conn.Write([]byte("You are not in any room\n"))
|
||||
return
|
||||
}
|
||||
|
||||
client.room.lock.Lock()
|
||||
delete(client.room.clients, client)
|
||||
client.room.lock.Unlock()
|
||||
|
||||
broadcastMessage(client.room, fmt.Sprintf("%s has left the room\n", client.username))
|
||||
client.conn.Write([]byte("You have left the room\n"))
|
||||
client.room = nil
|
||||
}
|
||||
|
||||
// Send a message to all clients in the current room
|
||||
func sendMessage(client *Client, message string) {
|
||||
if client.room == nil {
|
||||
client.conn.Write([]byte("You are not in any room\n"))
|
||||
return
|
||||
}
|
||||
|
||||
timestamp := time.Now().Format("2006-01-02 15:04:05")
|
||||
formattedMessage := fmt.Sprintf("[%s] %s: %s\n", timestamp, client.username, message)
|
||||
broadcastMessage(client.room, formattedMessage)
|
||||
}
|
||||
|
||||
// Broadcast a message to all clients in the room
|
||||
func broadcastMessage(room *Room, message string) {
|
||||
room.lock.Lock()
|
||||
defer room.lock.Unlock()
|
||||
for client := range room.clients {
|
||||
client.conn.Write([]byte(message))
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the SYNC command, expecting a JSON payload
|
||||
func handleSync(client *Client, payload string) {
|
||||
var data map[string]interface{}
|
||||
err := json.Unmarshal([]byte(payload), &data)
|
||||
if err != nil {
|
||||
client.conn.Write([]byte("Invalid JSON\n"))
|
||||
return
|
||||
}
|
||||
|
||||
// You can process the JSON payload here as needed
|
||||
client.conn.Write([]byte("Sync received\n"))
|
||||
}
|
||||
|
||||
// Start the server
|
||||
func startServer() {
|
||||
listener, err := net.Listen("tcp", ":5518")
|
||||
func main() {
|
||||
listener, err := net.Listen("tcp", PORT)
|
||||
if err != nil {
|
||||
fmt.Println("Error starting server:", err)
|
||||
return
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
fmt.Println("Server started on port 5518")
|
||||
fmt.Println("Server started on port", PORT)
|
||||
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
@ -203,21 +99,332 @@ func startServer() {
|
||||
fmt.Println("Error accepting connection:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
go func() {
|
||||
conn.Write([]byte("Enter your username: "))
|
||||
username, _ := bufio.NewReader(conn).ReadString('\n')
|
||||
username = strings.TrimSpace(username)
|
||||
client := &Client{
|
||||
conn: conn,
|
||||
username: username,
|
||||
}
|
||||
conn.Write([]byte(fmt.Sprintf("Welcome %s!\n", username)))
|
||||
handleClient(client)
|
||||
}()
|
||||
go handleClient(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
startServer()
|
||||
type ConnectionRequestPacket struct {
|
||||
UserID string
|
||||
RoomCode string
|
||||
Password string
|
||||
}
|
||||
|
||||
type ConnectionResponsePacket struct {
|
||||
Success bool
|
||||
Reason string
|
||||
}
|
||||
|
||||
type CreateRoomRequestPacket struct {
|
||||
UserID string
|
||||
Password string
|
||||
}
|
||||
|
||||
type CreateRoomResponsePacket struct {
|
||||
Success bool
|
||||
Reason string
|
||||
RoomCode string
|
||||
}
|
||||
|
||||
type JoinRequestPacket struct {
|
||||
UserID string
|
||||
Password string
|
||||
RoomCode string
|
||||
}
|
||||
|
||||
type JoinRequestResponsePacket struct {
|
||||
Success bool
|
||||
Reason string
|
||||
CurrentData string
|
||||
}
|
||||
|
||||
type SendDataRequestPacket struct {
|
||||
UserID string
|
||||
Data string
|
||||
}
|
||||
|
||||
type ForwardDataPacket struct {
|
||||
Data string
|
||||
}
|
||||
|
||||
type SendDataResponsePacket struct {
|
||||
Success bool
|
||||
Reason string
|
||||
}
|
||||
|
||||
func handleClient(conn net.Conn) {
|
||||
// defer conn.Close()
|
||||
|
||||
for {
|
||||
|
||||
var packetId PacketID
|
||||
packetId, data, err := readPacket(conn)
|
||||
if err != nil {
|
||||
fmt.Println("Error reading packet:", err)
|
||||
return
|
||||
}
|
||||
|
||||
switch packetId {
|
||||
case CONNECT_REQUEST:
|
||||
var packet ConnectionRequestPacket
|
||||
if err := json.Unmarshal(data, &packet); err != nil {
|
||||
fmt.Println("Error decoding connection request:", err)
|
||||
return
|
||||
}
|
||||
handleConnectRequest(conn, packet)
|
||||
case CREATE_REQUEST:
|
||||
var packet CreateRoomRequestPacket
|
||||
if err := json.Unmarshal(data, &packet); err != nil {
|
||||
fmt.Println("Error decoding connection request:", err)
|
||||
return
|
||||
}
|
||||
handleCreateRoomRequest(conn, packet)
|
||||
case JOIN_REQUEST:
|
||||
var packet JoinRequestPacket
|
||||
if err := json.Unmarshal(data, &packet); err != nil {
|
||||
fmt.Println("Error decoding connection request:", err)
|
||||
return
|
||||
}
|
||||
handleJoinRequest(conn, packet)
|
||||
case SEND_DATA_REQUEST:
|
||||
var packet SendDataRequestPacket
|
||||
if err := json.Unmarshal(data, &packet); err != nil {
|
||||
fmt.Println("Error decoding connection request:", err)
|
||||
return
|
||||
}
|
||||
handleSendData(conn, packet)
|
||||
case LEAVE_ROOM:
|
||||
handleLeaveRoom(conn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleConnectRequest(conn net.Conn, packet ConnectionRequestPacket) {
|
||||
room := findRoom(packet.RoomCode)
|
||||
if room == nil {
|
||||
writePacket(conn, byte(CONNECT_RESPONSE), encodeResponsePacket(ConnectionResponsePacket{
|
||||
Success: false,
|
||||
Reason: "room does not exist",
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
if room.Password == "" || packet.Password == room.Password {
|
||||
writePacket(conn, byte(CONNECT_RESPONSE), encodeResponsePacket(ConnectionResponsePacket{
|
||||
Success: true,
|
||||
Reason: "",
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
writePacket(conn, byte(CONNECT_RESPONSE), encodeResponsePacket(ConnectionResponsePacket{
|
||||
Success: false,
|
||||
Reason: "invalid password",
|
||||
}))
|
||||
}
|
||||
|
||||
func handleCreateRoomRequest(conn net.Conn, packet CreateRoomRequestPacket) {
|
||||
if !isValidPassword(packet.Password) {
|
||||
writePacket(conn, byte(CREATE_RESPONSE), encodeResponsePacket(CreateRoomResponsePacket{
|
||||
Success: false,
|
||||
Reason: "invalid password",
|
||||
RoomCode: "",
|
||||
}))
|
||||
return
|
||||
}
|
||||
code := generateRandomRoomCode()
|
||||
|
||||
room := &Room{
|
||||
Code: code,
|
||||
Password: packet.Password,
|
||||
Clients: []*Client{},
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
rooms[code] = room
|
||||
mu.Unlock()
|
||||
|
||||
writePacket(conn, byte(CREATE_RESPONSE), encodeResponsePacket(CreateRoomResponsePacket{
|
||||
Success: true,
|
||||
Reason: "",
|
||||
RoomCode: code,
|
||||
}))
|
||||
}
|
||||
|
||||
func handleJoinRequest(conn net.Conn, packet JoinRequestPacket) {
|
||||
room := findRoom(packet.RoomCode)
|
||||
if room == nil {
|
||||
writePacket(conn, byte(JOIN_RESPONSE), encodeResponsePacket(JoinRequestResponsePacket{
|
||||
Success: false,
|
||||
Reason: "room not found",
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
if !isValidPassword(packet.Password) {
|
||||
writePacket(conn, byte(JOIN_RESPONSE), encodeResponsePacket(JoinRequestResponsePacket{
|
||||
Success: false,
|
||||
Reason: "invalid password",
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
if room.Password == "" || room.Password == packet.Password {
|
||||
client := &Client{
|
||||
Conn: conn,
|
||||
ClientID: packet.UserID,
|
||||
RoomCode: packet.RoomCode,
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
room.Clients = append(room.Clients, client)
|
||||
clients[packet.UserID] = client
|
||||
mu.Unlock()
|
||||
|
||||
writePacket(conn, byte(JOIN_RESPONSE), encodeResponsePacket(JoinRequestResponsePacket{
|
||||
Success: true,
|
||||
Reason: "",
|
||||
CurrentData: "{}",
|
||||
// TODO: Send current data
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
writePacket(conn, byte(JOIN_RESPONSE), encodeResponsePacket(JoinRequestResponsePacket{
|
||||
Success: false,
|
||||
Reason: "invalid password",
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
func handleSendData(conn net.Conn, packet SendDataRequestPacket) {
|
||||
mu.Lock()
|
||||
client, exists := clients[packet.UserID]
|
||||
mu.Unlock()
|
||||
|
||||
if !exists || client.RoomCode == "" {
|
||||
writePacket(conn, byte(SEND_DATA_RESPONSE), encodeResponsePacket(SendDataResponsePacket{
|
||||
Success: false,
|
||||
Reason: "client not in a room",
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
room := findRoom(client.RoomCode)
|
||||
if room == nil {
|
||||
writePacket(conn, byte(SEND_DATA_RESPONSE), encodeResponsePacket(SendDataResponsePacket{
|
||||
Success: false,
|
||||
Reason: "room not found",
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
dataPacket := encodeResponsePacket(ForwardDataPacket{
|
||||
Data: packet.Data,
|
||||
})
|
||||
|
||||
mu.Lock()
|
||||
for _, roomClient := range room.Clients {
|
||||
// if roomClient.ClientID != packet.UserID {
|
||||
// // Send data to all other clients except the sender
|
||||
// writePacket(roomClient.Conn, byte(READ_DATA), dataPacket)
|
||||
// }
|
||||
writePacket(roomClient.Conn, byte(READ_DATA), dataPacket)
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
writePacket(conn, byte(SEND_DATA_RESPONSE), encodeResponsePacket(SendDataResponsePacket{
|
||||
Success: true,
|
||||
Reason: "",
|
||||
}))
|
||||
}
|
||||
|
||||
func handleLeaveRoom(conn net.Conn) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
var leavingClient *Client
|
||||
for _, client := range clients {
|
||||
if client.Conn == conn {
|
||||
leavingClient = client
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if leavingClient == nil {
|
||||
fmt.Println("Client not found")
|
||||
return
|
||||
}
|
||||
|
||||
room := findRoom(leavingClient.RoomCode)
|
||||
if room == nil {
|
||||
fmt.Println("Room not found")
|
||||
return
|
||||
}
|
||||
|
||||
for i, client := range room.Clients {
|
||||
if client == leavingClient {
|
||||
// Remove the client from the list
|
||||
room.Clients = append(room.Clients[:i], room.Clients[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If the room is now empty or the leaving client was the creator, delete the room
|
||||
if len(room.Clients) == 0 || room.Clients[0] == leavingClient {
|
||||
for _, client := range room.Clients {
|
||||
client.Conn.Close()
|
||||
}
|
||||
|
||||
delete(rooms, room.Code)
|
||||
fmt.Println("Room", room.Code, "deleted")
|
||||
} else {
|
||||
// // Notify remaining clients that a user has left (optional)
|
||||
// for _, remainingClient := range room.Clients {
|
||||
// writePacket(remainingClient.Conn, byte(LEAVE_ROOM), []byte(fmt.Sprintf("User %s has left the room", leavingClient.ClientID)))
|
||||
// }
|
||||
}
|
||||
|
||||
delete(clients, leavingClient.ClientID)
|
||||
|
||||
leavingClient.Conn.Close()
|
||||
fmt.Println("Client", leavingClient.ClientID, "left the room and connection closed")
|
||||
}
|
||||
|
||||
func findRoom(code string) *Room {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
return rooms[code]
|
||||
}
|
||||
|
||||
func encodeResponsePacket(packet interface{}) []byte {
|
||||
data, err := json.Marshal(packet)
|
||||
if err != nil {
|
||||
fmt.Println("Error encoding response packet:", err)
|
||||
return nil
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func generateRandomRoomCode() string {
|
||||
validChars := "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
code := make([]byte, 4)
|
||||
for i := range code {
|
||||
code[i] = validChars[rand.Intn(len(validChars))]
|
||||
}
|
||||
return string(code)
|
||||
}
|
||||
|
||||
func isValidPassword(password string) bool {
|
||||
validChars := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789?!()[]{}<>-_"
|
||||
charMap := make(map[rune]bool)
|
||||
for _, char := range validChars {
|
||||
charMap[char] = true
|
||||
}
|
||||
|
||||
for _, char := range password {
|
||||
if !charMap[char] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user