Skip to content

Commit 014fc40

Browse files
committed
Migrate to latest stores
1 parent 9365912 commit 014fc40

File tree

6 files changed

+71
-47
lines changed

6 files changed

+71
-47
lines changed

jupyter_collaboration/app.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from jupyter_server.extension.application import ExtensionApp
88
from traitlets import Bool, Float, Type
9-
from ypy_websocket.ystore import BaseYStore
9+
from ypy_websocket.stores import BaseYStore
1010

1111
from .handlers import DocSessionHandler, YDocWebSocketHandler
1212
from .loaders import FileLoaderMapping
@@ -22,6 +22,8 @@ class YDocExtension(ExtensionApp):
2222
Enables Real Time Collaboration in JupyterLab
2323
"""
2424

25+
_store: BaseYStore = None
26+
2527
disable_rtc = Bool(False, config=True, help="Whether to disable real time collaboration.")
2628

2729
file_poll_interval = Float(
@@ -80,10 +82,12 @@ def initialize_handlers(self):
8082
for k, v in self.config.get(self.ystore_class.__name__, {}).items():
8183
setattr(self.ystore_class, k, v)
8284

85+
# Instantiate the store
86+
self._store = self.ystore_class(log=self.log)
87+
8388
self.ywebsocket_server = JupyterWebsocketServer(
8489
rooms_ready=False,
8590
auto_clean_rooms=False,
86-
ystore_class=self.ystore_class,
8791
log=self.log,
8892
)
8993

@@ -103,7 +107,7 @@ def initialize_handlers(self):
103107
"document_cleanup_delay": self.document_cleanup_delay,
104108
"document_save_delay": self.document_save_delay,
105109
"file_loaders": self.file_loaders,
106-
"ystore_class": self.ystore_class,
110+
"store": self._store,
107111
"ywebsocket_server": self.ywebsocket_server,
108112
},
109113
),
@@ -120,3 +124,6 @@ async def stop_extension(self):
120124
],
121125
timeout=3,
122126
)
127+
128+
if self._store is not None and self._store.started.is_set():
129+
self._store.stop()

jupyter_collaboration/handlers.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
from jupyter_ydoc import ydocs as YDOCS
1515
from tornado import web
1616
from tornado.websocket import WebSocketHandler
17+
from ypy_websocket.stores import BaseYStore
1718
from ypy_websocket.websocket_server import YRoom
18-
from ypy_websocket.ystore import BaseYStore
1919
from ypy_websocket.yutils import YMessageType, write_var_uint
2020

2121
from .loaders import FileLoaderMapping
@@ -62,6 +62,15 @@ def create_task(self, aw):
6262
task.add_done_callback(self._background_tasks.discard)
6363

6464
async def prepare(self):
65+
# NOTE: Initialize in the ExtensionApp.start_extension once
66+
# https://github.com/jupyter-server/jupyter_server/issues/1329
67+
# is done.
68+
# We are temporarily initializing the store here because `start``
69+
# is an async function
70+
if self._store is not None and not self._store.started.is_set():
71+
await self._store.start()
72+
await self._store.initialize()
73+
6574
if not self._websocket_server.started.is_set():
6675
self.create_task(self._websocket_server.start())
6776
await self._websocket_server.started.wait()
@@ -84,15 +93,13 @@ async def prepare(self):
8493
)
8594

8695
file = self._file_loaders[file_id]
87-
updates_file_path = f".{file_type}:{file_id}.y"
88-
ystore = self._ystore_class(path=updates_file_path, log=self.log)
8996
self.room = DocumentRoom(
9097
self._room_id,
9198
file_format,
9299
file_type,
93100
file,
94101
self.event_logger,
95-
ystore,
102+
self._store,
96103
self.log,
97104
self._document_save_delay,
98105
)
@@ -111,15 +118,15 @@ def initialize(
111118
self,
112119
ywebsocket_server: JupyterWebsocketServer,
113120
file_loaders: FileLoaderMapping,
114-
ystore_class: type[BaseYStore],
121+
store: BaseYStore,
115122
document_cleanup_delay: float | None = 60.0,
116123
document_save_delay: float | None = 1.0,
117124
) -> None:
118125
self._background_tasks = set()
119126
# File ID manager cannot be passed as argument as the extension may load after this one
120127
self._file_id_manager = self.settings["file_id_manager"]
121128
self._file_loaders = file_loaders
122-
self._ystore_class = ystore_class
129+
self._store = store
123130
self._cleanup_delay = document_cleanup_delay
124131
self._document_save_delay = document_save_delay
125132
self._websocket_server = ywebsocket_server

jupyter_collaboration/rooms.py

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010

1111
from jupyter_events import EventLogger
1212
from jupyter_ydoc import ydocs as YDOCS
13+
from ypy_websocket.stores import BaseYStore
1314
from ypy_websocket.websocket_server import YRoom
14-
from ypy_websocket.ystore import BaseYStore, YDocNotFound
1515
from ypy_websocket.yutils import write_var_uint
1616

1717
from .loaders import FileLoader
@@ -104,36 +104,28 @@ async def initialize(self) -> None:
104104
return
105105

106106
self.log.info("Initializing room %s", self._room_id)
107-
108107
model = await self._file.load_content(self._file_format, self._file_type, True)
109108

110109
async with self._update_lock:
111110
# try to apply Y updates from the YStore for this document
112-
read_from_source = True
113-
if self.ystore is not None:
114-
try:
115-
await self.ystore.apply_updates(self.ydoc)
116-
self._emit(
117-
LogLevel.INFO,
118-
"load",
119-
"Content loaded from the store {}".format(
120-
self.ystore.__class__.__qualname__
121-
),
122-
)
123-
self.log.info(
124-
"Content in room %s loaded from the ystore %s",
125-
self._room_id,
126-
self.ystore.__class__.__name__,
127-
)
128-
read_from_source = False
129-
except YDocNotFound:
130-
# YDoc not found in the YStore, create the document from the source file (no change history)
131-
pass
111+
if self.ystore is not None and await self.ystore.exists(self._room_id):
112+
# Load the content from the store
113+
await self.ystore.apply_updates(self._room_id, self.ydoc)
114+
self._emit(
115+
LogLevel.INFO,
116+
"load",
117+
"Content loaded from the store {}".format(
118+
self.ystore.__class__.__qualname__
119+
),
120+
)
121+
self.log.info(
122+
"Content in room %s loaded from the ystore %s",
123+
self._room_id,
124+
self.ystore.__class__.__name__,
125+
)
132126

133-
if not read_from_source:
134127
# if YStore updates and source file are out-of-sync, resync updates with source
135128
if self._document.source != model["content"]:
136-
# TODO: Delete document from the store.
137129
self._emit(
138130
LogLevel.INFO, "initialize", "The file is out-of-sync with the ystore."
139131
)
@@ -142,17 +134,26 @@ async def initialize(self) -> None:
142134
self._file.path,
143135
self.ystore.__class__.__name__,
144136
)
145-
read_from_source = True
146137

147-
if read_from_source:
138+
doc = await self.ystore.get(self._room_id)
139+
await self.ystore.remove(self._room_id)
140+
version = 0
141+
if "version" in doc:
142+
version = doc["version"] + 1
143+
144+
await self.ystore.create(self._room_id, version)
145+
await self.ystore.encode_state_as_update(self._room_id, self.ydoc)
146+
147+
else:
148148
self._emit(LogLevel.INFO, "load", "Content loaded from disk.")
149149
self.log.info(
150150
"Content in room %s loaded from file %s", self._room_id, self._file.path
151151
)
152152
self._document.source = model["content"]
153153

154-
if self.ystore:
155-
await self.ystore.encode_state_as_update(self.ydoc)
154+
if self.ystore is not None:
155+
await self.ystore.create(self._room_id, 0)
156+
await self.ystore.encode_state_as_update(self._room_id, self.ydoc)
156157

157158
self._last_modified = model["last_modified"]
158159
self._document.dirty = False

jupyter_collaboration/stores.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
# Copyright (c) Jupyter Development Team.
22
# Distributed under the terms of the Modified BSD License.
33

4+
from __future__ import annotations
5+
6+
from logging import Logger
7+
48
from traitlets import Int, Unicode
59
from traitlets.config import LoggingConfigurable
6-
from ypy_websocket.ystore import SQLiteYStore as _SQLiteYStore
7-
from ypy_websocket.ystore import TempFileYStore as _TempFileYStore
10+
from ypy_websocket.stores import FileYStore
11+
from ypy_websocket.stores import SQLiteYStore as _SQLiteYStore
812

913

10-
class TempFileYStore(_TempFileYStore):
11-
prefix_dir = "jupyter_ystore_"
14+
class TempFileYStore(FileYStore):
15+
def __init__(self, log: Logger | None = None):
16+
super().__init__(path=".jupyter_store", log=log)
1217

1318

1419
class SQLiteYStoreMetaclass(type(LoggingConfigurable), type(_SQLiteYStore)): # type: ignore
@@ -17,7 +22,7 @@ class SQLiteYStoreMetaclass(type(LoggingConfigurable), type(_SQLiteYStore)): #
1722

1823
class SQLiteYStore(LoggingConfigurable, _SQLiteYStore, metaclass=SQLiteYStoreMetaclass):
1924
db_path = Unicode(
20-
".jupyter_ystore.db",
25+
".jupyter_store.db",
2126
config=True,
2227
help="""The path to the YStore database. Defaults to '.jupyter_ystore.db' in the current
2328
directory.""",
@@ -30,3 +35,6 @@ class SQLiteYStore(LoggingConfigurable, _SQLiteYStore, metaclass=SQLiteYStoreMet
3035
help="""The document time-to-live in seconds. Defaults to None (document history is never
3136
cleared).""",
3237
)
38+
39+
def __init__(self, log: Logger | None = None):
40+
super().__init__(path=self.db_path, log=log)

jupyter_collaboration/websocketserver.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
from tornado.websocket import WebSocketHandler
1111
from ypy_websocket.websocket_server import WebsocketServer, YRoom
12-
from ypy_websocket.ystore import BaseYStore
1312

1413

1514
class RoomNotFound(LookupError):
@@ -27,13 +26,11 @@ class JupyterWebsocketServer(WebsocketServer):
2726

2827
def __init__(
2928
self,
30-
ystore_class: BaseYStore,
3129
rooms_ready: bool = True,
3230
auto_clean_rooms: bool = True,
3331
log: Logger | None = None,
3432
):
3533
super().__init__(rooms_ready, auto_clean_rooms, log)
36-
self.ystore_class = ystore_class
3734
self.ypatch_nb = 0
3835
self.connected_users: dict[Any, Any] = {}
3936
# Async loop is not yet ready at the object instantiation

tests/conftest.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,16 +170,20 @@ def rtc_create_SQLite_store(jp_serverapp):
170170
setattr(SQLiteYStore, k, v)
171171

172172
async def _inner(type: str, path: str, content: str) -> DocumentRoom:
173-
db = SQLiteYStore(path=f"{type}:{path}")
173+
room_id = f"{type}:{path}"
174+
db = SQLiteYStore()
174175
await db.start()
176+
await db.initialize()
175177

176178
if type == "notebook":
177179
doc = YNotebook()
178180
else:
179181
doc = YUnicode()
180182

181183
doc.source = content
182-
await db.encode_state_as_update(doc.ydoc)
184+
185+
await db.create(room_id, 0)
186+
await db.encode_state_as_update(room_id, doc.ydoc)
183187

184188
return db
185189

0 commit comments

Comments
 (0)