summaryrefslogtreecommitdiffstats
path: root/server/src/request_handler.py
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/request_handler.py')
-rw-r--r--server/src/request_handler.py205
1 files changed, 180 insertions, 25 deletions
diff --git a/server/src/request_handler.py b/server/src/request_handler.py
index 9d820e4..458c554 100644
--- a/server/src/request_handler.py
+++ b/server/src/request_handler.py
@@ -1,5 +1,7 @@
"""RequestHandler."""
+import bisect
+import difflib
import log
from users_text_manager import AUTHORITY
@@ -10,19 +12,145 @@ class JSON_TOKEN: # pylint:disable=W0232
"""Enumeration the Ttken strings for json object."""
BYE = 'bye' # Resets the user and do nothong.
CURSORS = 'cursors' # other users' cursor position
+ DIFF = 'diff' # Difference between this time and last time.
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
+
+
+def apply_patch(orig_lines, patch_info):
+ """Applies a patch.
+
+ Args:
+ orig_lines: Original lines of the text.
+ patch_info: A list of replacing information.
+
+ Return:
+ A list of text.
+ """
+ new_lines, done_len = [], 0
+ for beg, end, lines in patch_info:
+ new_lines += orig_lines[done_len : beg]
+ new_lines += lines
+ done_len = end
+ return new_lines + orig_lines[done_len : ]
+
+
+def _squash_patch(patch_info):
+ """Squash list of replacing information to a smaller mount of information.
+
+ Args:
+ patch_info: Information of patches.
+
+ Return:
+ A list of replacing information.
+ """
+ ret, index = [], 0
+ while index < len(patch_info):
+ lines = patch_info[index][2]
+ index2 = index + 1
+ while index2 < len(patch_info) and \
+ patch_info[index2 - 1][1] >= patch_info[index2][0]:
+ lines += patch_info[index2][2]
+ index2 += 1
+ ret.append((patch_info[index][0], patch_info[index2 - 1][1], lines))
+ index = index2
+ return ret
+
+
+def gen_patch(orig_lines, new_lines):
+ """Creates a patch from two lines text.
+
+ Args:
+ orig_lines: Original lines of the text.
+ new_lines: New lines of the text.
+
+ Return:
+ A list of replacing information.
+ """
+ diff_result = list(difflib.Differ().compare(orig_lines, new_lines))
+ orig_index, ret = 0, []
+ for line in diff_result:
+ if line.startswith(' '):
+ orig_index += 1
+ elif line.startswith('+ '):
+ ret.append((orig_index, orig_index, [line[2 : ]]))
+ elif line.startswith('- '):
+ ret.append((orig_index, orig_index + 1, []))
+ orig_index += 1
+ return _squash_patch(ret)
+
+
+class _CursorTransformer(object):
+ """Transformer for format of the cursor position.
+
+ In vim, it use (row, col) to represent an cursor's position.
+ In UsersTextManager, it use numerical notation (byte distance between the
+ first byte in the text).
+
+ Attributes:
+ _sum_len: Sum of each row.
+ """
+ def __init__(self):
+ """Constructor."""
+ self._sum_len = [0]
+
+ def update_lines(self, lines):
+ """Update lines of text.
+
+ Args:
+ lines: Lines of text.
+ """
+ self._sum_len = [0]
+ for line in lines:
+ delta = len(line) + 1 # "+ 1" is for newline char
+ self._sum_len.append(self._sum_len[-1] + delta)
+
+ def rcs_to_nums(self, rcs):
+ """Transform row-col format's cursor position to numerical type.
+
+ Args:
+ lines: List of line text.
+ rcs: List of tuple of row-col format cursor postions
+
+ Return:
+ A list of numerical cursor positions.
+ """
+ ret = []
+ for rc in rcs:
+ base = self._sum_len[min(rc[0], len(self._sum_len) - 1)]
+ ret.append(min(base + rc[1], self._sum_len[-1]))
+ return ret
+
+
+ def nums_to_rcs(self, nums):
+ """Transform numerical format's cursor position to row-col format.
+
+ Args:
+ lines: List of line text.
+ nums: A list of numerical cursor positions.
+
+ Return:
+ List of tuple of row-col format cursor postions
+ """
+ ans, rmin = {}, 0
+ for num in sorted(nums):
+ row = bisect.bisect(self._sum_len, num, lo=rmin) - 1
+ col = (num - self._sum_len[row]) if row < len(self._sum_len) else 0
+ rmin = row + 1
+ ans[num] = (row, col)
+ return [ans[num] for num in nums]
+
class RequestHandler(object):
"""Handles all kinds of request.
Attributes:
_users_text_manager: An instance of UsersTextManager.
+ _cursor_transformer: An instance of _CursorTransformer.
"""
def __init__(self, users_text_manager):
"""Constructor.
@@ -32,6 +160,7 @@ class RequestHandler(object):
"""
super(RequestHandler, self).__init__()
self._users_text_manager = users_text_manager
+ self._cursor_transformer = _CursorTransformer()
def handle(self, request):
"""Handles the request and returns the response.
@@ -74,41 +203,68 @@ class RequestHandler(object):
identity: The identity of that user.
request: The request from that user.
"""
- if all(key in request for key in [JSON_TOKEN.INIT,
- JSON_TOKEN.TEXT,
- JSON_TOKEN.MODE,
- JSON_TOKEN.CURSORS]):
+ if all(key in request for key in [JSON_TOKEN.INIT, JSON_TOKEN.DIFF,
+ JSON_TOKEN.MODE, JSON_TOKEN.CURSORS]):
log.info('handle sync-request from %r\n' % identity)
self._check_init(identity, request)
self._check_authority(identity, request)
+ lines = apply_patch(
+ self._users_text_manager.get_user_text(identity).split('\n'),
+ request[JSON_TOKEN.DIFF])
+ self._cursor_transformer.update_lines(lines)
+ cursors = dict(zip(request[JSON_TOKEN.CURSORS].keys(),
+ self._cursor_transformer.rcs_to_nums(
+ request[JSON_TOKEN.CURSORS].values())))
new_user_info, new_text = self._users_text_manager.update_user_text(
identity,
- UserInfo(mode=request[JSON_TOKEN.MODE],
- cursors=request[JSON_TOKEN.CURSORS]),
- request[JSON_TOKEN.TEXT])
- return self._pack_sync_response(identity, new_user_info, new_text)
+ UserInfo(mode=request[JSON_TOKEN.MODE], cursors=cursors),
+ '\n'.join(lines))
+ return self._pack_sync_response(
+ identity, new_user_info, new_text.split('\n'), lines)
- def _pack_sync_response(self, identity, user_info, text):
+ def _pack_sync_response(self, identity, user_info, lines, old_lines):
"""Packs the response for the sync request by the result from manager.
Args:
identity: Identity of that user.
user_info: Informations of that user.
- text: New text.
+ lines: New lines of text.
+ old_lines: Old lines of text.
+
+ Return:
+ The response json object.
+ """
+ self._cursor_transformer.update_lines(lines)
+ return {
+ JSON_TOKEN.DIFF : gen_patch(old_lines, lines),
+ JSON_TOKEN.CURSORS : dict(zip(
+ user_info.cursors.keys(),
+ self._cursor_transformer.nums_to_rcs(
+ user_info.cursors.values()))),
+ JSON_TOKEN.MODE : user_info.mode,
+ JSON_TOKEN.OTHERS : self._pack_sync_others_response(identity)
+ }
+
+ def _pack_sync_others_response(self, identity):
+ """Packs the response information for other users.
+
+ Args:
+ identity: Identity of that user.
Return:
The response json object.
"""
- return {JSON_TOKEN.TEXT : text,
- JSON_TOKEN.CURSORS : user_info.cursors,
- JSON_TOKEN.MODE : 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()
- ]}
+ return [
+ {
+ JSON_TOKEN.NICKNAME : other.nick_name,
+ JSON_TOKEN.MODE : other.mode,
+ JSON_TOKEN.CURSORS: dict(zip(
+ other.cursors.keys(),
+ self._cursor_transformer.nums_to_rcs(
+ other.cursors.values())))
+ } for iden, other in self._users_text_manager.get_users_info(
+ without=[identity], must_online=True).items()
+ ]
def _check_init(self, identity, request):
"""Checks whether that user should be initialize or not.
@@ -122,9 +278,9 @@ class RequestHandler(object):
if request[JSON_TOKEN.INIT]:
log.info('Init the user %r\n' % identity)
self._users_text_manager.reset_user(identity)
- request[JSON_TOKEN.TEXT] = ''
+ request[JSON_TOKEN.DIFF] = []
for mark in request.get(JSON_TOKEN.CURSORS, []):
- request[JSON_TOKEN.CURSORS][mark] = 0
+ request[JSON_TOKEN.CURSORS][mark] = (0, 0)
def _check_authority(self, identity, request):
"""Checks the authroity and updates the request.
@@ -138,5 +294,4 @@ class RequestHandler(object):
"""
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
+ request[JSON_TOKEN.DIFF] = []