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
|
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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
netclient "hudly/client"
|
||||||
"hudly/hypixel"
|
"hudly/hypixel"
|
||||||
"hudly/mcfetch"
|
"hudly/mcfetch"
|
||||||
"log"
|
"log"
|
||||||
@ -9,8 +12,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var key = "9634ea92-80f0-482f-aebd-b082c6ed6f19"
|
var key = "ccebff0f-939a-4afe-b5b3-30a7a665ee38"
|
||||||
var uuid = "5328930e-d411-49cb-90ad-4e5c7b27dd86"
|
var UUID = "5328930e-d411-49cb-90ad-4e5c7b27dd86"
|
||||||
|
|
||||||
func demo() {
|
func demo() {
|
||||||
// Ensure a username is provided as a command-line argument
|
// Ensure a username is provided as a command-line argument
|
||||||
@ -73,3 +76,157 @@ func demo() {
|
|||||||
}
|
}
|
||||||
fmt.Println(fmt.Sprintf("%+v", res))
|
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
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Connect to the chat server
|
const PORT = ":5518"
|
||||||
func connectToServer(address string) (net.Conn, error) {
|
|
||||||
conn, err := net.Dial("tcp", address)
|
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 {
|
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
|
// CreateRoom creates a new room on the server.
|
||||||
func readInputAndSend(conn net.Conn) {
|
func (c *Client) CreateRoom(password string) (string, error) {
|
||||||
reader := bufio.NewReader(os.Stdin)
|
request := CreateRoomRequestPacket{
|
||||||
for {
|
UserID: c.ClientID,
|
||||||
fmt.Print("> ")
|
Password: password,
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// JoinRoom joins an existing room on the server.
|
||||||
func listenForMessages(conn net.Conn) {
|
func (c *Client) JoinRoom(roomCode, password string) error {
|
||||||
reader := bufio.NewReader(conn)
|
request := JoinRequestPacket{
|
||||||
for {
|
UserID: c.ClientID,
|
||||||
message, err := reader.ReadString('\n')
|
RoomCode: roomCode,
|
||||||
if err != nil {
|
Password: password,
|
||||||
fmt.Println("Disconnected from server.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Print(message)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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() {
|
func (c *Client) ListenForData() {
|
||||||
if len(os.Args) < 2 {
|
go func() {
|
||||||
fmt.Println("Usage: go run client.go <server-address>")
|
for {
|
||||||
return
|
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]
|
if err := c.sendPacket(SEND_DATA_REQUEST, request); err != nil {
|
||||||
conn, err := connectToServer(serverAddress)
|
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 {
|
if err != nil {
|
||||||
fmt.Println(err)
|
return fmt.Errorf("failed to encode packet: %w", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
return writePacket(c.Conn, byte(packetID), packetData)
|
||||||
|
}
|
||||||
// Start a goroutine to listen for incoming messages from the server
|
|
||||||
go listenForMessages(conn)
|
// receivePacket reads a response from the server for a given packet ID.
|
||||||
|
func (c *Client) receivePacket(expected PacketID, v interface{}) error {
|
||||||
// Read input from the terminal and send it to the server
|
packetID, data, err := readPacket(c.Conn)
|
||||||
readInputAndSend(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
|
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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
|
||||||
"sync"
|
"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 {
|
type Client struct {
|
||||||
conn net.Conn
|
Conn net.Conn
|
||||||
username string
|
ClientID string
|
||||||
room *Room
|
RoomCode string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Room struct {
|
type Room struct {
|
||||||
code string
|
Code string
|
||||||
password string
|
Password string
|
||||||
clients map[*Client]bool
|
Clients []*Client
|
||||||
lock sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var rooms = map[string]*Room{}
|
||||||
rooms = make(map[string]*Room)
|
var clients = map[string]*Client{}
|
||||||
mu sync.Mutex
|
var mu sync.Mutex
|
||||||
)
|
|
||||||
|
|
||||||
// Helper function to generate a 4-hexadecimal room code
|
func readPacket(conn net.Conn) (PacketID, []byte, error) {
|
||||||
func generateRoomCode() string {
|
// First, read the length of the packet (4 bytes)
|
||||||
rand.Seed(time.Now().UnixNano())
|
var length uint32
|
||||||
return fmt.Sprintf("%04x", rand.Intn(0xFFFF))
|
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 writePacket(conn net.Conn, messageType byte, data []byte) error {
|
||||||
func handleClient(client *Client) {
|
// Calculate the total length of the packet
|
||||||
defer client.conn.Close()
|
// 4 bytes for length, 1 byte for messageType
|
||||||
|
length := uint32(5 + len(data))
|
||||||
|
|
||||||
reader := bufio.NewReader(client.conn)
|
// Write the length and the message type
|
||||||
|
if err := binary.Write(conn, binary.BigEndian, length); err != nil {
|
||||||
for {
|
return err
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
|
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 main() {
|
||||||
func processCommand(client *Client, input string) {
|
listener, err := net.Listen("tcp", PORT)
|
||||||
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")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error starting server:", err)
|
fmt.Println("Error starting server:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
|
|
||||||
fmt.Println("Server started on port 5518")
|
fmt.Println("Server started on port", PORT)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
conn, err := listener.Accept()
|
conn, err := listener.Accept()
|
||||||
@ -203,21 +99,332 @@ func startServer() {
|
|||||||
fmt.Println("Error accepting connection:", err)
|
fmt.Println("Error accepting connection:", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
go handleClient(conn)
|
||||||
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)
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
type ConnectionRequestPacket struct {
|
||||||
startServer()
|
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