Skip to content

Commit 9a71e85

Browse files
Backport 'Initialize and update the ydoc path property' #342 (#357)
* Backport PR #342 to 2.x branch * linting * update snapshots * Update jupyter_collaboration/loaders.py Co-authored-by: David Brochart <[email protected]> --------- Co-authored-by: David Brochart <[email protected]>
1 parent 80dd8c1 commit 9a71e85

File tree

4 files changed

+54
-3
lines changed

4 files changed

+54
-3
lines changed

jupyter_collaboration/loaders.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,11 @@ def __init__(
3939

4040
self._log = log or getLogger(__name__)
4141
self._subscriptions: dict[str, Callable[[], Coroutine[Any, Any, None]]] = {}
42+
self._filepath_subscriptions: dict[str, Callable[[], Coroutine[Any, Any, None] | None]] = {}
4243

4344
self._watcher = asyncio.create_task(self._watch_file()) if self._poll_interval else None
4445
self.last_modified = None
46+
self._current_path = self.path
4547

4648
@property
4749
def file_id(self) -> str:
@@ -79,7 +81,12 @@ async def clean(self) -> None:
7981
except asyncio.CancelledError:
8082
self._log.info(f"file watcher for '{self.file_id}' is cancelled now")
8183

82-
def observe(self, id: str, callback: Callable[[], Coroutine[Any, Any, None]]) -> None:
84+
def observe(
85+
self,
86+
id: str,
87+
callback: Callable[[], Coroutine[Any, Any, None]],
88+
filepath_callback: Callable[[], Coroutine[Any, Any, None] | None] | None = None,
89+
) -> None:
8390
"""
8491
Subscribe to the file to get notified about out-of-band file changes.
8592
@@ -88,6 +95,8 @@ def observe(self, id: str, callback: Callable[[], Coroutine[Any, Any, None]]) ->
8895
callback (Callable): Callback for notifying the room.
8996
"""
9097
self._subscriptions[id] = callback
98+
if filepath_callback is not None:
99+
self._filepath_subscriptions[id] = filepath_callback
91100

92101
def unobserve(self, id: str) -> None:
93102
"""
@@ -97,6 +106,8 @@ def unobserve(self, id: str) -> None:
97106
id (str): Room ID
98107
"""
99108
del self._subscriptions[id]
109+
if id in self._filepath_subscriptions:
110+
del self._filepath_subscriptions[id]
100111

101112
async def load_content(self, format: str, file_type: str) -> dict[str, Any]:
102113
"""
@@ -190,15 +201,26 @@ async def maybe_notify(self) -> None:
190201
Notifies subscribed rooms about out-of-band file changes.
191202
"""
192203
do_notify = False
204+
filepath_change = False
193205
async with self._lock:
206+
path = self.path
207+
if self._current_path != path:
208+
self._current_path = path
209+
filepath_change = True
210+
194211
# Get model metadata; format and type are not need
195-
model = await ensure_async(self._contents_manager.get(self.path, content=False))
212+
model = await ensure_async(self._contents_manager.get(path, content=False))
196213

197214
if self.last_modified is not None and self.last_modified < model["last_modified"]:
198215
do_notify = True
199216

200217
self.last_modified = model["last_modified"]
201218

219+
if filepath_change:
220+
# Notify filepath change
221+
for callback in self._filepath_subscriptions.values():
222+
await ensure_async(callback())
223+
202224
if do_notify:
203225
# Notify out-of-band change
204226
# callbacks will load the file content, thus release the lock before calling them

jupyter_collaboration/rooms.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ def __init__(
4242
self._file_type: str = file_type
4343
self._file: FileLoader = file
4444
self._document = YDOCS.get(self._file_type, YFILE)(self.ydoc, self.awareness)
45+
self._document.path = self._file.path
4546

4647
self._logger = logger
4748
self._save_delay = save_delay
@@ -54,7 +55,7 @@ def __init__(
5455

5556
# Listen for document changes
5657
self._document.observe(self._on_document_change)
57-
self._file.observe(self.room_id, self._on_outofband_change)
58+
self._file.observe(self.room_id, self._on_outofband_change, self._on_filepath_change)
5859

5960
@property
6061
def room_id(self) -> str:
@@ -211,6 +212,12 @@ async def _on_outofband_change(self) -> None:
211212
self._document.source = model["content"]
212213
self._document.dirty = False
213214

215+
def _on_filepath_change(self) -> None:
216+
"""
217+
Update the document path property.
218+
"""
219+
self._document.path = self._file.path
220+
214221
def _on_document_change(self, target: str, event: Any) -> None:
215222
"""
216223
Called when the shared document changes.

tests/test_rooms.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,22 @@ async def test_undefined_save_delay_should_not_save_content_after_document_chang
7575
await asyncio.sleep(0.15)
7676

7777
assert "save" not in cm.actions
78+
79+
80+
async def test_document_path(rtc_create_mock_document_room):
81+
id = "test-id"
82+
path = "test.txt"
83+
new_path = "test2.txt"
84+
85+
_, loader, room = rtc_create_mock_document_room(id, path, "")
86+
87+
await room.initialize()
88+
assert room._document.path == path
89+
90+
# Update the path
91+
loader._file_id_manager.move(id, new_path)
92+
93+
# Wait for a bit more than the poll_interval
94+
await asyncio.sleep(0.15)
95+
96+
assert room._document.path == new_path

tests/utils.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ def __init__(self, mapping: dict):
1616
def get_path(self, id: str) -> str:
1717
return self.mapping[id]
1818

19+
def move(self, id: str, new_path: str) -> None:
20+
self.mapping[id] = new_path
21+
1922

2023
class FakeContentsManager:
2124
def __init__(self, model: dict):

0 commit comments

Comments
 (0)