Skip to content

Commit fbae4ff

Browse files
authored
Support file types (#154)
* Support file types * Improves error handling
1 parent 5891988 commit fbae4ff

File tree

5 files changed

+59
-25
lines changed

5 files changed

+59
-25
lines changed

jupyter_collaboration/handlers.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,14 +153,25 @@ async def open(self, room_id):
153153
# Close the connection if the document session expired
154154
session_id = self.get_query_argument("sessionId", "")
155155
if SERVER_SESSION != session_id:
156-
self.close(1003, f"Document session {session_id} expired")
156+
self.close(
157+
1003,
158+
f"Document session {session_id} expired. You need to reload this browser tab.",
159+
)
157160

158161
# cancel the deletion of the room if it was scheduled
159162
if self.room.cleaner is not None:
160163
self.room.cleaner.cancel()
161164

162-
# Initialize the room
163-
await self.room.initialize()
165+
try:
166+
# Initialize the room
167+
await self.room.initialize()
168+
except Exception as e:
169+
_, _, file_id = decode_file_path(self._room_id)
170+
file = self._file_loaders[file_id]
171+
self.log.error(f"Error initializing: {file.path}\n{e!r}", exc_info=e)
172+
self.close(
173+
1003, f"Error initializing: {file.path}. You need to close the document."
174+
)
164175

165176
self._emit(LogLevel.INFO, "initialize", "New client connected.")
166177

jupyter_collaboration/loaders.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@
1414
from jupyter_server.utils import ensure_async
1515
from jupyter_server_fileid.manager import BaseFileIdManager
1616

17-
18-
class OutOfBandChanges(Exception):
19-
pass
17+
from .utils import OutOfBandChanges
2018

2119

2220
class FileLoader:
@@ -137,6 +135,11 @@ async def save_content(self, model: dict[str, Any]) -> dict[str, Any]:
137135
"""
138136
async with self._lock:
139137
path = self.path
138+
if model["type"] not in {"directory", "file", "notebook"}:
139+
# fall back to file if unknown type, the content manager only knows
140+
# how to handle these types
141+
model["type"] = "file"
142+
140143
m = await ensure_async(
141144
self._contents_manager.get(
142145
path, format=model["format"], type=model["type"], content=False

jupyter_collaboration/rooms.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
from ypy_websocket.websocket_server import YRoom
1313
from ypy_websocket.ystore import BaseYStore, YDocNotFound
1414

15-
from .loaders import FileLoader, OutOfBandChanges
16-
from .utils import JUPYTER_COLLABORATION_EVENTS_URI, LogLevel
15+
from .loaders import FileLoader
16+
from .utils import JUPYTER_COLLABORATION_EVENTS_URI, LogLevel, OutOfBandChanges
1717

1818
YFILE = YDOCS["file"]
1919

@@ -94,6 +94,7 @@ async def initialize(self) -> None:
9494
return
9595

9696
self.log.info("Initializing room %s", self._room_id)
97+
9798
model = await self._file.load_content(self._file_format, self._file_type, True)
9899

99100
async with self._update_lock:
@@ -187,11 +188,17 @@ async def _on_content_change(self, event: str, args: dict[str, Any]) -> None:
187188
args (dict): A dictionary with format, type, last_modified.
188189
"""
189190
if event == "metadata" and self._last_modified < args["last_modified"]:
190-
model = await self._file.load_content(self._file_format, self._file_type, True)
191-
192191
self.log.info("Out-of-band changes. Overwriting the content in room %s", self._room_id)
193192
self._emit(LogLevel.INFO, "overwrite", "Out-of-band changes. Overwriting the room.")
194193

194+
try:
195+
model = await self._file.load_content(self._file_format, self._file_type, True)
196+
except Exception as e:
197+
msg = f"Error loading content from file: {self._file.path}\n{e!r}"
198+
self.log.error(msg, exc_info=e)
199+
self._emit(LogLevel.ERROR, None, msg)
200+
return None
201+
195202
async with self._update_lock:
196203
self._document.source = model["content"]
197204
self._last_modified = model["last_modified"]
@@ -255,14 +262,26 @@ async def _maybe_save_document(self) -> None:
255262

256263
except OutOfBandChanges:
257264
self.log.info("Out-of-band changes. Overwriting the content in room %s", self._room_id)
258-
model = await self._file.load_content(self._file_format, self._file_type, True)
265+
try:
266+
model = await self._file.load_content(self._file_format, self._file_type, True)
267+
except Exception as e:
268+
msg = f"Error loading content from file: {self._file.path}\n{e!r}"
269+
self.log.error(msg, exc_info=e)
270+
self._emit(LogLevel.ERROR, None, msg)
271+
return None
272+
259273
async with self._update_lock:
260274
self._document.source = model["content"]
261275
self._last_modified = model["last_modified"]
262276
self._document.dirty = False
263277

264278
self._emit(LogLevel.INFO, "overwrite", "Out-of-band changes while saving.")
265279

280+
except Exception as e:
281+
msg = f"Error saving file: {self._file.path}\n{e!r}"
282+
self.log.error(msg, exc_info=e)
283+
self._emit(LogLevel.ERROR, None, msg)
284+
266285

267286
class TransientRoom(YRoom):
268287
"""A Y room for sharing state (e.g. awareness)."""

jupyter_collaboration/utils.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,18 @@ class LogLevel(Enum):
1717
CRITICAL = "CRITICAL"
1818

1919

20+
class OutOfBandChanges(Exception):
21+
pass
22+
23+
24+
class ReadError(Exception):
25+
pass
26+
27+
28+
class WriteError(Exception):
29+
pass
30+
31+
2032
def decode_file_path(path: str) -> Tuple[str, str, str]:
2133
"""
2234
Decodes a file path. The file path is composed by the format,

packages/docprovider/src/yprovider.ts

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -121,19 +121,9 @@ export class WebSocketProvider implements IDocumentProvider {
121121
if (event.code === 1003) {
122122
console.error('Document provider closed:', event.reason);
123123

124-
showErrorMessage(
125-
this._trans.__('Session expired'),
126-
this._trans.__(
127-
'The document session expired. You need to reload this browser tab.'
128-
),
129-
[Dialog.okButton({ label: this._trans.__('Reload') })]
130-
)
131-
.then((r: any) => {
132-
if (r.button.accept) {
133-
window.location.reload();
134-
}
135-
})
136-
.catch(e => window.location.reload());
124+
showErrorMessage(this._trans.__('Document session error'), event.reason, [
125+
Dialog.okButton()
126+
]);
137127

138128
// Dispose shared model immediately. Better break the document model,
139129
// than overriding data on disk.
@@ -142,7 +132,6 @@ export class WebSocketProvider implements IDocumentProvider {
142132
};
143133

144134
private _onSync = (isSynced: boolean) => {
145-
console.log(`_onSync ${isSynced}`);
146135
if (isSynced) {
147136
this._ready.resolve();
148137
this._yWebsocketProvider?.off('sync', this._onSync);

0 commit comments

Comments
 (0)