|
@@ -8,11 +8,24 @@ import time
|
|
|
|
|
|
from tornado.ioloop import IOLoop
|
|
|
from tornado.websocket import WebSocketHandler
|
|
|
+from enum import IntEnum
|
|
|
|
|
|
-acquireLockMessageType = 127
|
|
|
-releaseLockMessageType = 126
|
|
|
-requestInitializedContentMessageType = 125
|
|
|
-putInitializedContentMessageType = 124
|
|
|
+## The y-protocol defines messages types that just need to be propagated to all other peers.
|
|
|
+## Here, we define some additional messageTypes that the server can interpret.
|
|
|
+## Messages that the server can't interpret should be broadcasted to all other clients.
|
|
|
+
|
|
|
+class ServerMessageType(IntEnum):
|
|
|
+ # The client is asking for a lock. Should return a lock-identifier if one is available.
|
|
|
+ ACQUIRE_LOCK = 127
|
|
|
+ # The client is asking to release a lock to make it available to other users again.
|
|
|
+ RELEASE_LOCK = 126
|
|
|
+ # The client is asking to retrieve the initial state of the Yjs document. Return an empty buffer when nothing is available.
|
|
|
+ REQUEST_INITIALIZED_CONTENT = 125
|
|
|
+ # The client retrieved an empty "initial content" and generated the initial state of the document after acquiring a lock. Store this.
|
|
|
+ PUT_INITIALIZED_CONTENT = 124
|
|
|
+ # The client moved the document to a different location. After receiving this message, we make the current document available under a different url.
|
|
|
+ # The other clients are automatically notified of this change because the path is shared through the Yjs document as well.
|
|
|
+ RENAME_SESSION = 123
|
|
|
|
|
|
class YjsRoom:
|
|
|
def __init__(self):
|
|
@@ -32,35 +45,44 @@ class YJSEchoWS(WebSocketHandler):
|
|
|
if room is None:
|
|
|
room = YjsRoom()
|
|
|
cls.rooms[self.room_id] = room
|
|
|
- room.clients[self.id] = ( IOLoop.current(), self.hook_send_message )
|
|
|
+ room.clients[self.id] = ( IOLoop.current(), self.hook_send_message, self )
|
|
|
# Send SyncStep1 message (based on y-protocols)
|
|
|
self.write_message(bytes([0, 0, 1, 0]), binary=True)
|
|
|
|
|
|
def on_message(self, message):
|
|
|
#print("[YJSEchoWS]: message, ", message)
|
|
|
cls = self.__class__
|
|
|
- room = cls.rooms.get(self.room_id)
|
|
|
- if message[0] == acquireLockMessageType: # tries to acquire lock
|
|
|
+ room_id = self.room_id
|
|
|
+ room = cls.rooms.get(room_id)
|
|
|
+ if message[0] == ServerMessageType.ACQUIRE_LOCK:
|
|
|
now = int(time.time())
|
|
|
if room.lock is None or now - room.lock > 15: # no lock or timeout
|
|
|
room.lock = now
|
|
|
# print('Acquired new lock: ', room.lock)
|
|
|
# return acquired lock
|
|
|
- self.write_message(bytes([acquireLockMessageType]) + room.lock.to_bytes(4, byteorder = 'little'), binary=True)
|
|
|
- elif message[0] == releaseLockMessageType:
|
|
|
+ self.write_message(bytes([ServerMessageType.ACQUIRE_LOCK]) + room.lock.to_bytes(4, byteorder = 'little'), binary=True)
|
|
|
+ elif message[0] == ServerMessageType.RELEASE_LOCK:
|
|
|
releasedLock = int.from_bytes(message[1:], byteorder = 'little')
|
|
|
# print("trying release lock: ", releasedLock)
|
|
|
if room.lock == releasedLock:
|
|
|
# print('released lock: ', room.lock)
|
|
|
room.lock = None
|
|
|
- elif message[0] == requestInitializedContentMessageType:
|
|
|
+ elif message[0] == ServerMessageType.REQUEST_INITIALIZED_CONTENT:
|
|
|
# print("client requested initial content")
|
|
|
- self.write_message(bytes([requestInitializedContentMessageType]) + room.content, binary=True)
|
|
|
- elif message[0] == putInitializedContentMessageType:
|
|
|
+ self.write_message(bytes([ServerMessageType.REQUEST_INITIALIZED_CONTENT]) + room.content, binary=True)
|
|
|
+ elif message[0] == ServerMessageType.PUT_INITIALIZED_CONTENT:
|
|
|
# print("client put initialized content")
|
|
|
room.content = message[1:]
|
|
|
+ elif message[0] == ServerMessageType.RENAME_SESSION:
|
|
|
+ # We move the room to a different entry and also change the room_id property of each connected client
|
|
|
+ new_room_id = message[1:].decode("utf-8")
|
|
|
+ for client_id, (loop, hook_send_message, client) in room.clients.items() :
|
|
|
+ client.room_id = new_room_id
|
|
|
+ cls.rooms.pop(room_id)
|
|
|
+ cls.rooms[new_room_id] = room
|
|
|
+ # print("renamed room to " + new_room_id + ". Old room name was " + room_id)
|
|
|
elif room:
|
|
|
- for client_id, (loop, hook_send_message) in room.clients.items() :
|
|
|
+ for client_id, (loop, hook_send_message, client) in room.clients.items() :
|
|
|
if self.id != client_id :
|
|
|
loop.add_callback(hook_send_message, message)
|
|
|
|