Queer European MD passionate about IT

client.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. import argparse
  2. import asyncio
  3. import collections
  4. import logging
  5. # import signal
  6. import os
  7. class Client:
  8. def __init__(self, host='localhost', port=3001,
  9. buffer_chunk_size=10**4, buffer_length_limit=10**4):
  10. self._host = host
  11. self._port = port
  12. self._stopping = False
  13. self.buffer = collections.deque() # Shared queue of bytes
  14. self._buffer_chunk_size = buffer_chunk_size # How many bytes per chunk
  15. self._buffer_length_limit = buffer_length_limit # How many chunks in buffer
  16. self._file_path = None
  17. self._working = False
  18. @property
  19. def host(self) -> str:
  20. return self._host
  21. @property
  22. def port(self) -> int:
  23. return self._port
  24. @property
  25. def stopping(self) -> bool:
  26. return self._stopping
  27. @property
  28. def buffer_length_limit(self) -> int:
  29. return self._buffer_length_limit
  30. @property
  31. def buffer_chunk_size(self) -> int:
  32. return self._buffer_chunk_size
  33. @property
  34. def file_path(self) -> str:
  35. return self._file_path
  36. @property
  37. def working(self) -> bool:
  38. return self._working
  39. async def run_sending_client(self, file_path='~/output.txt'):
  40. self._file_path = file_path
  41. _, writer = await asyncio.open_connection(host=self.host, port=self.port)
  42. await self.send(writer=writer)
  43. async def send(self, writer: asyncio.StreamWriter):
  44. self._working = True
  45. with open(self.file_path, 'rb') as file_to_send:
  46. while not self.stopping:
  47. output_data = file_to_send.read(self.buffer_chunk_size)
  48. if not output_data:
  49. break
  50. writer.write(output_data)
  51. try:
  52. await writer.drain()
  53. except ConnectionResetError:
  54. logging.info('Server closed the connection.')
  55. self.stop()
  56. break
  57. else:
  58. # If transmission has succeeded, write end of file
  59. writer.write_eof()
  60. await writer.drain()
  61. return
  62. async def run_receiving_client(self, file_path='~/input.txt'):
  63. self._file_path = file_path
  64. reader, _ = await asyncio.open_connection(host=self.host, port=self.port)
  65. await self.receive(reader=reader)
  66. async def receive(self, reader: asyncio.StreamReader):
  67. self._working = True
  68. with open(self.file_path, 'wb') as file_to_receive:
  69. while not self.stopping:
  70. input_data = await reader.read(self.buffer_chunk_size)
  71. if reader.at_eof():
  72. break
  73. if not input_data:
  74. continue
  75. file_to_receive.write(input_data)
  76. def stop(self, *_):
  77. if self.working:
  78. logging.info("Received interruption signal, stopping...")
  79. self._stopping = True
  80. else:
  81. raise KeyboardInterrupt("Not working yet...")
  82. def get_action(action):
  83. """Parse abbreviations for `action`."""
  84. if not isinstance(action, str):
  85. return
  86. elif action.lower().startswith('r'):
  87. return 'receive'
  88. elif action.lower().startswith('s'):
  89. return 'send'
  90. def get_file_path(path, action='receive'):
  91. """Check that file `path` is correct and return it."""
  92. if (
  93. isinstance(path, str)
  94. and action == 'send'
  95. and os.path.isfile(path)
  96. ):
  97. return path
  98. elif (
  99. isinstance(path, str)
  100. and action == 'receive'
  101. and os.access(os.path.dirname(os.path.abspath(path)), os.W_OK)
  102. ):
  103. return path
  104. elif path is not None:
  105. logging.error(f"Invalid file: `{path}`")
  106. if __name__ == '__main__':
  107. log_formatter = logging.Formatter(
  108. "%(asctime)s [%(module)-15s %(levelname)-8s] %(message)s",
  109. style='%'
  110. )
  111. root_logger = logging.getLogger()
  112. root_logger.setLevel(logging.DEBUG)
  113. console_handler = logging.StreamHandler()
  114. console_handler.setFormatter(log_formatter)
  115. console_handler.setLevel(logging.DEBUG)
  116. root_logger.addHandler(console_handler)
  117. # Parse command-line arguments
  118. cli_parser = argparse.ArgumentParser(description='Run client',
  119. allow_abbrev=False)
  120. cli_parser.add_argument('--host', type=str,
  121. default=None,
  122. required=False,
  123. help='server address')
  124. cli_parser.add_argument('--port', type=int,
  125. default=None,
  126. required=False,
  127. help='server _port')
  128. cli_parser.add_argument('--action', type=str,
  129. default=None,
  130. required=False,
  131. help='[S]end or [R]eceive')
  132. cli_parser.add_argument('--path', type=str,
  133. default=None,
  134. required=False,
  135. help='File path')
  136. cli_parser.add_argument('others',
  137. metavar='R or S',
  138. nargs='*',
  139. help='[S]end or [R]eceive (see `action`)')
  140. args = vars(cli_parser.parse_args())
  141. _host = args['host']
  142. _port = args['port']
  143. _action = get_action(args['action'])
  144. _file_path = args['path']
  145. # If _host and _port are not provided from command-line, try to import them
  146. if _host is None:
  147. try:
  148. from config import host as _host
  149. except ImportError:
  150. _host = None
  151. if _port is None:
  152. try:
  153. from config import port as _port
  154. except ImportError:
  155. _port = None
  156. # Take `s`, `r` etc. from command line as `_action`
  157. if _action is None:
  158. for arg in args['others']:
  159. _action = get_action(arg)
  160. if _action:
  161. break
  162. if _action is None:
  163. try:
  164. from config import action as _action
  165. _action = get_action(_action)
  166. except ImportError:
  167. _action = None
  168. if _file_path is None:
  169. try:
  170. from config import file_path as _file_path
  171. _file_path = get_action(_file_path)
  172. except ImportError:
  173. _file_path = None
  174. # If import fails, prompt user for _host or _port
  175. while _host is None:
  176. _host = input("Enter _host:\t\t\t\t\t\t")
  177. while _port is None:
  178. try:
  179. _port = int(input("Enter _port:\t\t\t\t\t\t"))
  180. except ValueError:
  181. logging.info("Invalid _port. Enter a valid _port number!")
  182. _port = None
  183. while _action is None:
  184. _action = get_action(
  185. input("Do you want to (R)eceive or (S)end a file?\t\t")
  186. )
  187. while _file_path is None:
  188. _file_path = get_file_path(
  189. path=input(f"Enter file to {_action}:\t\t\t\t\t\t"),
  190. action=_action
  191. )
  192. loop = asyncio.get_event_loop()
  193. client = Client(
  194. host=_host,
  195. port=_port,
  196. )
  197. logging.info("Starting client...")
  198. if _action == 'send':
  199. loop.run_until_complete(
  200. client.run_sending_client(
  201. file_path=_file_path
  202. )
  203. )
  204. else:
  205. loop.run_until_complete(
  206. client.run_receiving_client(
  207. file_path=_file_path
  208. )
  209. )
  210. loop.close()
  211. logging.info("Stopped client")