Skip to content

Commit 882d32e

Browse files
committed
Save current hash on the document
1 parent 7ea2cd5 commit 882d32e

File tree

3 files changed

+62
-16
lines changed

3 files changed

+62
-16
lines changed

packages/docprovider/src/ydrive.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,11 @@ export class YDrive extends Drive implements ICollaborativeDrive {
161161
const key = `${options.format}:${options.contentType}:${options.path}`;
162162
this._providers.set(key, provider);
163163

164+
sharedModel.changed.connect((_, change) => {
165+
// TODO: make use of the hash
166+
console.log(change.stateChange);
167+
});
168+
164169
sharedModel.disposed.connect(() => {
165170
const provider = this._providers.get(key);
166171
if (provider) {

projects/jupyter-server-ydoc/jupyter_server_ydoc/loaders.py

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ def __init__(
4040
self._log = log or getLogger(__name__)
4141
self._subscriptions: dict[str, Callable[[], Coroutine[Any, Any, None]]] = {}
4242

43-
self._watcher = asyncio.create_task(self._watch_file()) if self._poll_interval else None
43+
self._watcher = (
44+
asyncio.create_task(self._watch_file()) if self._poll_interval else None
45+
)
4446
self.last_modified = None
4547

4648
@property
@@ -79,7 +81,9 @@ 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, id: str, callback: Callable[[], Coroutine[Any, Any, None]]
86+
) -> None:
8387
"""
8488
Subscribe to the file to get notified about out-of-band file changes.
8589
@@ -112,12 +116,14 @@ async def load_content(self, format: str, file_type: str) -> dict[str, Any]:
112116
"""
113117
async with self._lock:
114118
model = await ensure_async(
115-
self._contents_manager.get(self.path, format=format, type=file_type, content=True)
119+
self._contents_manager.get(
120+
self.path, format=format, type=file_type, content=True
121+
)
116122
)
117123
self.last_modified = model["last_modified"]
118124
return model
119125

120-
async def maybe_save_content(self, model: dict[str, Any]) -> None:
126+
async def maybe_save_content(self, model: dict[str, Any]) -> dict[str, Any] | None:
121127
"""
122128
Save the content of the file.
123129
@@ -149,20 +155,34 @@ async def maybe_save_content(self, model: dict[str, Any]) -> None:
149155
# otherwise it could corrupt the file
150156
done_saving = asyncio.Event()
151157
task = asyncio.create_task(self._save_content(model, done_saving))
158+
saved_model = None
152159
try:
153-
await asyncio.shield(task)
160+
saved_model = await asyncio.shield(task)
154161
except asyncio.CancelledError:
155162
pass
156163
await done_saving.wait()
164+
return saved_model
157165
else:
158166
# file changed on disk, raise an error
159167
self.last_modified = m["last_modified"]
160168
raise OutOfBandChanges
161169

162-
async def _save_content(self, model: dict[str, Any], done_saving: asyncio.Event) -> None:
170+
async def _save_content(
171+
self, model: dict[str, Any], done_saving: asyncio.Event
172+
) -> dict[str, Any]:
163173
try:
164174
m = await ensure_async(self._contents_manager.save(model, self.path))
165175
self.last_modified = m["last_modified"]
176+
# TODO, get rid of the extra `get` here once upstream issue:
177+
# https://github.com/jupyter-server/jupyter_server/issues/1453 is resolved
178+
model_with_hash = await ensure_async(
179+
self._contents_manager.get(
180+
self.path,
181+
content=False,
182+
require_hash=True, # TODO require version supporting hash
183+
)
184+
)
185+
return {**m, "hash": model_with_hash["hash"]}
166186
finally:
167187
done_saving.set()
168188

@@ -181,7 +201,9 @@ async def _watch_file(self) -> None:
181201
try:
182202
await self.maybe_notify()
183203
except Exception as e:
184-
self._log.error(f"Error watching file: {self.path}\n{e!r}", exc_info=e)
204+
self._log.error(
205+
f"Error watching file: {self.path}\n{e!r}", exc_info=e
206+
)
185207
except asyncio.CancelledError:
186208
break
187209

@@ -192,9 +214,14 @@ async def maybe_notify(self) -> None:
192214
do_notify = False
193215
async with self._lock:
194216
# Get model metadata; format and type are not need
195-
model = await ensure_async(self._contents_manager.get(self.path, content=False))
217+
model = await ensure_async(
218+
self._contents_manager.get(self.path, content=False)
219+
)
196220

197-
if self.last_modified is not None and self.last_modified < model["last_modified"]:
221+
if (
222+
self.last_modified is not None
223+
and self.last_modified < model["last_modified"]
224+
):
198225
do_notify = True
199226

200227
self.last_modified = model["last_modified"]

projects/jupyter-server-ydoc/jupyter_server_ydoc/rooms.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ def __init__(
3535
save_delay: float | None = None,
3636
exception_handler: Callable[[Exception, Logger], bool] | None = None,
3737
):
38-
super().__init__(ready=False, ystore=ystore, exception_handler=exception_handler, log=log)
38+
super().__init__(
39+
ready=False, ystore=ystore, exception_handler=exception_handler, log=log
40+
)
3941

4042
self._room_id: str = room_id
4143
self._file_format: str = file_format
@@ -168,7 +170,9 @@ async def initialize(self) -> None:
168170
self.ready = True
169171
self._emit(LogLevel.INFO, "initialize", "Room initialized")
170172

171-
def _emit(self, level: LogLevel, action: str | None = None, msg: str | None = None) -> None:
173+
def _emit(
174+
self, level: LogLevel, action: str | None = None, msg: str | None = None
175+
) -> None:
172176
data = {"level": level.value, "room": self._room_id, "path": self._file.path}
173177
if action:
174178
data["action"] = action
@@ -210,8 +214,12 @@ async def _on_outofband_change(self) -> None:
210214
"""
211215
Called when the file got out-of-band changes.
212216
"""
213-
self.log.info("Out-of-band changes. Overwriting the content in room %s", self._room_id)
214-
self._emit(LogLevel.INFO, "overwrite", "Out-of-band changes. Overwriting the room.")
217+
self.log.info(
218+
"Out-of-band changes. Overwriting the content in room %s", self._room_id
219+
)
220+
self._emit(
221+
LogLevel.INFO, "overwrite", "Out-of-band changes. Overwriting the room."
222+
)
215223

216224
try:
217225
model = await self._file.load_content(self._file_format, self._file_type)
@@ -271,7 +279,7 @@ async def _maybe_save_document(self, saving_document: asyncio.Task | None) -> No
271279
await asyncio.sleep(self._save_delay)
272280

273281
self.log.info("Saving the content from room %s", self._room_id)
274-
await self._file.maybe_save_content(
282+
saved_model = await self._file.maybe_save_content(
275283
{
276284
"format": self._file_format,
277285
"type": self._file_type,
@@ -280,16 +288,22 @@ async def _maybe_save_document(self, saving_document: asyncio.Task | None) -> No
280288
)
281289
async with self._update_lock:
282290
self._document.dirty = False
291+
if saved_model:
292+
self._document.hash = saved_model["hash"]
283293

284294
self._emit(LogLevel.INFO, "save", "Content saved.")
285295

286296
except asyncio.CancelledError:
287297
return
288298

289299
except OutOfBandChanges:
290-
self.log.info("Out-of-band changes. Overwriting the content in room %s", self._room_id)
300+
self.log.info(
301+
"Out-of-band changes. Overwriting the content in room %s", self._room_id
302+
)
291303
try:
292-
model = await self._file.load_content(self._file_format, self._file_type)
304+
model = await self._file.load_content(
305+
self._file_format, self._file_type
306+
)
293307
except Exception as e:
294308
msg = f"Error loading content from file: {self._file.path}\n{e!r}"
295309
self.log.error(msg, exc_info=e)

0 commit comments

Comments
 (0)