Skip to content

Commit 3c80ae8

Browse files
committed
first version receiving Yjs messages
1 parent 4f40a42 commit 3c80ae8

File tree

7 files changed

+92
-35
lines changed

7 files changed

+92
-35
lines changed

jupyter_rtc_core/app.py

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from traitlets.config import Config
33
import asyncio
44

5-
from .handlers import RouteHandler
5+
from .handlers import RouteHandler, YRoomSessionHandler
66
from .websockets import GlobalAwarenessWebsocket, YRoomWebsocket
77
from .rooms import YRoomManager
88

@@ -18,7 +18,9 @@ class RtcExtensionApp(ExtensionApp):
1818
# global awareness websocket
1919
# (r"api/collaboration/room/JupyterLab:globalAwareness/?", GlobalAwarenessWebsocket),
2020
# # ydoc websocket
21-
(r"api/collaboration/room/(.*)", YRoomWebsocket)
21+
(r"api/collaboration/room/(.*)", YRoomWebsocket),
22+
# handler that just adds compatibility with Jupyter Collaboration's frontend
23+
(r"api/collaboration/session/(.*)", YRoomSessionHandler)
2224
]
2325

2426
def initialize(self):
@@ -30,18 +32,25 @@ def initialize_settings(self):
3032
# We cannot access the 'file_id_manager' key immediately because server
3133
# extensions initialize in alphabetical order. 'jupyter_rtc_core' <
3234
# 'jupyter_server_fileid'.
33-
get_fileid_manager = lambda: self.settings["file_id_manager"]
34-
contents_manager = self.serverapp.contents_manager
35-
loop = asyncio.get_event_loop_policy().get_event_loop()
36-
log = self.log
37-
38-
# Initialize YRoomManager
39-
self.settings["yroom_manager"] = YRoomManager(
40-
get_fileid_manager=get_fileid_manager,
41-
contents_manager=contents_manager,
42-
loop=loop,
43-
log=log
44-
)
35+
# def get_fileid_manager():
36+
# self.log.info("IN GETTER")
37+
# for k, v in self.settings.items():
38+
# print(f"{k}: {v}")
39+
# print(len(self.settings.items()))
40+
# print(id(self.settings))
41+
# return self.settings["file_id_manager"]
42+
# contents_manager = self.serverapp.contents_manager
43+
# loop = asyncio.get_event_loop_policy().get_event_loop()
44+
# log = self.log
45+
46+
# # Initialize YRoomManager
47+
# self.settings["yroom_manager"] = YRoomManager(
48+
# get_fileid_manager=get_fileid_manager,
49+
# contents_manager=contents_manager,
50+
# loop=loop,
51+
# log=log
52+
# )
53+
pass
4554

4655

4756
def _link_jupyter_server_extension(self, server_app):

jupyter_rtc_core/handlers.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
import uuid
23

34
from jupyter_server.base.handlers import APIHandler
45
import tornado
@@ -14,4 +15,33 @@ def get(self):
1415
}))
1516

1617

18+
# TODO: remove this by v1.0.0 if deemed unnecessary. Just adding this for
19+
# compatibility with the `jupyter_collaboration` frontend.
20+
class YRoomSessionHandler(APIHandler):
21+
SESSION_ID = str(uuid.uuid4())
22+
23+
@tornado.web.authenticated
24+
def put(self, path):
25+
body = json.loads(self.request.body)
26+
format = body["format"]
27+
content_type = body["type"]
28+
# self.log.info("IN HANDLER")
29+
# for k, v in self.settings.items():
30+
# print(f"{k}: {v}")
31+
# print(len(self.settings.items()))
32+
# print(id(self.settings))
33+
34+
file_id_manager = self.settings["file_id_manager"]
35+
file_id = file_id_manager.index(path)
36+
37+
data = json.dumps(
38+
{
39+
"format": format,
40+
"type": content_type,
41+
"fileId": file_id,
42+
"sessionId": self.SESSION_ID,
43+
}
44+
)
45+
self.set_status(200)
46+
self.finish(data)
1747

jupyter_rtc_core/rooms/yroom.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from .yroom_file_api import YRoomFileAPI
1313

1414
if TYPE_CHECKING:
15-
from typing import Literal, Tuple
15+
from typing import Literal, Tuple, Any
1616
from jupyter_server_fileid.manager import BaseFileIdManager
1717
from jupyter_server.services.contents.manager import AsyncContentsManager, ContentsManager
1818

@@ -22,7 +22,11 @@ class YRoom:
2222
log: Logger
2323
"""Log object"""
2424
room_id: str
25-
"""Room Id"""
25+
"""
26+
The ID of the room. This is a composite ID following the format:
27+
28+
room_id := "{file_type}:{file_format}
29+
"""
2630

