Untitled

mail@pastecode.io avatar
unknown
plain_text
a year ago
9.0 kB
4
Indexable
Never
package main

import (
	"bufio"
	//"path/filepath"
	"fmt"
	//"io"
	"log"
	"net"
	"os"
	"strconv"
	"strings"
	"sync"
	"time"
)

const (
	HOST        = "localhost"
	TYPE        = "tcp"
	MaxNumConns = 10
	BUFFERSIZE  = 1024
)

var (
	linuxLogo = `
         _nnnn_
        dGGGGMMb
       @p~qp~~qMb
       M|@||@) M|
       @,----.JM|
      JS^\__/  qKL
     dZP        qKRb
    dZP          qKKb
   fZP            SMMb
   HZM            MMMM
   FqM            MMMM
 __| ".        |\dS"qML
 |    ` + "`" + `.       | ` + "`" + `' \Zq
_)      \.___.,|     .'
\____   )MMMMMP|   .'
     ` + "`" + `-'       ` + "`" + `--'
`
	clients     = make(map[net.Conn]struct{})
	clientsMux  sync.Mutex
	clientNames = make(map[net.Conn]string)
	wg          sync.WaitGroup
	PORT        int = 2522
	messages    []string
	messagesMux sync.Mutex
)

func main() {
	if len(os.Args) > 1 {
		PORT, _ = strconv.Atoi(os.Args[1])
		if len(os.Args) > 2 {
			fmt.Println("[USAGE]: ./TCPChat $port || go run . $port")
			return
		}
	}
	fmt.Println("listening on port", PORT)
	file, err := os.Create("logfile.txt")
	if err != nil {
		fmt.Println("ERROR CREATIN FILE")
		return
	}
	defer file.Close()

	originalStdout := os.Stdout
	os.Stdout = file

	fmt.Println("Port: ", PORT)
	listen, err := net.Listen(TYPE, HOST+":"+strconv.Itoa(PORT))
	if err != nil {
		log.Fatal(err)
		os.Exit(1)
	}
	fmt.Println("listening on port", PORT)
	// close listener
	defer listen.Close()
	for {
		if len(clients) >= MaxNumConns {
			fmt.Println("Max number of connections reached. Rejecting new connections...")
			conn, err := listen.Accept()
			if err != nil {
				log.Println(err)
				continue
			}
			conn.Close()
			continue
		}
		conn, err := listen.Accept()
		if err != nil {
			log.Fatal(err)
			os.Exit(1)
		}
		wg.Add(1)
		go handleIncomingRequest(conn)
	}

	os.Stdout = originalStdout
}

/* func sendFileToClient(connection net.Conn) {

	reader := bufio.NewReader(connection)
	// Receive file path from client
	srcFilePath, err := reader.ReadString('\n')
	if err != nil {
		log.Println(err)
		return
	}
	srcFilePath = strings.TrimSpace(srcFilePath)

	if srcFilePath == "" {
		connection.Write([]byte("Please specify filepath\n"))
		return
	}
	connection.Write([]byte(fmt.Sprintf("File transmission incoming, the file name is: %s. Type accept to accept file transmission \n", filepath.Base(srcFilePath))))
	reader = bufio.NewReader(connection)
	input, err := reader.ReadString('\n')
	if err != nil {
		log.Println(err)
		return
	}

	input = strings.TrimSpace(input)
	if input != "accept" {
		connection.Write([]byte("You have declined the file transfer "))
		return
	}
	originalFile, err := os.Open(srcFilePath)
	if err != nil {
		fmt.Println(err)
		return
	}
	var copyName string
	defer originalFile.Close()
	if len(os.Args) == 1 {
		connection.Write([]byte("\n You haven't specified the name of the file"))
		return
	}
	// Create a new file to store the copy
	copyFile, err := os.Create(copyName)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer copyFile.Close()

	fileInfo, err := originalFile.Stat()
	if err != nil {
		fmt.Println(err)
		return
	}

	fileSize := fillString(strconv.FormatInt(fileInfo.Size(), 10), 10)
	fileName := fillString(fileInfo.Name(), 64)
	fmt.Println("Sending filename and filesize!")
	connection.Write([]byte(fileSize))
	connection.Write([]byte(fileName))

	sendBuffer := make([]byte, BUFFERSIZE)
	fmt.Println("Start sending file!")

	for {
		n, err := originalFile.Read(sendBuffer)
		if err == io.EOF {
			break
		}
		if n > 0 {
			connection.Write(sendBuffer[:n])
			copyFile.Write(sendBuffer[:n]) // Write the bytes directly to the copy file
		}
	}

	fmt.Println("File has been sent and copied, closing connection!")
} */

