summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.clang-format51
-rw-r--r--.clang-tidy101
-rwxr-xr-x[-rw-r--r--].gitignore0
-rw-r--r--Makefile61
-rw-r--r--Version_History.md20
-rw-r--r--main-pre.py291
-rw-r--r--main.py276
-rwxr-xr-xsetup.sh40
-rw-r--r--src/FileDescriptor.cpp89
-rw-r--r--src/FileDescriptor.hpp39
-rw-r--r--src/log.hpp13
-rw-r--r--src/main.cpp9
-rw-r--r--src/server.cpp50
-rw-r--r--src/server.hpp23
-rw-r--r--src/session.cpp112
-rw-r--r--src/session.hpp32
-rw-r--r--src/sessionManager.cpp29
-rw-r--r--src/sessionManager.hpp20
18 files changed, 629 insertions, 627 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..c1bd334
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,51 @@
+---
+Language: Cpp
+BasedOnStyle: LLVM
+
+PointerAlignment: Right
+RemoveBracesLLVM: true
+AlignAfterOpenBracket: BlockIndent
+AlignEscapedNewlines: Right
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AlignConsecutiveAssignments: true
+AlignConsecutiveBitFields: true
+AlignConsecutiveDeclarations: true
+AlignConsecutiveMacros: true
+# AlignConsecutiveShortCaseStatements: # need clang-format 17(not avaliable yet)
+ # Enabled: true
+# AlignConsecutiveTableGenBreakingDAGArgColons: true # need clang-format 19(not avaliable yet)
+# AlignConsecutiveTableGenCondOperatorColons: true # need clang-format 19(not avaliable yet)
+# AlignConsecutiveTableGenDefinitionColons: true # need clang-format 19(not avaliable yet)
+BinPackParameters: false
+Standard: Auto
+UseTab: Always
+IndentWidth: 4
+TabWidth: 4
+ColumnLimit: 0
+# AccessModifierOffset: -4
+BreakConstructorInitializers: BeforeComma
+ConstructorInitializerAllOnOneLineOrOnePerLine: true
+PackConstructorInitializers: Never
+
+BraceWrapping: {
+ AfterCaseLabel: false,
+ AfterClass: false,
+ AfterControlStatement: Never,
+ AfterEnum: false,
+ AfterFunction: false,
+ AfterNamespace: false,
+ AfterObjCDeclaration: false,
+ AfterStruct: false,
+ AfterUnion: false,
+ AfterExternBlock: false,
+ BeforeCatch: false,
+ BeforeElse: false,
+ BeforeLambdaBody: false,
+ BeforeWhile: false,
+ IndentBraces: false,
+ SplitEmptyFunction: true,
+ SplitEmptyRecord: true,
+}
+BreakBeforeBraces: Custom
+# InsertBraces: false
diff --git a/.clang-tidy b/.clang-tidy
new file mode 100644
index 0000000..84f69b0
--- /dev/null
+++ b/.clang-tidy
@@ -0,0 +1,101 @@
+WarningsAsErrors: '*'
+HeaderFilterRegex: '.*\.hpp'
+FormatStyle: file
+Checks: >
+ -*,
+ bugprone-*,
+ -bugprone-easily-swappable-parameters,
+ -bugprone-forward-declararion-namespace,
+ -bugprone-forward-declararion-namespace,
+ -bugprone-macro-parentheses,
+ -bugprone-narrowing-conversions,
+ -bugprone-branch-clone,
+ -bugprone-assignment-in-if-condition,
+ concurrency-*,
+ -concurrency-mt-unsafe,
+ cppcoreguidelines-*,
+ -cppcoreguidelines-owning-memory,
+ -cppcoreguidelines-avoid-magic-numbers,
+ -cppcoreguidelines-pro-bounds-constant-array-index,
+ -cppcoreguidelines-avoid-const-or-ref-data-members,
+ -cppcoreguidelines-non-private-member-variables-in-classes,
+ -cppcoreguidelines-avoid-goto,
+ -cppcoreguidelines-pro-bounds-array-to-pointer-decay,
+ -cppcoreguidelines-avoid-do-while,
+ -cppcoreguidelines-avoid-non-const-global-variables,
+ -cppcoreguidelines-special-member-functions,
+ -cppcoreguidelines-explicit-virtual-functions,
+ -cppcoreguidelines-avoid-c-arrays,
+ -cppcoreguidelines-pro-bounds-pointer-arithmetic,
+ -cppcoreguidelines-narrowing-conversions,
+ -cppcoreguidelines-pro-type-union-access,
+ -cppcoreguidelines-pro-type-member-init,
+ -cppcoreguidelines-macro-usage,
+ -cppcoreguidelines-macro-to-enum,
+ -cppcoreguidelines-init-variables,
+ -cppcoreguidelines-pro-type-cstyle-cast,
+ -cppcoreguidelines-pro-type-vararg,
+ -cppcoreguidelines-pro-type-reinterpret-cast,
+ google-global-names-in-headers,
+ -google-readability-casting,
+ google-runtime-operator,
+ misc-*,
+ -misc-unused-parameters,
+ -misc-no-recursion,
+ -misc-non-private-member-variables-in-classes,
+ -misc-include-cleaner,
+ -misc-use-anonymous-namespace,
+ -misc-const-correctness,
+ modernize-*,
+ -modernize-return-braced-init-list,
+ -modernize-use-trailing-return-type,
+ -modernize-use-using,
+ -modernize-use-override,
+ -modernize-avoid-c-arrays,
+ -modernize-macro-to-enum,
+ -modernize-loop-convert,
+ -modernize-use-nodiscard,
+ -modernize-pass-by-value,
+ -modernize-use-auto,
+ performance-*,
+ -performance-avoid-endl,
+ -performance-unnecessary-value-param,
+ portability-std-allocator-const,
+ readability-*,
+ -readability-function-cognitive-complexity,
+ -readability-function-size,
+ -readability-identifier-length,
+ -readability-magic-numbers,
+ -readability-uppercase-literal-suffix,
+ -readability-braces-around-statements,
+ -readability-redundant-access-specifiers,
+ -readability-else-after-return,
+ -readability-container-data-pointer,
+ -readability-implicit-bool-conversion,
+ -readability-avoid-nested-conditional-operator,
+ -readability-redundant-member-init,
+ -readability-redundant-string-init,
+ -readability-avoid-const-params-in-decls,
+ -readability-named-parameter,
+ -readability-convert-member-functions-to-static,
+ -readability-qualified-auto,
+ -readability-make-member-function-const,
+ -readability-isolate-declaration,
+ -readability-inconsistent-declaration-parameter-name,
+ -clang-diagnostic-error,
+
+CheckOptions:
+ performance-for-range-copy.WarnOnAllAutoCopies: true
+ performance-inefficient-string-concatenation.StrictMode: true
+ readability-braces-around-statements.ShortStatementLines: 0
+ readability-identifier-naming.ClassCase: CamelCase
+ readability-identifier-naming.ClassIgnoredRegexp: I.*
+ readability-identifier-naming.ClassPrefix: C # We can't use regex here?!?!?!?
+ readability-identifier-naming.EnumCase: CamelCase
+ readability-identifier-naming.EnumPrefix: e
+ readability-identifier-naming.EnumConstantCase: UPPER_CASE
+ readability-identifier-naming.FunctionCase: camelBack
+ readability-identifier-naming.NamespaceCase: CamelCase
+ # readability-identifier-naming.NamespacePrefix: N
+ readability-identifier-naming.StructPrefix: S
+ readability-identifier-naming.StructCase: CamelCase
diff --git a/.gitignore b/.gitignore
index 7ee6886..7ee6886 100644..100755
--- a/.gitignore
+++ b/.gitignore
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..afd0ce8
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,61 @@
+BIN = chat-server
+SRCS = $(wildcard src/*.cpp)
+HEADERS = $(wildcard src/*.hpp)
+
+PREFIX ?= /usr/local
+BINDIR ?= $(PREFIX)/bin
+DATADIR ?= $(PREFIX)/share
+
+OBJS = $(SRCS:.cpp=.o)
+
+CXXFLAGS += -std=c++23 -Oz -s -Wall -flto -fPIC
+LDFLAGS += -Wl,--as-needed,-z,now,-z,pack-relative-relocs
+
+# Debug flags
+DEBUG_CXXFLAGS = -std=c++23 -g3 -O0 -Wall -fno-lto -fPIC -DDEBUG
+DEBUG_LDFLAGS = -Wl,--as-needed,-z,now,-z,pack-relative-relocs
+
+JOB_COUNT := $(BIN) $(OBJS)
+JOBS_DONE := $(shell ls -l $(JOB_COUNT) 2> /dev/null | wc -l)
+
+define progress
+ $(eval JOBS_DONE := $(shell echo $$(($(JOBS_DONE) + 1))))
+ @printf "[$(JOBS_DONE)/$(shell echo $(JOB_COUNT) | wc -w)] %s %s\n" $(1) $(2)
+endef
+
+all: $(BIN)
+
+install: $(all)
+ @echo "Installing..."
+ @install -D -t $(DESTDIR)$(BINDIR) $(BIN)
+
+clean:
+ @echo "Cleaning up"
+ -@rm $(BIN) $(OBJS)
+
+$(BIN): $(OBJS)
+ $(call progress, Linking $@)
+ @$(CXX) -o $(BIN) \
+ $(OBJS) \
+ $(CXXFLAGS) \
+ $(LDFLAGS) \
+ -Wl,--no-as-needed \
+
+
+%.o: %.cpp
+ $(call progress, Compiling $@)
+ @$(CXX) -c $< -o $@ \
+ $(CXXFLAGS)
+
+clang-format:
+ @echo "Formatting code..."
+ @clang-format -i $(SRCS) $(HEADERS)
+
+clang-tidy:
+ @echo "Running clang-tidy..."
+ @clang-tidy $(SRCS) -- $(CXXFLAGS)
+
+debug: CXXFLAGS = $(DEBUG_CXXFLAGS)
+debug: LDFLAGS = $(DEBUG_LDFLAGS)
+debug: $(BIN)
+ @echo "Debug build complete"
diff --git a/Version_History.md b/Version_History.md
deleted file mode 100644
index 3fa378a..0000000
--- a/Version_History.md
+++ /dev/null
@@ -1,20 +0,0 @@
-# Version History
-
-#### Version 1.2
-
-<details>
-1. admin commands: mute, unmute, tempmute
-2. automute: swear 3 times as normal user
-3. userlist, kick username
-</details>
-
-#### Version 1.1
-
-<details>
-1. Implement not swearing words
-2. Remove Inapropriate/invalid names
-3. fixed all "Broken Pipe" errors
-4. auto-clean history file
-5. Fixed welcome join message appered twice
-6. Improved syntax, and generally improved code
-</details>
diff --git a/main-pre.py b/main-pre.py
deleted file mode 100644
index 5893623..0000000
--- a/main-pre.py
+++ /dev/null
@@ -1,291 +0,0 @@
-
-# Copyright (C) 2023 littleblack111 <https://github.com/littleblack111/>
-
-import socket
-from os import path
-from sys import exit
-import threading
-
-HOST = '0.0.0.0' # listen on both loopback and local IP address
-PORT = 8000
-
-VERSION = "1.2" # Implemented mute, unmute and tempmute, added userlist
-HOME = path.expanduser('~')
-
-swearList = ['fuck', 'shit', 'bitch', 'nigg']
-server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-server_socket.bind((HOST, PORT))
-print(f"Server binded on {HOST} with port {PORT}")
-server_socket.listen()
-
-clients = []
-names = []
-userlist = []
-cmdlist = ['list', 'kick', 'mute', 'unmute', 'tmpmute', 'tempmute']
-sweart1 = []
-sweart2 = []
-muted = []
-
-def stop(self):
- client_thread.join()
- server_thread.join()
- self.running = False
- socket.socket(socket.AF_INET,
- socket.SOCK_STREAM).connect( (self.hostname, self.port))
- self.socket.close()
-
-def broadcast(message):
- for client in clients:
- client.send(message)
- with open(f"{HOME}/scripts/store/chat-server/chat.log", "a") as log:
- try:
- log.write(message.decode())
- except AttributeError:
- log.write(message)
- #with open("chat.log", "a") as log:
- #log.write(message.decode())
-
-def kick(address, kuname, client_socket):
- global faddress
- for c in clients:
- c_address = c.getpeername()[0]
- if c_address == address:
- c.send("You have been kicked\n".encode())
- clients.remove(c)
- c.close()
- names.remove(kuname)
- userlist.remove(kuname)
- if faddress:
- address = userlist[userlist.index(address)-1]
- userlist.remove(address)
- faddress = False
- try:
- muted.remove(kuname)
- except ValueError:
- pass
- client_socket.send(f"Kicked client at {address} Succefully\n".encode())
- try:
- broadcast(f"{address} is kicked")
- except:
- pass
- return
- elif c_address != address and address in userlist:
- for u in userlist:
- if address == u:
- address = userlist[userlist.index(u)-1]
- faddress = True
- continue
- else:
- client_socket.send(f"No client found at {address}\n".encode())
-
-
-def mute(mname: str):
- muted.append(mname)
-
-def unmute(mname: str):
- muted.remove(mname)
- try:
- tplaceholdertmp.cancel()
- except:
- pass
-
-def tempmute(mname: str, time: int):
- global tplaceholdertmp
- mute(mname)
- #threading.Timer(time*60, unmute(name))
- tplaceholdertmp = threading.Timer(time*60, unmute, mname).start()
-
-def verifyaccount(vname: str, vpasswd: str):
- vpasswdhash = hash(vpasswd)
- del vpasswd
- with open(f"{HOME}/scripts/store/chat-server/accountdb.plaindb", r) as db:
- while line := db.readline():
- verificationcontent = line.rstrip()
-
-
-
-def createaccount(cname: str, cpasswd: str):
- cpasswdhash = hash(cpasswd)
- del cpasswd
- with open(f"{HOME}/scripts/store/chat-server/accountdb.plaindb", "a") as db:
- db.writelines(f"{cname}:{cpasswdhash}")
- print(f"Successfully created account with username: {cname} and passwordhash of {cpasswdhash}")
-
-
-def handle_client(client_socket, client_address):
- print(f'New connection from {client_address}')
- if client_address[0] in [c.getpeername()[0] for c in clients]:
- print(f'{client_address} is attempting to connect twice')
- client_socket.send("Your already connected\n".encode())
- client_socket.close()
- return
- clients.append(client_socket)
- userlist.append(client_address[0])
- client_socket.send("Name: ".encode())
- try:
- name = client_socket.recv(1024).decode().replace("\n", "")
- if name:
- if name == "" or " " in name or SwearOrNot(name):
- client_socket.send("Your name have problems\n".encode())
- print(f"{client_address}'s name contains problem")
- clients.remove(client_socket)
- client_socket.close()
- return
- for n in names:
- if n == name:
- snames = " ".join(names)
- client_socket.send(f"Username taken, use an unused name, current names: {snames}\n".encode())
- print(f'{client_address} is trying to use a taken name - {name}')
- clients.remove(client_socket)
- client_socket.close()
- return
- userlist.insert(userlist.index(client_address[0])+1, name)
- print(userlist)
- print(userlist[userlist.index(name)])
- else:
- raise Exception(f"Client '{client_address}' disconnected without registering")
- except Exception as e:
- print(e)
- clients.remove(client_socket)
- print(f'{client_address} has left the chat without registering')
- #broadcast(f'{client_address} has left the chat\n'.encode())
- broadcast(f'{client_address} has left the chat without even registering\n'.encode())
- client_socket.close()
- return
-
- names.append(name)
- #broadcast(f'{client_address} has joined the chat\n'.encode())
- with open(f"{HOME}/scripts/store/chat-server/chat.log", "r") as log:
- log_contents = log.read()
- client_socket.send(log_contents.encode())
- broadcast(f'{name} has joined the chat\n'.encode())
- #with open("chat.log", "r") as log:
- # log_contents = log.read()
- # client_socket.send(log_contents.encode())
-
- while True:
- try:
- message = client_socket.recv(1024)
- if message and name not in muted:
- rmsg = message.decode()
- if rmsg.startswith("$"): #or client_address[0] == "192.168.1.16" and rmsg.startswith("$"):
- command = rmsg[1:].strip()
- #command_parts = message[1:].strip().split()
- print(f"Command spawned by {client_address},({name}): {command}")
- if command == "list":
- client_socket.send("Connected clients:\n".encode())
- for c in clients:
- c_address = c.getpeername()[0]
- client_socket.send(f"{c_address}\n".encode())
- if client_address[0] == "127.0.0.1":
- if "kick" in command and len(command.split()) == 2:
- kick_address = command.split()[1]
- if kick_address != "127.0.0.1": # and client_address[0] != "192.168.1.16":
- kick(kick_address, userlist[userlist.index(kick_address)+1], client_socket)
- elif "mute" in command and len(command.split()) == 2:
- mute_name = command.split()[1]
- if mute_name != "127.0.0.1":
- mute(mute_name)
- elif "unmute" in command and len(command.split()) == 2:
- unmute_name = command.split()[1]
- if unmute_name in muted:
- print(unmute_name)
- unmute(unmute_name)
- elif command not in cmdlist:
- client_socket.send("Unknown command or syntax \n".encode())
- print(f"{client_address},({name}) send an unknown command: {command}")
- if message.decode() != "" or message.decode() != " ":
- #broadcast(f'{client_address}: {message.decode()}'.encode())
- if SwearOrNot(message.decode()) and client_address[0] != "127.0.0.1":
- broadcast(f'{name}: {len(message.decode())*"#"}\n'.encode())
- if name not in sweart1 and name not in sweart2 and client_address[0] != "127.0.0.1":
- sweart1.append(name)
- print("t1")
- elif name not in sweart2 and name in sweart1 and client_address[0] != "127.0.0.1":
- sweart2.append(name)
- sweart1.remove(name)
- print("t2")
- elif name not in sweart1 and name in sweart2 and client_address[0] != "127.0.0.1":
- tempmute(name, 15)
- print("tmpmute")
- else:
- broadcast(f'{name}: {message.decode()}'.encode())
- print(f'{client_address}({name}): {message.decode()}')
- elif message and name in muted:
- client_socket.send("You have been muted\n".encode())
- else:
- raise Exception(f'Client {name} disconnected')
- except Exception as e:
- print(e)
- clients.remove(client_socket)
- print(f'{client_address} has left the chat')
- #broadcast(f'{client_address} has left the chat\n'.encode())
- broadcast(f'{name} has left the chat\n'.encode())
- client_socket.close()
- names.remove(name)
- userlist.remove(name)
- userlist.remove(client_address[0])
- try:
- muted.remove(name)
- except ValueError:
- pass
- return
-
-def SwearOrNot(var):
- if any(word in var for word in swearList):
- return True
- else:
- return False
-
-
-def accept_connections():
- global client_thread
- while True:
- try:
- client_socket, client_address = server_socket.accept()
- client_thread = threading.Thread(target=handle_client, args=(client_socket, client_address))
- client_thread.start()
- except KeyboardInterrupt:
- print("Closing server...")
- #broadcast("Closing server...")
- #client_thread.join()
- exit()
-
-try:
- if __name__ == '__main__':
- #with open("chat.log", "w") as log:
- # log.truncate(0)
- with open(f"{HOME}/scripts/store/chat-server/chat.log", "w") as log:
- log.truncate(0)
- try:
- server_thread = threading.Thread(target=accept_connections)
- server_thread.start()
- #client_socket, client_address = server_socket.accept()
- #handle_client(client_socket, client_address)
- except KeyboardInterrupt:
- print("Closing server...")
- #broadcast("Closing server...")
- #client_thread.join()
- exit()
-
- # Handle the server's own connection
- try:
- while True:
- try:
- message = input('> ')
- broadcast(f'{message}\n'.encode())
- except KeyboardInterrupt:
- print("Closing server...")
- #broadcast("Closing server...")
- #client_thread.join()
- exit()
- except KeyboardInterrupt:
- print("Closing server...")
- #broadcast("Closing server...")
- #client_thread.join()
- exit()
-except KeyboardInterrupt:
- print("Closing server...")
- #broadcast("Closing server...")
- #client_thread.join()
- exit()
diff --git a/main.py b/main.py
deleted file mode 100644
index b933241..0000000
--- a/main.py
+++ /dev/null
@@ -1,276 +0,0 @@
-
-# Copyright (C) 2023 littleblack111 <https://github.com/littleblack111/>
-
-import socket
-from os import path
-from sys import exit
-import threading
-
-HOST = '0.0.0.0' # listen on both loopback and local IP address
-PORT = 8000
-
-VERSOIN = "1.2" # Implemented mute, unmute and tempmute, added userlist
-HOME = path.expanduser('~')
-
-swearList = ['fuck', 'shit', 'bitch', 'nigg']
-server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-server_socket.bind((HOST, PORT))
-print(f"Server binded on {HOST} with port {PORT}")
-server_socket.listen()
-
-clients = []
-names = []
-userlist = []
-cmdlist = ['list', 'kick', 'mute', 'unmute', 'tmpmute', 'tempmute']
-sweart1 = []
-sweart2 = []
-muted = []
-
-def stop(self):
- client_thread.join()
- main.join()
- self.running = False
- socket.socket(socket.AF_INET,
- socket.SOCK_STREAM).connect( (self.hostname, self.port))
- self.socket.close()
-
-def broadcast(message):
- for client in clients:
- client.send(message)
- with open(f"{HOME}/scripts/logs/chat.log", "a") as log_file:
- try:
- log_file.write(message.decode())
- except AttributeError:
- log_file.write(message)
- #with open("chat.log", "a") as log_file:
- #log_file.write(message.decode())
-
-def kick(address, kuname, client_socket):
- global faddress
- for c in clients:
- c_address = c.getpeername()[0]
- if c_address == address:
- c.send("You have been kicked\n".encode())
- clients.remove(c)
- c.close()
- names.remove(kuname)
- userlist.remove(kuname)
- if faddress:
- address = userlist[userlist.index(address)-1]
- userlist.remove(address)
- faddress = False
- try:
- muted.remove(kuname)
- except ValueError:
- pass
- client_socket.send(f"Kicked client at {address} Succefully\n".encode())
- try:
- broadcast(f"{address} is kicked")
- except:
- pass
- return
- elif c_address != address and address in userlist:
- for u in userlist:
- if address == u:
- address = userlist[userlist.index(u)-1]
- faddress = True
- continue
- else:
- client_socket.send(f"No client found at {address}\n".encode())
-
-
-def mute(mname: str):
- muted.append(mname)
-
-def unmute(umname: str):
- muted.remove(umname)
- try:
- tplaceholdertmp.cancel()
- except:
- pass
-
-def tempmute(mname: str, time: int):
- global tplaceholdertmp
- mute(mname)
- #threading.Timer(time*60, unmute(name))
- tplaceholdertmp = threading.Timer(time*60, unmute, mname).start()
-
-
-
-def handle_client(client_socket, client_address):
- print(f'New connection from {client_address}')
- if client_address[0] in [c.getpeername()[0] for c in clients]:
- print(f'{client_address} is attempting to connect twice')
- client_socket.send("Your already connected\n".encode())
- client_socket.close()
- return
- clients.append(client_socket)
- userlist.append(client_address[0])
- client_socket.send("Name: ".encode())
- try:
- name = client_socket.recv(1024).decode().replace("\n", "")
- if name:
- if name == "" or " " in name or SwearOrNot(name):
- client_socket.send("Your name have problems\n".encode())
- print(f"{client_address}'s name contains problem")
- clients.remove(client_socket)
- client_socket.close()
- return
- for n in names:
- if n == name:
- snames = " ".join(names)
- client_socket.send(f"Username taken, use an unused name, current names: {snames}\n".encode())
- print(f'{client_address} is trying to use a taken name - {name}')
- clients.remove(client_socket)
- client_socket.close()
- return
- userlist.insert(userlist.index(client_address[0])+1, name)
- print(userlist)
- print(userlist[userlist.index(name)])
- else:
- raise Exception(f"Client '{client_address}' disconnected without registering")
- except Exception as e:
- print(e)
- clients.remove(client_socket)
- print(f'{client_address} has left the chat without registering')
- #broadcast(f'{client_address} has left the chat\n'.encode())
- broadcast(f'{client_address} has left the chat without even registering\n'.encode())
- client_socket.close()
- return
-
- names.append(name)
- #broadcast(f'{client_address} has joined the chat\n'.encode())
- with open(f"{HOME}/scripts/logs/chat.log", "r") as log_file:
- log_contents = log_file.read()
- client_socket.send(log_contents.encode())
- broadcast(f'{name} has joined the chat\n'.encode())
- #with open("chat.log", "r") as log_file:
- # log_contents = log_file.read()
- # client_socket.send(log_contents.encode())
-
- while True:
- try:
- message = client_socket.recv(1024)
- if message and name not in muted:
- rmsg = message.decode()
- if rmsg.startswith("$"): #or client_address[0] == "192.168.1.16" and rmsg.startswith("$"):
- command = rmsg[1:].strip()
- #command_parts = message[1:].strip().split()
- print(f"Command spawned by {client_address},({name}): {command}")
- if command == "list":
- client_socket.send("Connected clients:\n".encode())
- for c in clients:
- c_address = c.getpeername()[0]
- client_socket.send(f"{c_address}\n".encode())
- if client_address[0] == "127.0.0.1":
- if "kick" in command and len(command.split()) == 2:
- kick_address = command.split()[1]
- if kick_address != "127.0.0.1": # and client_address[0] != "192.168.1.16":
- kick(kick_address, userlist[userlist.index(kick_address)+1], client_socket)
- elif "mute" in command and len(command.split()) == 2:
- mute_name = command.split()[1]
- if mute_name != "127.0.0.1":
- mute(mute_name)
- elif "unmute" in command and len(command.split()) == 2:
- unmute_name = command.split()[1]
- if unmute_name in muted:
- print(unmute_name)
- unmute(unmute_name)
- elif command not in cmdlist:
- client_socket.send("Unknown command or syntax \n".encode())
- print(f"{client_address},({name}) send an unknown command: {command}")
- if message.decode() != "" or message.decode() != " ":
- #broadcast(f'{client_address}: {message.decode()}'.encode())
- if SwearOrNot(message.decode()) and client_address[0] != "127.0.0.1":
- broadcast(f'{name}: {len(message.decode())*"#"}\n'.encode())
- if name not in sweart1 and name not in sweart2 and client_address[0] != "127.0.0.1":
- sweart1.append(name)
- print("t1")
- elif name not in sweart2 and name in sweart1 and client_address[0] != "127.0.0.1":
- sweart2.append(name)
- sweart1.remove(name)
- print("t2")
- elif name not in sweart1 and name in sweart2 and client_address[0] != "127.0.0.1":
- tempmute(name, 15)
- print("tmpmute")
- else:
- broadcast(f'{name}: {message.decode()}'.encode())
- print(f'{client_address}({name}): {message.decode()}')
- elif message and name in muted:
- client_socket.send("You have been muted\n".encode())
- else:
- raise Exception(f'Client {name} disconnected')
- except Exception as e:
- print(e)
- clients.remove(client_socket)
- print(f'{client_address} has left the chat')
- #broadcast(f'{client_address} has left the chat\n'.encode())
- broadcast(f'{name} has left the chat\n'.encode())
- client_socket.close()
- names.remove(name)
- userlist.remove(name)
- userlist.remove(client_address[0])
- try:
- muted.remove(name)
- except ValueError:
- pass
- return
-
-def SwearOrNot(var):
- if any(word in var for word in swearList):
- return True
- else:
- return False
-
-
-def accept_connections():
- global client_thread
- while True:
- try:
- client_socket, client_address = server_socket.accept()
- client_thread = threading.Thread(target=handle_client, args=(client_socket, client_address))
- client_thread.start()
- except KeyboardInterrupt:
- print("Closing server...")
- #broadcast("Closing server...")
- #client_thread.join()
- exit()
-
-try:
- if __name__ == '__main__':
- #with open("chat.log", "w") as log_file:
- # log_file.truncate(0)
- with open(f"{HOME}/scripts/logs/chat.log", "w") as log_file:
- log_file.truncate(0)
- try:
- main = threading.Thread(target=accept_connections)
- main.start()
- #client_socket, client_address = server_socket.accept()
- #handle_client(client_socket, client_address)
- except KeyboardInterrupt:
- print("Closing server...")
- #broadcast("Closing server...")
- #client_thread.join()
- exit()
-
- # Handle the server's own connection
- try:
- while True:
- try:
- message = input('> ')
- broadcast(f'{message}\n'.encode())
- except KeyboardInterrupt:
- print("Closing server...")
- #broadcast("Closing server...")
- #client_thread.join()
- exit()
- except KeyboardInterrupt:
- print("Closing server...")
- #broadcast("Closing server...")
- #client_thread.join()
- exit()
-except KeyboardInterrupt:
- print("Closing server...")
- #broadcast("Closing server...")
- #client_thread.join()
- exit()
diff --git a/setup.sh b/setup.sh
deleted file mode 100755
index 952aaa8..0000000
--- a/setup.sh
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/usr/bin/env bash
-# Private Chat Server(PCS) - Simple Python chat server
-# # Copyright (C) 2023 littleblack111 <https://github.com/littleblack111/>
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-set -e
-if ! command -v python /dev/null && ! command -v python3 && ! command -v py; then
- echo "Error, python not found." >&2
- exit 1
-elif ! command -v git; then
- echo "Error, git not found." >&2
-fi
-
-echo "Cloning repository"
-
-git clone https://github.com/littleblack111/chat-server.git
-
-printf "Run server? (y/N) "
-if read -q "choice? "; then
- cd chat-server
- if command -v python > /dev/null; then
- python main.py
- elif command -v python3 > /dev/null; then
- python3 main.py
- elif command -v py > /dev/null; then
- python3 main.py
- fi
-fi
diff --git a/src/FileDescriptor.cpp b/src/FileDescriptor.cpp
new file mode 100644
index 0000000..8592f42
--- /dev/null
+++ b/src/FileDescriptor.cpp
@@ -0,0 +1,89 @@
+#include <cstdlib>
+#include <hyprutils/os/FileDescriptor.hpp>
+#include <fcntl.h>
+#include <sys/poll.h>
+#include <unistd.h>
+#include <utility>
+
+using namespace Hyprutils::OS;
+
+CFileDescriptor::CFileDescriptor(int const fd) : m_fd(fd) {}
+
+CFileDescriptor::CFileDescriptor(CFileDescriptor&& other) : m_fd(std::exchange(other.m_fd, -1)) {}
+
+CFileDescriptor& CFileDescriptor::operator=(CFileDescriptor&& other) {
+ if (this == &other) // Shit will go haywire if there is duplicate ownership
+ abort();
+
+ reset();
+ m_fd = std::exchange(other.m_fd, -1);
+ return *this;
+}
+
+CFileDescriptor::~CFileDescriptor() {
+ reset();
+}
+
+bool CFileDescriptor::isValid() const {
+ return m_fd != -1;
+}
+
+int CFileDescriptor::get() const {
+ return m_fd;
+}
+
+int CFileDescriptor::getFlags() const {
+ return fcntl(m_fd, F_GETFD);
+}
+
+bool CFileDescriptor::setFlags(int flags) {
+ if (fcntl(m_fd, F_SETFD, flags) == -1)
+ return false;
+
+ return true;
+}
+
+int CFileDescriptor::take() {
+ return std::exchange(m_fd, -1);
+}
+
+void CFileDescriptor::reset() {
+ if (m_fd != -1) {
+ close(m_fd);
+ m_fd = -1;
+ }
+}
+
+CFileDescriptor CFileDescriptor::duplicate(int flags) const {
+ if (m_fd == -1)
+ return {};
+
+ return CFileDescriptor{fcntl(m_fd, flags, 0)};
+}
+
+bool CFileDescriptor::isClosed() const {
+ return isClosed(m_fd);
+}
+
+bool CFileDescriptor::isReadable() const {
+ return isReadable(m_fd);
+}
+
+bool CFileDescriptor::isClosed(int fd) {
+ pollfd pfd = {
+ .fd = fd,
+ .events = POLLIN,
+ .revents = 0,
+ };
+
+ if (poll(&pfd, 1, 0) < 0)
+ return true;
+
+ return pfd.revents & (POLLHUP | POLLERR);
+}
+
+bool CFileDescriptor::isReadable(int fd) {
+ pollfd pfd = {.fd = fd, .events = POLLIN, .revents = 0};
+
+ return poll(&pfd, 1, 0) > 0 && (pfd.revents & POLLIN);
+}
diff --git a/src/FileDescriptor.hpp b/src/FileDescriptor.hpp
new file mode 100644
index 0000000..59fb7e9
--- /dev/null
+++ b/src/FileDescriptor.hpp
@@ -0,0 +1,39 @@
+#pragma once
+
+#include <fcntl.h>
+namespace Hyprutils {
+namespace OS {
+class CFileDescriptor {
+ public:
+ CFileDescriptor() = default;
+ explicit CFileDescriptor(int const fd);
+ CFileDescriptor(CFileDescriptor &&);
+ CFileDescriptor &operator=(CFileDescriptor &&);
+ ~CFileDescriptor();
+
+ CFileDescriptor(const CFileDescriptor &) = delete;
+ CFileDescriptor &operator=(const CFileDescriptor &) = delete;
+
+ bool operator==(const CFileDescriptor &rhs) const {
+ return m_fd == rhs.m_fd;
+ }
+
+ bool isValid() const;
+ int get() const;
+ int getFlags() const;
+ bool setFlags(int flags);
+ int take();
+ void reset();
+ CFileDescriptor duplicate(int flags = F_DUPFD_CLOEXEC) const;
+
+ bool isReadable() const;
+ bool isClosed() const;
+
+ static bool isReadable(int fd);
+ static bool isClosed(int fd);
+
+ private:
+ int m_fd = -1;
+};
+}; // namespace OS
+}; // namespace Hyprutils
diff --git a/src/log.hpp b/src/log.hpp
new file mode 100644
index 0000000..5285dc8
--- /dev/null
+++ b/src/log.hpp
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <map>
+#include <string>
+typedef enum {
+ LOG_LEVEL_DEBUG = 0,
+ LOG_LEVEL_INFO,
+ LOG_LEVEL_WARN,
+ LOG_LEVEL_ERROR,
+ LOG_LEVEL_FATAL,
+ LOG_LEVEL_MAX
+} eLogLevel;
+
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..1cf50b5
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,9 @@
+#include "sessionManager.hpp"
+#include "server.hpp"
+
+int main() {
+ g_pServer = std::make_unique<CServer>(8080);
+ g_pSessionManager = std::make_unique<CSessionManager>();
+ g_pSessionManager->enterLoop();
+ return 0;
+}
diff --git a/src/server.cpp b/src/server.cpp
new file mode 100644
index 0000000..f85d1fc
--- /dev/null
+++ b/src/server.cpp
@@ -0,0 +1,50 @@
+#include "server.hpp"
+#include "sessionManager.hpp"
+#include <cstring>
+#include <print>
+#include <sys/socket.h>
+
+CServer::CServer(uint16_t port)
+ : m_sockfd(Hyprutils::OS::CFileDescriptor{socket(AF_INET, SOCK_STREAM, 0)}) {
+ if (!m_sockfd.isValid())
+ throw std::runtime_error("Failed to create socket");
+
+ memset(&m_addr, 0, sizeof(m_addr));
+ m_addr.sin_family = AF_INET;
+ m_addr.sin_port = htons(port);
+ m_addr.sin_addr.s_addr = INADDR_ANY;
+ m_dBind = bind(m_sockfd.get(), reinterpret_cast<sockaddr *>(&m_addr), sizeof(m_addr));
+ m_dListen = listen(m_sockfd.get(), 5);
+ m_dBind < 0 || m_dListen < 0 ? throw std::runtime_error("Failed to bind or listen") : std::println("Server started on port: {}", port);
+
+ // char buffer[1024] = {0};
+ // int clientSocket = accept(m_sockfd.get(), nullptr, nullptr);
+ // recv(clientSocket, buffer, sizeof(buffer), 0);
+ // std::println("Message from client: {}", buffer);
+};
+
+// void CServer::run() {
+// while (true) {
+// char buffer[1024] = {0};
+// int clientSocket = accept(m_sockfd.get(), nullptr, nullptr);
+// recv(clientSocket, buffer, sizeof(buffer), 0);
+// std::println("Message from client: {}", buffer);
+// }
+// }
+
+CServer::~CServer() {
+ m_sockfd.reset();
+}
+
+
+void CServer::log(const eLogLevel loglevel, const std::string &message) const {
+ std::println("[{}]: {}", "a", message);
+}
+
+bool CServer::isValid() {
+ return m_sockfd.isValid() && m_dBind >= 0 && m_dListen >= 0;
+}
+
+Hyprutils::OS::CFileDescriptor CServer::getSocket() const {
+ return m_sockfd.duplicate();
+}
diff --git a/src/server.hpp b/src/server.hpp
new file mode 100644
index 0000000..e100266
--- /dev/null
+++ b/src/server.hpp
@@ -0,0 +1,23 @@
+#pragma once
+
+#include "FileDescriptor.hpp"
+#include "log.hpp"
+#include <netinet/in.h>
+#include <string>
+#include <memory>
+
+class CServer {
+ public:
+ CServer(uint16_t port);
+ ~CServer();
+ void log(const eLogLevel loglevel, const std::string &message) const;
+ bool isValid();
+ Hyprutils::OS::CFileDescriptor getSocket() const;
+
+ private:
+ Hyprutils::OS::CFileDescriptor m_sockfd;
+ sockaddr_in m_addr;
+ int m_dBind = -1;
+ int m_dListen = -1;
+};
+inline std::unique_ptr<CServer> g_pServer;
diff --git a/src/session.cpp b/src/session.cpp
new file mode 100644
index 0000000..da7ebf6
--- /dev/null
+++ b/src/session.cpp
@@ -0,0 +1,112 @@
+#include "session.hpp"
+#include <cmath>
+#include <ostream>
+#include <sys/socket.h>
+#include <print>
+#include <cerrno>
+#include <cstring>
+
+CSession::CSession(Hyprutils::OS::CFileDescriptor sockfd) : m_sockfd(std::move(sockfd)) {
+ if (!m_sockfd.isValid())
+ throw std::runtime_error("Failed to create socket");
+}
+
+CSession::~CSession() {
+ m_sockfd.reset();
+}
+
+void CSession::onConnect() const {
+ std::println("Client connected");
+}
+
+void CSession::onDisconnect() const {
+ std::println("Client disconnected");
+}
+
+void CSession::onErrno() const {
+ std::println("Error: {}", strerror(errno));
+}
+
+void CSession::onRecv(SRecvData &data) const {
+ std::println("Received: {}", data.data, data.dataSize);
+}
+
+void CSession::recvManager() {
+ while (true) {
+ CSession::SRecvData recvData = read();
+ if (!recvData.good) {
+ break;
+ }
+ }
+}
+
+CSession::SRecvData CSession::read() {
+ SRecvData recvData;
+ ssize_t dataSize = recv(m_sockfd.get(), recvData.data, recvData.dataSize, 0);
+ if (dataSize < 0) {
+ recvData.good = false;
+ onErrno();
+ }
+ if (dataSize == 0) {
+ recvData.good = false;
+ onDisconnect();
+ }
+ onRecv(recvData);
+ return recvData;
+}
+
+CSession::SRecvData CSession::read(std::string &msg) {
+ write(msg);
+ SRecvData recvData;
+ ssize_t dataSize = recv(m_sockfd.get(), recvData.data, recvData.dataSize, 0);
+
+ if (dataSize > 0) {
+ recvData.good = false;
+
+ if (dataSize < 0)
+ onErrno();
+
+ if (dataSize == 0)
+ onDisconnect();
+ }
+ onRecv(recvData);
+
+ return recvData;
+}
+
+void CSession::run() {
+ onConnect();
+ // std::jthread recvThread(&CSession::recvManager, this);
+ registerSession();
+ recvManager();
+}
+
+bool CSession::registerSession() {
+ SRecvData rervData = read((std::string&)"Name: ");
+ if (!rervData.good) {
+ return false;
+ }
+ m_name = rervData.data;
+
+ std::println("Client registered as: {}", m_name);
+
+ return true;
+}
+
+bool CSession::isValid() const {
+ return m_sockfd.isValid() && !m_name.empty();
+}
+
+bool CSession::write(const std::string &msg) const {
+ bool bad = false;
+ ssize_t bytes_sent = send(m_sockfd.get(), msg.c_str(), msg.size(), 0);
+ if (bytes_sent < 0) {
+ bad = true;
+ onErrno();
+ }
+ return bad;
+}
+
+std::string CSession::getName() const {
+ return m_name;
+}
diff --git a/src/session.hpp b/src/session.hpp
new file mode 100644
index 0000000..8e0859c
--- /dev/null
+++ b/src/session.hpp
@@ -0,0 +1,32 @@
+#pragma once
+
+#include "FileDescriptor.hpp"
+#include <string>
+
+class CSession {
+ public:
+ struct SRecvData {
+ char data[1024] = {0};
+ ssize_t dataSize = sizeof(data);
+ bool good = true;
+ };
+ CSession(Hyprutils::OS::CFileDescriptor m_sockfd);
+ ~CSession();
+ std::string getName() const;
+ bool isValid() const;
+ SRecvData read();
+ SRecvData read(std::string &msg);
+ bool write(const std::string &msg) const;
+ void run();
+
+ private:
+ Hyprutils::OS::CFileDescriptor m_sockfd;
+ std::string m_name;
+ void recvManager();
+ bool registerSession();
+void onConnect() const;
+void onDisconnect() const;
+void onErrno() const;
+void onRecv(SRecvData &data) const;
+ friend class CSessionManager;
+};
diff --git a/src/sessionManager.cpp b/src/sessionManager.cpp
new file mode 100644
index 0000000..a249f61
--- /dev/null
+++ b/src/sessionManager.cpp
@@ -0,0 +1,29 @@
+#include "sessionManager.hpp"
+#include "session.hpp"
+#include "server.hpp"
+#include <print>
+
+CSessionManager::CSessionManager() = default;
+
+CSessionManager::~CSessionManager() {
+ for (auto &[thread, session] : m_vSessions) {
+ thread.join();
+ }
+}
+
+void CSessionManager::newSession(Hyprutils::OS::CFileDescriptor sockfd) {
+ std::shared_ptr session = std::make_shared<CSession>(std::move(sockfd));
+ m_vSessions.emplace_back(std::jthread(&CSession::run, session), std::move(session));
+}
+
+void CSessionManager::enterLoop() {
+ while (true) {
+ newSession(Hyprutils::OS::CFileDescriptor{accept(g_pServer->getSocket().take(), nullptr, nullptr) /* awaits new client/session */});
+ }
+}
+
+void CSessionManager::broadcast(const std::string &msg) const {
+ for (const auto &[thread, session] : m_vSessions) {
+ session->write(msg);
+ }
+}
diff --git a/src/sessionManager.hpp b/src/sessionManager.hpp
new file mode 100644
index 0000000..0e6eef3
--- /dev/null
+++ b/src/sessionManager.hpp
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "session.hpp"
+#include <memory>
+#include <thread>
+#include <vector>
+
+class CSessionManager {
+public:
+ CSessionManager();
+ ~CSessionManager();
+ void enterLoop();
+ void newSession(Hyprutils::OS::CFileDescriptor sockfd);
+ void broadcast(const std::string &message) const;
+ void kick(const CSession *session) const;
+ void addSession(const CSession &session);
+private:
+ std::vector<std::pair<std::jthread, std::shared_ptr<CSession>>> m_vSessions;
+};
+inline std::unique_ptr<CSessionManager> g_pSessionManager;