-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathserver.py
416 lines (328 loc) · 17.5 KB
/
server.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
from socket import *
from threading import Thread
import struct
# Custom Modules
from command import Command
from user import User
from chatroom import Chatroom
import util
class Server:
def __init__(self):
"""
The class that contains server related functionality.
"""
self.listener = socket()
self.address = (gethostname(), 8585)
self.tcp_backlog = 5
self.users = {}
self.userlist = []
self.chatrooms = {"General": Chatroom("General", None, True)}
def listen(self):
"""
Listens for all new traffic and delegates a separate thread for each client.
"""
# Begins listening for a connection
self.listener.bind(self.address)
self.listener.listen(self.tcp_backlog)
# Accepts all new traffic and delegates a thread to be responsible for the new client
while True:
client_sock, origin_address = self.listener.accept()
Thread(target=self.handle_client, args=(origin_address, client_sock)).start()
def handle_client(self, origin_address: (str, int), client_sock: socket):
"""
Handles all commands sent from the client.
"""
while True:
try:
lengthbuf = client_sock.recv(4)
length, = struct.unpack('!I', lengthbuf)
data = client_sock.recv(length)
except: # Should specify the actual exception that is occuring
break
if data is not None:
cmd = Command(data)
self.execute_command(cmd, origin_address, client_sock)
print("{} lost connection".format(self.users[client_sock].alias))
self.userlist.remove(self.users[client_sock].alias)
self.users.pop(client_sock, None)
client_sock.close()
def execute_command(self, cmd: Command, origin_address: (str, int), sock: socket):
"""
Executes a given command and performs an action depending on the command type.
"""
currUser = self.users.get(sock, None)
if cmd.type == 'message':
# Adds a tag that says who authored the command
cmd.creator = currUser.alias
# Notifies user if chatroom doesnt exist
if cmd.specificChatroom not in self.chatrooms:
errorResponse = Command()
errorResponse.init_error("Chatroom {} doesnt exist.".format(cmd.specificChatroom))
errorResponse.send(sock)
return
# Notifies user if their message is too long
if len(cmd.body) > 200:
errorResponse = Command()
errorResponse.init_error("Your message exceeds the 200 character limit.")
errorResponse.send(sock)
return
print('{}/{}: {}'.format(cmd.creator, cmd.specificChatroom, cmd.body))
# Relays the message to all the other clients in the same chatroom that the message was sent from
self.chatrooms[cmd.specificChatroom].send_all(cmd)
elif cmd.type == 'alias':
# Some thoughts about https://github.com/sterlinglaird/SENG-299/issues/53:
# to remove old user in user list, we'd have to search by sock (because that is unique to user, even if alias changes)
# also have to edit cliengui.py, as every time set_alias is executed, a new user is added to GUI user list
# Get the chosen alias and address
alias = cmd.body
# Check that the alias isn't already in use
if alias in self.userlist:
# Send warning back to client
errorResponse = Command()
errorResponse.init_error("Alias '{}' already exist.".format(alias))
errorResponse.send(sock)
return
# Update the server data and the command
newUser = User(alias, sock)
self.users[sock] = newUser
self.chatrooms[util.defaultChatroom].add_user(newUser)
cmd.creator = newUser.alias
self.userlist.append(newUser.alias)
cmd.specificChatroom = util.defaultChatroom
print("Alias '{}' accepted".format(alias))
# let all users in default chatroom know about the connection
self.chatrooms[cmd.specificChatroom].send_all(cmd)
elif cmd.type == 'connect':
# Get the chosen alias and address
address = "{}:{}".format(origin_address[0], origin_address[1])
alias = address
print("Connected with address '{}'".format(address))
# let users know about connection
cmd.send(sock)
elif cmd.type == 'disconnect':
# Close the socket
sock.close()
connectedChatrooms = []
# Remove the user
self.users.pop(sock)
for chatroom in self.chatrooms.values():
chatroom.rem_user(currUser.alias)
connectedChatrooms.append(chatroom.name)
# Adds a tag that says who authored the command
cmd.creator = currUser.alias
print("{} disconnected".format(currUser.alias))
# Relays the message to all the other clients in the same chatrooms as the user who disconnected
for chatroom in connectedChatrooms:
chatroom.send_all(cmd)
elif cmd.type == 'join_chatroom':
userOccupies = self.get_all_chatrooms(currUser)
newChatroom = self.chatrooms.get(cmd.body, None)
if newChatroom in userOccupies:
errorResponse = Command()
errorResponse.init_error("You are already in chatroom '{}'.".format(cmd.body))
errorResponse.send(sock)
return
if newChatroom is None:
errorResponse = Command()
errorResponse.init_error("Chatroom '{}' doesn't exist.".format(cmd.body))
errorResponse.send(sock)
return
# Check if user is blocked from the room they are trying to join
if currUser.alias in self.chatrooms[cmd.body].blocked:
errorResponse = Command()
errorResponse.init_error("You are blocked from joining chatroom '{}'.".format(cmd.body))
errorResponse.send(sock)
return
# Remove user from previous chatrooms
for chatroom in self.chatrooms.values():
chatroom.rem_user(currUser)
# Add user to chatroom
newChatroom.add_user(currUser)
print("{} joined chatroom {}".format(currUser.alias, newChatroom.name))
# Adds a tag that says who authored the command
cmd.creator = currUser.alias
# Notify users in chatroom that user has joined
self.chatrooms[newChatroom.name].send_all(cmd)
# Notify users in old chatrooms that user joined a different one
for chatroom in userOccupies:
chatroom.send_all(cmd)
elif cmd.type == 'create_chatroom':
# Create chatroom if it doesnt already exist, if it does then let user know
if cmd.body in self.chatrooms:
errorResponse = Command()
errorResponse.init_error("Chatroom \"{}\" already exists.".format(cmd.body))
errorResponse.send(sock)
return
else:
self.chatrooms[cmd.body] = Chatroom(cmd.body, currUser)
print("{} created chatroom {}".format(currUser.alias, cmd.body))
# Adds a tag that says who authored the command
cmd.creator = currUser.alias
# Let all users know about the new chatroom
self.send_all(cmd)
elif cmd.type == 'delete_chatroom':
chatroom = self.chatrooms.get(cmd.body, None)
if chatroom is None:
# Send error if chatroom doesnt exist
errorResponse = Command()
errorResponse.init_error("Chatroom \"{}\" doesn't exist.".format(cmd.body))
errorResponse.send(sock)
return
elif chatroom.owner is not currUser:
# Send error if user doesnt own the chatroom
errorResponse = Command()
errorResponse.init_error("Chatroom \"{}\" is not owned by you so you cannot delete it.".format(chatroom.name))
errorResponse.send(sock)
return
# Move all current users in chatroom to default room
userList = list(chatroom.users.values())
for user in userList:
chatroom.rem_user(user)
self.chatrooms[util.defaultChatroom].add_user(user)
self.chatrooms.pop(cmd.body, None)
# Adds a tag that says who authored the command
cmd.creator = currUser.alias
# Let all users know about the deleted chatroom
self.send_all(cmd)
# Let all users know about the joins to default chatroom
for deletedUser in userList:
joinCmd = Command()
joinCmd.init_join_chatroom(util.defaultChatroom)
joinCmd.creator = deletedUser.alias
for user_socket in self.chatrooms[util.defaultChatroom].users.values():
joinCmd.send(user_socket.socket)
print("{} deleted chatroom {}".format(currUser.alias, cmd.body))
elif cmd.type == 'block_user':
# Find location of blocker
for blocker_chatroom in self.chatrooms:
# When the blocker is found
if currUser.alias in self.chatrooms[blocker_chatroom].users:
user_location = self.chatrooms[blocker_chatroom]
# Check if they are the owner of the room they're in
if user_location.owner is not currUser:
errorResponse = Command()
errorResponse.init_error("You don't own chatroom {}, so you can't block users from joining it.".format(user_location.name))
errorResponse.send(sock)
return
# If they are the owner
else:
# Find the location of the user being blocked
for chatroom in self.chatrooms:
if cmd.body in self.chatrooms[chatroom].users:
blocked_user_location = self.chatrooms[chatroom]
blocked_user = blocked_user_location.users[cmd.body]
# Check if the user is trying to block themselves (it should have been a feature, but sterlinglaird is lame)
if currUser == blocked_user:
errorResponse = Command()
errorResponse.init_error("Why are you trying to block yourself? Stop that.")
errorResponse.send(sock)
return
# Block the user
user_location.block_user(blocked_user)
# Add a tag that says who authored the command
cmd.creator = currUser.alias
# Add a tag that says which room the user is blocked from
cmd.specificChatroom = user_location.name
# Let all users in the blocker's room know about the block
user_location.send_all(cmd)
# If the blocker and the user being blocked are in the same room
if user_location == blocked_user_location:
# Remove the blocked user from the room, and return them to Default
user_location.rem_user(blocked_user)
self.chatrooms[util.defaultChatroom].add_user(blocked_user)
else:
# Let the blocked user's room know about the block (console logging only)
blocked_user_location.send_all(cmd)
# Let user know they've been forced into default chatroom
join_cmd = Command()
join_cmd.init_join_chatroom(util.defaultChatroom)
join_cmd.creator = blocked_user.alias
# Let all users in new chatroom know that user has joined
self.chatrooms[util.defaultChatroom].send_all(join_cmd)
# Let all users in old chatroom know that user has left, as long as we havent already sent the message in the line above
if blocked_user_location is not self.chatrooms[util.defaultChatroom]:
blocked_user_location.send_all(join_cmd)
print("{} blocked {} from chatroom {}".format(currUser.alias, cmd.body, user_location.name))
return
# If the user can't be found
errorResponse = Command()
errorResponse.init_error("User \"{}\" does not exist.".format(cmd.body))
errorResponse.send(sock)
return
elif cmd.type == 'unblock_user':
# Find location of unblocker
for unblocker_chatroom in self.chatrooms:
# When the unblocker is found
if currUser.alias in self.chatrooms[unblocker_chatroom].users:
user_location = self.chatrooms[unblocker_chatroom]
# Check if they are the owner of the room they're in
if user_location.owner is not currUser:
errorResponse = Command()
errorResponse.init_error("You are not the owner of chatroom {}, so you cannot unblock blocked users.".format(user_location.name))
errorResponse.send(sock)
return
# If they are the owner
else:
# Find the location of the user being unblocked
for chatroom in self.chatrooms:
if cmd.body in self.chatrooms[chatroom].users:
blocked_user_location = self.chatrooms[chatroom]
blocked_user = blocked_user_location.users[cmd.body]
# unlock the user
user_location.unblock_user(blocked_user)
print("{} unblocked {} from chatroom {}".format(currUser.alias, cmd.body, user_location.name))
# Add a tag that says who authored the command
cmd.creator = currUser.alias
# Add a tag that says which room the user is unblocked from
cmd.specificChatroom = user_location.name
# Let all users in the room know about the unblock
user_location.send_all(cmd)
if user_location != blocked_user_location:
# Let the blocked user's room know about the block
blocked_user_location.send_all(cmd)
return
# If the user can't be found
errorResponse = Command()
errorResponse.init_error("User \"{}\" does not exist.".format(cmd.body))
errorResponse.send(sock)
return
elif cmd.type == 'get_chatrooms':
get_chatrooms_cmd = Command()
get_chatrooms_cmd.init_get_chatrooms(list(self.chatrooms.keys()))
get_chatrooms_cmd.send(sock)
elif cmd.type == 'list_users':
newChatroom = self.chatrooms.get(cmd.body, None)
# send a join command for every user in the chatroom, except the current user
for user in newChatroom.users:
if user == currUser.alias:
continue
responseCmd = Command()
responseCmd.init_join_chatroom(newChatroom.name)
responseCmd.creator = user
responseCmd.suppress = True
responseCmd.send(sock)
print("Listing user '{}' for '{}'".format(user, currUser.alias))
def get_all_chatrooms(self, user: User):
'''
Returns all chatrooms a user belongs to
'''
occupies = []
for chatroom in self.chatrooms.values():
if chatroom.users.get(user.alias, None):
occupies.append(chatroom)
return occupies
def send_all(self, cmd: Command):
'''
Sends a command to all users
'''
for user_socket in list(self.users):
try:
cmd.send(user_socket)
except:
print("{} lost connection".format(self.users[user_socket].alias))
self.userlist.remove(self.users[user_socket].alias)
self.users.pop(user_socket, None)
if __name__ == '__main__':
server = Server()
print("Starting Server")
server.listen()