func handleIncomingRequest(conn net.Conn) {
	defer func() {
		clientsMux.Lock()
		delete(clients, conn)
		disconnectedName := clientNames[conn]
		delete(clientNames, conn)
		clientsMux.Unlock()

		// Notify other clients about the disconnection
		heDisconnected := fmt.Sprintf("\n%s has left the chat\n", disconnectedName)
		notifyClients(heDisconnected, conn, false)
		conn.Write([]byte(fmt.Sprintf("You have disconnected, press enter to close connection and return.")))
		conn.Close()
		fmt.Println("Client disconnected:", conn.RemoteAddr())
		wg.Done()
	}()

	fmt.Println("Client connected:", conn.RemoteAddr())

	// Add the client to the clients map
	clientsMux.Lock()
	clients[conn] = struct{}{}
	clientsMux.Unlock()

	// Send the initial response

	response := fmt.Sprintf("Welcome to TCP-Chat!\n%s\n", linuxLogo)
	conn.Write([]byte(response))

	reader := bufio.NewReader(conn)
	timeStr := time.Now().Format("Monday, 02-Jan-06 15:04:05 MST")
	// Process the name
	var name string
	var enteredName = false

	for !enteredName {
		conn.Write([]byte("[Enter your name]: "))

		// Read the user's input
		input, err := reader.ReadString('\n')
		if err != nil {
			log.Println(err)
			break
		}

		name = strings.TrimSpace(input)
		if name == "" {
			conn.Write([]byte("Your name cannot be empty\n"))
			continue
		}
		if nameExists(name) {
			conn.Write([]byte("Name already exists. Please choose a different name.\n"))
			continue
		}

		// Respond to the client
		conn.Write([]byte(fmt.Sprintf("Hello, %s!\n", name)))
		// conn.Write([]byte("Press enter to continue to the chatroom\n"))
		break
	}

	fmt.Println("Name:", name)
	clientNames[conn] = name
	enteredName = true

	// Respond to the client
	conn.Write([]byte(fmt.Sprintf("Hello, %s! You are now in the chatroom.\n", name)))
	// conn.Write([]byte("Press enter to continue.\n"))
	//defaultEntry := fmt.Sprintf("[%s][%s]: ", timeStr, name)
	userJoin := fmt.Sprintf("\n User '%s' joined the chatroom.\n", name)
	messagesMux.Lock()
	for _, message := range messages {
		if enteredName {
			clearLines(conn, 1)
		}
		conn.Write([]byte(message))
	}
	messagesMux.Unlock()
	// Notify other clients about the new user joining
	notifyClients(userJoin, conn, true)

	for {
		timeStr = time.Now().Format("Monday, 02-Jan-06 15:04:05 MST")
		ownDetails := fmt.Sprintf("[%s][%s]: ", timeStr, name)
		conn.Write([]byte(ownDetails))
		message, err := reader.ReadString('\n')
		if err != nil {
			log.Println(err)
			break
		}

		// Process the received message
		message = strings.TrimSpace(message)

		if message == "exit" {
			break
		} /* else if message == "send" {
			for client := range clients {
				if client != conn {
					sendFileToClient(client)
				}
			}
		} */
		if strings.HasPrefix(message, "change name") {
			newName := strings.TrimSpace(strings.TrimPrefix(message, "change name"))
			clientsMux.Lock()
			oldName := clientNames[conn]
			clientsMux.Unlock()

			clientsMux.Lock()
			clientNames[conn] = newName
			clientsMux.Unlock()
			
			conn.Write([]byte(fmt.Sprintf("You have changed your name to %s\n", newName)))
			nameChangeMessage := fmt.Sprintf("\n%s changed his name to %s\n", oldName, newName)
			notifyClients(nameChangeMessage, conn, false)
			name = newName
			continue
		}

		// Update the receiver's details
		receiverTimeStr := time.Now().Format("Monday, 02-Jan-06 15:04:05 MST")
		receiverName := name

		messageWithDetails := fmt.Sprintf("\n[%s][%s]: %s\n", receiverTimeStr, receiverName, message)

		// Broadcast the message to other clients
		if message != "" {
			messagesMux.Lock()
			messages = append(messages, messageWithDetails)
			messagesMux.Unlock()
			notifyClients(messageWithDetails, conn, false)
			fmt.Println("[" + timeStr + "]" + "[" + name + "]: " + message)
		}
	}
}

func nameExists(name string) bool {
	for _, existingName := range clientNames {
		if existingName == name {
			return true
		}
	}
	return false
}

func notifyClients(message string, sender net.Conn, entry bool) {
	clientsMux.Lock()
	defer clientsMux.Unlock()

	for client := range clients {
		if client != sender {
			if !entry {
				clearLines(client, 1)
			}
			clientTimeStr := time.Now().Format("Monday, 02-Jan-06 15:04:05 MST")
			receiverName := clientNames[client]
			if receiverName == "" {
				return
			}
			// Construct the default entry with the client's time
			defaultEntry := fmt.Sprintf("[%s][%s]: ", clientTimeStr, receiverName)

			// Send the message and default entry to the client
			client.Write([]byte(message))
			client.Write([]byte(defaultEntry))
		}
	}
}

func clearLines(client net.Conn, lines int) {
	for i := 0; i < lines; i++ {

		if clientNames[client] != "" {
			client.Write([]byte("\033[2K\033[1A"))
		}
	}
}

func fillString(retunString string, toLength int) string {
	for {
		lengtString := len(retunString)
		if lengtString < toLength {
			retunString = retunString + ":"
			continue
		}
		break
	}
	return retunString
}