in gui/backend/gui_plugin/shell/ShellModuleSession.py [0:0]
def handle_shell_output(self):
# Read characters from the shell stdout and build responses
# to deliver to the handle_frontend_command method
reply_line = ""
error_buffer = ""
while not self._shell_exited:
reply_json = None
char = self._shell.stdout.read(1)
if len(char) == 0:
break
if not char == '\n':
reply_line += char
continue
# when running on windows, remove the \r (from \r\n sequence)
if reply_line.endswith('\r'):
reply_line = reply_line[:-1]
if reply_line.startswith("["):
reply_line = f"{{ \"rows\": {reply_line} }}"
if reply_line.startswith("{"):
reply_json = json.loads(reply_line)
# While in python mode, the python engine produces errors by
# calling the print callback lots of times, to avoid sending a
# lot of replies to the frontend we will cache consecutive errors
# and send them in one call to the frontend as soon as a non
# error response is received from the shell
if 'error' in reply_json and isinstance(reply_json['error'], str):
error_buffer += reply_json["error"]
else:
if len(error_buffer) > 0:
self._pending_request.send_output(
{"error": error_buffer})
error_buffer = ""
if 'prompt_descriptor' in reply_json:
# remove empty strings
reply_json = remove_dict_useless_items(reply_json)
# command complete
if not self._initialize_complete.is_set():
data = {"last_prompt": self._last_prompt,
"module_session_id": self.module_session_id}
if self._last_prompt != reply_json:
data.update(reply_json)
self._pending_request.complete(message="New Shell Interactive session created successfully.",
data=data)
self._initialize_complete.set()
else:
self._pending_request.complete(
data=None if self._last_prompt == reply_json else reply_json)
self._command_complete.set()
self._last_prompt = reply_json
elif 'prompt' in reply_json:
# request for a client prompt
prompt_event = threading.Event()
if 'type' in reply_json and reply_json['type'] == 'password':
logger.add_filter({
"type": "key",
"key": "reply",
"expire": Filtering.FilterExpire.OnUse
})
reply_json.update(
{"module_session_id": self.module_session_id})
self.send_prompt_response(
self._pending_request.task_id, reply_json, lambda: prompt_event.set())
# Locks until the prompt is handled
prompt_event.wait()
if self._prompt_replied:
self._shell.stdin.write(self._prompt_reply + "\n")
self._shell.stdin.flush()
else:
self.kill_command()
elif 'value' in reply_json:
# generic response to send to the client
send_response = True
if isinstance(reply_json['value'], str) and reply_json['value'].endswith('\r'):
reply_json['value'] = reply_json['value'][:-1]
if len(reply_json['value']) == 0:
send_response = False
if send_response:
self._pending_request.send_output(reply_json)
else:
# Shell commands are stored as JSON and sent to the Shell
# Then the shell will print them as JSON because of interactive=full
# We do not need to reply back the original command to the frontend
if self._pending_request.command != reply_json:
if 'complete' in self._pending_request.command:
self._pending_request.send_output(
reply_json['info'])
else:
self._pending_request.send_output(reply_json)
elif reply_line == "Bye!":
self._shell_exited = True
else:
# Some shell errors are not reported as JSON, i.e. initialization errors
error_buffer += reply_line
reply_line = ''
# A pending request is expected to be present in 3 cases:
# - When the initialization of the session failed
# - When a CLI call was done
# - When the Shell session is closed
if not self._pending_request is None:
exit_status = None
attempts = 3
while exit_status is None and attempts > 0:
try:
exit_status = self._shell.wait(5)
except subprocess.TimeoutExpired:
attempts = attempts - 1
if exit_status != 0:
self._pending_request.fail(message=error_buffer, data={
"exit_status": exit_status})
else:
data = {"module_session_id": self.module_session_id,
"exit_status": exit_status}
self._pending_request.complete(
data=data)
self._command_complete.set()
# Finally closes the module session if not already being closed
if not isinstance(self._pending_request, ShellQuitTask):
self.close()
# On error conditions no processing will take place, still the frontend handler thread is waiting, we need to let it go
self._shell_exited = True
if not self._initialize_complete.is_set():
self._initialize_complete.set()