2731
_jupyter_ydoc: YBaseDoc
2832
"""JupyterYDoc"""
@@ -60,9 +64,10 @@ def __init__(
6064
self._client_group = YjsClientGroup(room_id=room_id, log=self.log, loop=self._loop)
6165
self._ydoc = pycrdt.Doc()
6266
self._awareness = pycrdt.Awareness(ydoc=self._ydoc)
67+
_, file_type, _ = self.room_id.split(":")
6368
JupyterYDocClass = cast(
6469
type[YBaseDoc],
65-
jupyter_ydoc_classes.get(self.file_type, jupyter_ydoc_classes["file"])
70+
jupyter_ydoc_classes.get(file_type, jupyter_ydoc_classes["file"])
6671
)
6772
self.jupyter_ydoc = JupyterYDocClass(ydoc=self._ydoc, awareness=self._awareness)
6873

@@ -307,11 +312,11 @@ def write_sync_update(self, message_payload: bytes, client_id: str | None = None
307312
def handle_awareness_update(self, client_id: str, message: bytes) -> None:
308313
# Apply the AwarenessUpdate message
309314
try:
310-
message_payload = message[1:]
315+
message_payload = pycrdt.read_message(message[1:])
311316
self._awareness.apply_awareness_update(message_payload, origin=self)
312317
except Exception as e:
313318
self.log.error(
314-
"An exception occurred when applying an AwarenessUpdate"
319+
"An exception occurred when applying an AwarenessUpdate "
315320
f"message from client '{client_id}':"
316321
)
317322
self.log.exception(e)

jupyter_rtc_core/rooms/yroom_manager.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ class YRoomManager:
1515
def __init__(
1616
self,
1717
*,
18-
get_fileid_manager: Callable[[], BaseFileIdManager],
18+
fileid_manager: BaseFileIdManager,
1919
contents_manager: AsyncContentsManager | ContentsManager,
2020
loop: asyncio.AbstractEventLoop,
2121
log: logging.Logger,
2222
):
2323
# Bind instance attributes
24-
self._get_fileid_manager = get_fileid_manager
24+
self.fileid_manager = fileid_manager
2525
self.contents_manager = contents_manager
2626
self.loop = loop
2727
self.log = log
@@ -30,11 +30,6 @@ def __init__(
3030
self._rooms_by_id = {}
3131

3232

33-
@property
34-
def fileid_manager(self) -> BaseFileIdManager:
35-
return self._get_fileid_manager()
36-
37-
3833
def get_room(self, room_id: str) -> YRoom | None:
3934
"""
4035
Retrieves a YRoom given a room ID. If the YRoom does not exist, this
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
from .global_awareness_ws import GlobalAwarenessWebsocket
2-
from .yroom_ws import YRoomWebsocket
32
from .clients import YjsClient, YjsClientGroup
3+
from .yroom_ws import YRoomWebsocket

jupyter_rtc_core/websockets/clients.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
"""
66

77
from __future__ import annotations
8-
from datetime import timedelta, timezone
9-
import datetime
8+
from datetime import timedelta, timezone, datetime
109
from logging import Logger
1110
from typing import TYPE_CHECKING
1211
import uuid
@@ -43,7 +42,7 @@ def synced(self):
4342

4443
@synced.setter
4544
def synced(self, v: bool):
46-
self.synced = v
45+
self._synced = v
4746
self.last_modified = datetime.now(timezone.utc)
4847

4948
class YjsClientGroup:
@@ -67,7 +66,7 @@ class YjsClientGroup:
6766
"""Log object"""
6867
loop: asyncio.AbstractEventLoop
6968
"""Event loop"""
70-
poll_interval_seconds: int
69+
_poll_interval_seconds: int
7170
"""The poll time interval used while auto removing desynced clients"""
7271
desynced_timeout_seconds: int
7372
"""The max time period in seconds that a desynced client does not become synced before get auto removed from desynced dict"""
@@ -79,7 +78,7 @@ def __init__(self, *, room_id: str, log: Logger, loop: asyncio.AbstractEventLoop
7978
self.log = log
8079
self.loop = loop
8180
self.loop.create_task(self._clean_desynced())
82-
self.poll_interval_seconds = poll_interval_seconds
81+
self._poll_interval_seconds = poll_interval_seconds
8382
self.desynced_timeout_seconds = desynced_timeout_seconds
8483

8584
def add(self, websocket: WebSocketHandler) -> str:

jupyter_rtc_core/websockets/yroom_ws.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22
from tornado.httpclient import HTTPError
33
from tornado.websocket import WebSocketHandler
44
from typing import TYPE_CHECKING
5+
import asyncio
6+
from ..rooms import YRoomManager
7+
import logging
58

69
if TYPE_CHECKING:
710
from jupyter_server_fileid.manager import BaseFileIdManager
8-
from ..rooms import YRoom, YRoomManager
11+
from jupyter_server.services.contents.manager import AsyncContentsManager, ContentsManager
12+
from ..rooms import YRoom
913

1014
class YRoomWebsocket(WebSocketHandler):
1115
yroom: YRoom
@@ -14,12 +18,26 @@ class YRoomWebsocket(WebSocketHandler):
1418

1519
@property
1620
def yroom_manager(self) -> YRoomManager:
21+
if "yroom_manager" not in self.settings:
22+
self.settings["yroom_manager"] = YRoomManager(
23+
fileid_manager=self.fileid_manager,
24+
contents_manager=self.contents_manager,
25+
loop=asyncio.get_event_loop_policy().get_event_loop(),
26+
# TODO: change this. we should pass `self.log` from our
27+
# `ExtensionApp` to log messages w/ "RtcCoreExtension" prefix
28+
log=logging.Logger("TEMP")
29+
30+
)
1731
return self.settings["yroom_manager"]
18-
1932

2033
@property
2134
def fileid_manager(self) -> BaseFileIdManager:
2235
return self.settings["file_id_manager"]
36+
37+
38+
@property
39+
def contents_manager(self) -> AsyncContentsManager | ContentsManager:
40+
return self.settings["contents_manager"]
2341

2442

2543
def prepare(self):
@@ -38,7 +56,7 @@ def prepare(self):
3856
raise HTTPError(404, f"No file with ID '{fileid}'.")
3957

4058

41-
def open(self):
59+
def open(self, *_, **__):
4260
# Create the YRoom
4361
yroom = self.yroom_manager.get_room(self.room_id)
4462
if not yroom:
@@ -51,6 +69,7 @@ def open(self):
5169

5270
def on_message(self, message: bytes):
5371
# Route all messages to the YRoom for processing
72+
print(message)
5473
self.yroom.add_message(self.client_id, message)
5574

5675

0 commit comments

Comments
 (0)