-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgui_chat.py
225 lines (194 loc) · 8.45 KB
/
gui_chat.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
import argparse
import asyncio
import contextlib
import logging.config
import os
import socket
import sys
from datetime import datetime
from tkinter import messagebox
import aionursery
from aiofile import AIOFile
from async_timeout import timeout
from core import gui
from core.chat_reader import read_stream_chat, save_messages
from core.chat_tool import (
ReadConnectionStateChanged,
SendingConnectionStateChanged,
register,
send_watchdog_messages,
NicknameReceived,
get_open_connection_tools
)
from core.chat_writer import InvalidToken, authorise, write_stream_chat
@contextlib.asynccontextmanager
async def create_handy_nursery():
try:
async with aionursery.Nursery() as nursery:
yield nursery
except aionursery.MultiError as e:
if len(e.exceptions) == 1:
raise e.exceptions[0]
raise
async def read_connection(
reader, messages_queue, history_queue, watchdog_queue, history_log_path
):
async with contextlib.AsyncExitStack() as stack:
nursery = await stack.enter_async_context(create_handy_nursery())
nursery.start_soon(
read_stream_chat(reader, messages_queue, history_queue, watchdog_queue))
nursery.start_soon(
save_messages(history_log_path, history_queue)
)
async def send_connection(writer, reader, watchdog_queue, sending_queue):
async with create_handy_nursery() as nursery:
nursery.start_soon(
write_stream_chat(writer, sending_queue, watchdog_queue)
)
nursery.start_soon(
send_watchdog_messages(writer, reader, watchdog_queue)
)
async def watch_for_connection(watchdog_queue):
logger = logging.getLogger('watchdog_logger')
while True:
current_timestamp = datetime.now().timestamp()
try:
async with timeout(5):
message = await watchdog_queue.get()
logger.info(f'[{current_timestamp}] {message}')
# because context manager doesn't work
except asyncio.TimeoutError:
logger.info(f'[{current_timestamp}] 5s timeout is elapsed')
raise ConnectionError
async def handle_connection(host, read_port, send_port, messages_queue, history_queue, watchdog_queue, sending_queue,
status_updates_queue, token, attempts,
history_log_path):
while True:
async with contextlib.AsyncExitStack() as stack:
status_updates_queue.put_nowait(SendingConnectionStateChanged.INITIATED)
status_updates_queue.put_nowait(ReadConnectionStateChanged.INITIATED)
(reader, _) = await stack.enter_async_context(get_open_connection_tools(
host, read_port, attempts)
)
(write_reader, write_writer) = await stack.enter_async_context(get_open_connection_tools(
host, send_port, attempts)
)
status_updates_queue.put_nowait(SendingConnectionStateChanged.ESTABLISHED)
status_updates_queue.put_nowait(ReadConnectionStateChanged.ESTABLISHED)
try:
if token:
nickname = await authorise(write_reader, write_writer, token, watchdog_queue)
msg = f'Выполнена авторизация. Пользователь {nickname}.'
logging.debug(msg)
else:
user_data = await register(write_reader, write_writer)
nickname = user_data.get('nickname')
status_updates_queue.put_nowait(NicknameReceived(nickname))
async with create_handy_nursery() as nursery:
nursery.start_soon(
read_connection(reader, messages_queue, history_queue, watchdog_queue, history_log_path)
)
nursery.start_soon(
send_connection(write_writer, write_reader, watchdog_queue, sending_queue)
)
nursery.start_soon(
watch_for_connection(watchdog_queue)
)
except (
socket.gaierror,
ConnectionRefusedError,
ConnectionResetError,
ConnectionError,
):
status_updates_queue.put_nowait(SendingConnectionStateChanged.CLOSED)
status_updates_queue.put_nowait(ReadConnectionStateChanged.CLOSED)
continue
except InvalidToken:
messagebox.showinfo("Неверный токен", "Проверьте токен, сервер не узнал его")
raise
break
def setup_loggers():
main_logger = logging.getLogger('')
main_logger.setLevel(logging.DEBUG)
watchdog_logger = logging.getLogger('watchdog_logger')
watchdog_logger.setLevel(logging.INFO)
def create_parser_for_user_arguments():
parser = argparse.ArgumentParser()
parser.add_argument('--host', required=False,
help='chat host',
type=str)
parser.add_argument('--read_port', required=False,
help='chat port for reading messages',
type=int)
parser.add_argument('--send_port', required=False,
help='chat port for sending messages',
type=int)
parser.add_argument('--history', required=False,
help='history log dir path',
type=str)
parser.add_argument('--attempts', required=False,
help='connect attempts before timeout',
type=str)
parser.add_argument('--token', required=False,
help='user token',
type=str)
parser.add_argument('--token_file_path', required=False,
help='file with token path',
type=str)
namespace = parser.parse_args()
return namespace
async def get_token_from_file(token_file_path=None):
if not token_file_path:
token_file_path = './token.txt'
if not os.path.exists(token_file_path):
return
async with AIOFile(token_file_path) as token_file:
token = await token_file.read()
return token
async def main():
setup_loggers()
user_arguments = create_parser_for_user_arguments()
history_log_path = user_arguments.history or os.getenv('HISTORY_LOG_DIR_PATH', f'{os.getcwd()}')
if not os.path.exists(history_log_path):
logging.error(f'history log path does not exist {history_log_path}')
sys.exit(2)
token_file_path = user_arguments.token_file_path or os.getenv('TOKEN_FILE_PATH')
if user_arguments.token_file_path and not os.path.exists(user_arguments.token_file_path):
logging.error(f'token file path does not exist {user_arguments.token_file_path}')
sys.exit(2)
elif user_arguments.token_file_path:
token_file_path = user_arguments.token_file_path
token_from_file = await get_token_from_file(token_file_path)
host = user_arguments.host or os.getenv('HOST', 'minechat.dvmn.org')
read_port = user_arguments.read_port or os.getenv('READ_PORT', 5000)
send_port = user_arguments.send_port or os.getenv('SEND_PORT', 5050)
attempts = int(user_arguments.attempts or os.getenv('ATTEMPTS_COUNT', 3))
token = user_arguments.token or os.getenv('TOKEN') or token_from_file
messages_queue = asyncio.Queue()
sending_queue = asyncio.Queue()
status_updates_queue = asyncio.Queue()
history_queue = asyncio.Queue()
watchdog_queue = asyncio.Queue()
if os.path.exists(f'{history_log_path}/history_logs.txt'):
with open(f'{history_log_path}/history_logs.txt') as log_file:
messages_queue.put_nowait(log_file.read())
async with create_handy_nursery() as nursery:
nursery.start_soon(
gui.draw(messages_queue, sending_queue, status_updates_queue)
)
nursery.start_soon(
handle_connection(host, read_port, send_port, messages_queue, history_queue,
watchdog_queue, sending_queue,
status_updates_queue,
token, attempts, history_log_path)
)
if __name__ == '__main__':
try:
asyncio.run(main())
except (
KeyboardInterrupt,
gui.TkAppClosed,
InvalidToken,
ConnectionError
):
exit()