wip: add server and client logic

This commit is contained in:
illyum 2024-10-25 22:20:55 -06:00
parent c24ae92a81
commit 65caea328b
6 changed files with 943 additions and 238 deletions

View File

@ -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
View 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")
}

View File

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

View File

@ -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
View File

@ -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

View File

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