-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
save_screenshot seems to be broken on standalone server #147
Comments
Well, I guess I never tested this, sorry. I'll look into it |
yep, not only untested, but pretty much not implemented :-) This is how it works:
So, browser does not need any dangerous "write-to-local-filesystem" Caveat: The process is completely async (well, we talk to Javascript at the end), and by passing the filename through it'll work. But, you might not be able to rely on the screenshot being written immediately after save_screenshot returns in Python. Only when the websocket command comes back, Python will write the file |
Thanks, @bernhard-42 ! Polling client side for appearance of the saved file seems fine to me. I hope that the sync aspects of the implementation are such that as soon as the save_screenshot call returns, it is at least guaranteed that the snapshot has been taken (in js), so that it is safe to call other Python show() commands and know that the screenshot will not reflect these later show()s. If true, and if your fix commit removes the polling in save_screenshot, then this would enable a pipelined approach to dumping screenshots from a successive set of views without having to wait for each file to be written to request the next screenshot. |
This is the fix diff --git a/ocp_vscode/standalone.py b/ocp_vscode/standalone.py
index 29682ca..70491b1 100644
--- a/ocp_vscode/standalone.py
+++ b/ocp_vscode/standalone.py
@@ -1,3 +1,4 @@
+import base64
import orjson
import socket
import yaml
@@ -93,6 +94,17 @@ def COMMS(host, port):
INIT = """onload="showViewer()" """
+def save_png_data_url(data_url, output_path):
+ base64_data = data_url.split(",")[1]
+ image_data = base64.b64decode(base64_data)
+ try:
+ with open(output_path, "wb") as f:
+ f.write(image_data)
+ print(f"Write png file to {output_path}")
+ except Exception as ex:
+ print("Cannot save png file:", str(ex))
+
+
class Viewer:
def __init__(self, params):
self.status = {}
@@ -224,14 +236,19 @@ class Viewer:
if cmd == "status":
self.debug_print("Received status command")
self.python_client.send(orjson.dumps({"text": self.status}))
+
elif cmd == "config":
self.debug_print("Received config command")
self.configure(self.params)
self.config["_splash"] = self.splash
self.python_client.send(orjson.dumps(self.config))
- elif cmd.type == "screenshot":
+
+ elif cmd.get("type") == "screenshot":
self.debug_print("Received screenshot command")
- self.python_client(orjson.dumps(cmd))
+ if self.javascript_client is None:
+ self.not_registered()
+ continue
+ self.javascript_client.send(data)
elif message_type == "D":
self.python_client = ws
@@ -245,11 +262,18 @@ class Viewer:
elif message_type == "U":
self.javascript_client = ws
- changes = orjson.loads(data)["text"]
- self.debug_print("Received incremental UI changes", changes)
- for key, value in changes.items():
- self.status[key] = value
- self.backend.handle_event(changes, MessageType.UPDATES)
+ message = orjson.loads(data)
+ if message["command"] == "screenshot":
+ filename = message["text"]["filename"]
+ data_url = message["text"]["data"]
+ self.debug_print("Received screenshot data for file", filename)
+ save_png_data_url(data_url, filename)
+ else:
+ changes = message["text"]
+ self.debug_print("Received incremental UI changes", changes)
+ for key, value in changes.items():
+ self.status[key] = value
+ self.backend.handle_event(changes, MessageType.UPDATES)
elif message_type == "S":
self.python_client = ws |
I still need to do more testing to check whether there are regressions. |
Thanks, that seems like it works to me too. Suggestions:
|
I'll add a flag to def save_screenshot(filename, port=None, polling=True):
"""Save a screenshot of the current view"""
if not filename.startswith(os.sep):
prefix = pathlib.Path(".").absolute()
full_path = str(prefix / filename)
else:
full_path = filename
p = pathlib.Path(full_path)
mtime = p.stat().st_mtime if p.exists() else 0
send_command({"type": "screenshot", "filename": f"{full_path}"}, port=port)
if polling:
done = False
for i in range(20):
if p.exists() and p.stat().st_mtime > mtime:
print("Screenshot saved to ", full_path)
done = True
break
time.sleep(0.1)
if not done:
print("Warning: Screenshot not found in 2 seconds, aborting") So, for not-so-experts it'll wait. And if you want to avoid polling for your workflow, then add the parameter |
Is save_screenshot meant to work in the standalone server?
AFAICT it can't work with
standalone.py
as written due to mismatched types/expectations, but even if those are fixed it seems to rely on the browser being able to write to the file-system, which seems impossible without some sort of permissions flow or download file dialog / picker(?), or a return message from JS to the standalone server, but I'm not seeing evidence that that is happening, either.Reproduction recipe:
python -m ocp_vscode
and open http://localhost:3939/viewer in a browserpython -c 'from ocp_vscode import show, save_screenshot; from build123d import Box; show(Box(1,2,3)); print(save_screenshot("x.png"))'
Expected:
a) The box is rendered in the browser's viewer
b) A screenshot of the box is written to a file named "x.png" in the standalone server's CWD.
Actual:
i) The box is rendered in the browser's viewer as expected (yay)
ii) save_screenshot raises an exception; when a naive fix is applied to
standalone.py
then it takes 2s, returns None, and no file is written out. Using an absolute path like /tmp/x.png instead of the relative one above doesn't seem to matter.Naive fix mentioned above is:
The text was updated successfully, but these errors were encountered: