summaryrefslogtreecommitdiffstats
path: root/server/src/tcp_server.py
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/tcp_server.py')
-rw-r--r--server/src/tcp_server.py254
1 files changed, 254 insertions, 0 deletions
diff --git a/server/src/tcp_server.py b/server/src/tcp_server.py
new file mode 100644
index 0000000..9aaf8fe
--- /dev/null
+++ b/server/src/tcp_server.py
@@ -0,0 +1,254 @@
+"""TCP Server."""
+
+import log
+import select
+import socket
+import threading
+import time
+
+from json_package import JSONPackage
+from json_package import JSONPackageError
+from users_text_manager import AUTHORITY
+from users_text_manager import UserInfo
+
+
+FREQUENCY = 8
+
+TIMEOUT = 5
+
+
+class _JSON_TOKEN: # pylint:disable=W0232
+ """Enumeration the Ttken strings for json object."""
+ CURSORS = 'cursors' # other users' cursor position
+ ERROR = 'error' # error string
+ IDENTITY = 'identity' # identity of myself
+ INIT = 'init' # initialize connect flag
+ MODE = 'mode' # vim mode.
+ NICKNAME = 'nickname' # nick name of the user.
+ OTHERS = 'others' # other users info.
+ TEXT = 'text' # text content in the buffer
+
+
+class TCPServer(threading.Thread):
+ """A thread to be the tcp server.
+
+ Attributes:
+ _port: Port number.
+ _sock: Socket fd.
+ _users_text_manager: An instance of UsersTextManager.
+ _stop_flag: Flag for stopping.
+ _connection_handler_threads: List of connction handler threads.
+ """
+ def __init__(self, port, users_text_manager):
+ """Constructor.
+
+ Args:
+ port: Port number.
+ users_text_manager: An instance of UsersTextManager.
+ """
+ super(TCPServer, self).__init__()
+ self._port = port
+ self._sock = None
+ self._users_text_manager = users_text_manager
+ self._stop_flag = False
+ self._connection_handler_threads = []
+
+ @property
+ def port(self):
+ """Gets the port of this server. None for unconnected case."""
+ return self._port if self._sock else None
+
+ def run(self):
+ """Runs the thread."""
+ self._build()
+ self._accept()
+
+ def stop(self):
+ """Stops the thread."""
+ self._stop_flag = True
+ for thr in self._connection_handler_threads:
+ thr.join()
+
+ def _build(self):
+ """Creates the socket."""
+ timeout = 1
+ while not self._stop_flag and not self._sock:
+ try:
+ self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self._sock.bind(('', self._port))
+ self._sock.listen(1024)
+ except socket.error as e:
+ self._sock = None
+ log.error(str(e) + '\n')
+ log.info('Try it %d second(s) later.\n' % timeout)
+ for _ in range(timeout * FREQUENCY):
+ if self._stop_flag:
+ break
+ time.sleep(float(1) / FREQUENCY)
+ timeout *= 2
+ if self._sock:
+ log.info('Successfully built the tcp server.\n')
+
+ def _accept(self):
+ """Accepts the connection and calls the handler."""
+ while not self._stop_flag:
+ readable, _, _ = select.select([self._sock], [], [],
+ float(1) / FREQUENCY)
+ if readable:
+ sock, addr = self._sock.accept()
+ log.info('Client %r connect to server.\n' % str(addr))
+ thr = _TCPConnectionHandler(sock, self._users_text_manager)
+ thr.start()
+ self._connection_handler_threads += [thr]
+
+
+class TCPConnection(object):
+ """My custom tcp connection.
+
+ Args:
+ _conn: The TCP-connection.
+ """
+ def __init__(self, conn):
+ """Constructor.
+
+ Args:
+ conn: TCP-connection.
+ """
+ self._conn = conn
+ self._conn.settimeout(TIMEOUT)
+
+ def send(self, data):
+ """Sends the data until timeout or the socket closed.
+
+ Args:
+ data: Data to be sent.
+ """
+ self._conn.sendall(data)
+
+ def recv(self, nbyte):
+ """Receives the data until timeout or the socket closed.
+
+ Args:
+ nbyte: Bytes of data to receive.
+
+ Return:
+ Bytes of data.
+ """
+ ret = b''
+ while nbyte > 0:
+ recv = self._conn.recv(nbyte)
+ if not recv:
+ raise socket.error('Connection die.')
+ ret += recv
+ nbyte -= len(recv)
+ return ret
+
+ def close(self):
+ """Closes the connection."""
+ self._conn.close()
+
+
+class _TCPConnectionHandler(threading.Thread):
+ """A thread to handle a connection.
+
+ Attributes:
+ _sock: The connection socket.
+ _users_text_manager: An instance of UsersTextManager.
+ """
+ def __init__(self, sock, users_text_manager):
+ """Constructor.
+
+ Args:
+ sock: The connection socket.
+ users_text_manager: An instance of UsersTextManager.
+ """
+ super(_TCPConnectionHandler, self).__init__()
+ self._sock = TCPConnection(sock)
+ self._users_text_manager = users_text_manager
+
+ def run(self):
+ """Runs the thread."""
+ try:
+ json_package = self._receive()
+ json_info = self._sanitize(json_package.content)
+ if json_info:
+ self._handle(json_info)
+ else:
+ self._send({_JSON_TOKEN.ERROR : 'Invalid client.'})
+ except JSONPackageError as e:
+ log.error(str(e))
+ except socket.error as e:
+ log.error(str(e))
+ self._sock.close()
+
+ def _sanitize(self, request):
+ """Sanitizes the request.
+
+ Args:
+ request: The request package.
+
+ Return:
+ Sanitized package.
+ """
+ identity = request[_JSON_TOKEN.IDENTITY]
+ if identity not in self._users_text_manager.get_users_info():
+ return None
+ if request[_JSON_TOKEN.INIT]:
+ self._users_text_manager.reset_user(identity)
+ request[_JSON_TOKEN.TEXT] = ''
+ for mark in request[_JSON_TOKEN.CURSORS]:
+ request[_JSON_TOKEN.CURSORS][mark] = 0
+ else:
+ auth = self._users_text_manager.get_users_info()[identity].authority
+ if auth < AUTHORITY.READWRITE:
+ old_text = self._users_text_manager.get_user_text(identity)
+ request[_JSON_TOKEN.TEXT] = old_text
+ return request
+
+ def _handle(self, request):
+ """Handles the request.
+
+ Args:
+ request: The request package.
+ """
+ identity = request[_JSON_TOKEN.IDENTITY]
+ text = request[_JSON_TOKEN.TEXT]
+ user_info = UserInfo()
+ user_info.mode = request[_JSON_TOKEN.MODE]
+ user_info.cursors = request[_JSON_TOKEN.CURSORS]
+ new_user_info, new_text = self._users_text_manager.update_user_text(
+ identity, user_info, text)
+ response = JSONPackage()
+ response.content = {
+ _JSON_TOKEN.TEXT : new_text,
+ _JSON_TOKEN.CURSORS : new_user_info.cursors,
+ _JSON_TOKEN.MODE : new_user_info.mode,
+ _JSON_TOKEN.OTHERS : [
+ {_JSON_TOKEN.NICKNAME : other.nick_name,
+ _JSON_TOKEN.MODE : other.mode,
+ _JSON_TOKEN.CURSORS: other.cursors}
+ for iden, other in self._users_text_manager.get_users_info(
+ without=[identity], must_online=True).items()]
+ }
+ response.send_to(self._sock)
+
+
+ def _receive(self):
+ """Receive a request.
+
+ Return:
+ The request package.
+ """
+ request = JSONPackage()
+ request.recv_from(self._sock)
+ return request
+
+ def _send(self, pkg):
+ """Sends a response.
+
+ Args:
+ pkg: The package to be sent.
+ """
+ response = JSONPackage()
+ response.content = pkg
+ response.send_to(self._sock)