-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathclient.py
167 lines (146 loc) · 5.99 KB
/
client.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
import asyncio, json, argparse
from sys import stdout
class Client(asyncio.Protocol):
def __init__(self, user: str, message: str, on_con_lost: asyncio.Future, loop: asyncio.AbstractEventLoop):
"""
Client holds info about:
name, last sent message, event loop, timeouted messages.
"""
self.message = message
self.on_con_lost = on_con_lost
self.user = user
self.last_message = ""
self.loop = loop
self.timeouts = []
def connection_made(self, transport):
"""
Connection to server with basic message
"""
self.sockname = transport.get_extra_info("sockname")
self.transport = transport
msg = {"author": self.user, "event": "init", "content": "back to the server"}
self.transport.write((bytes(json.dumps(msg), encoding= "utf-8")))
def connection_lost(self, exc):
"""
Disconnect from the server.
Set Future object self.on_con_lost to close transport connection
"""
print('The server closed the connection')
self.on_con_lost.set_result(True)
def data_received(self, data: bytes):
"""
Data receive handler with callback to process_message function
"""
if data:
message = json.loads(data.decode())
self.process_message(message)
def process_message(self, message: str|list|dict):
"""
Message processing callback.
1. Checks event-type of message to generate message style
2. Filters messages from the same author as self.user
3. Ready to work with lists of jsons to save ordering of history messages
4. Callback parsed message to self.output
"""
if isinstance(message, dict):
match message["event"]:
case "message":
data = "{timestamp} | {author}: {content}".format(**message)
case "servermsg":
data = "{timestamp} | {author} {content}".format(**message)
case "direct":
data = f'{message["timestamp"]} | DIRECT {message["author"]}->{self.user}: {message["content"]}'
case _:
data = "{timestamp} | {author}: {content}".format(**message)
if message["author"] == self.user:
data = f'{message["content"]}'
stdout.write(data + '\n')
else:
review = "{content}".format(**message)
self.output(data.strip(), review.strip())
elif len(list(message)) > 0:
history = list(message)
for m in history:
self.process_message(m)
def send(self, data: str|list|dict):
"""
Parses message before sending it to the server.
Options:
1) message to all;
2) direct message to user;
3) timeout sending or kill TimerHandling objects on client side.
"""
if data and self.user:
self.last_message = data
if data.startswith("direct:"):
msg = {"author": self.user, "event": "direct", "content": data.replace("direct:", "")}
else:
msg = {"author": self.user, "event": "message", "content": data}
delay = False
cont = msg["content"].split(" ")
for i in range(len(cont)):
if cont[i].startswith("timeout:"):
delay = True
if cont[i].endswith("kill"):
for handler in self.timeouts:
handler.cancel()
self.timeouts.clear()
print("All timeouts have been cancelled.")
break
else:
timeout = int(cont[i].replace("timeout:", ""))
msg["content"] = " ".join(msg["content"].replace(cont[i], "", 1).split())
handler = self.loop.call_later(timeout, self.write, msg)
self.timeouts.append(handler)
if not delay:
self.write(msg)
def write(self, msg: str|list|dict):
"""
Callback func that writes to the server.
"""
self.transport.write(bytes(json.dumps(msg), encoding = "utf-8"))
async def getmsgs(self, loop: asyncio.AbstractEventLoop):
"""
Async Task for always available user input-line in console.
Solves the problem of blocked input while reading chat.
"""
self.output = self.stdoutput
self.output("Connected to {0}:{1}\n".format(*self.sockname), "")
while True:
msg = await loop.run_in_executor(None, input)
if msg != "":
self.send(msg)
def stdoutput(self, data: str|list|dict, review: str|list|dict):
"""
Writes last message to the client output if it's not ours.
"""
if self.last_message == review:
return
else:
stdout.write(data + '\n')
async def main():
"""
Runs with console args: user, addr, port.
Runs Client in current event loop.
Runs input Task in current event loop.
Awaits until connection is lost.
"""
loop = asyncio.get_running_loop()
on_con_lost = loop.create_future()
message = args["user"] + "connected"
client = Client(args["user"], message, on_con_lost, loop)
transport, protocol = await loop.create_connection(
lambda: client,
args["addr"], args["port"])
loop.create_task(client.getmsgs(loop))
try:
await on_con_lost
finally:
transport.close()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Client settings")
parser.add_argument("--user", default="User", type=str)
parser.add_argument("--addr", default="127.0.0.1", type=str)
parser.add_argument("--port", default=8000, type=int)
args = vars(parser.parse_args())
asyncio.run(main())