Skip to content

Commit aacf113

Browse files
committed
fix: CustomCloud export, event handler & error handler (cherry picked)
1 parent fde3f98 commit aacf113

4 files changed

Lines changed: 1153 additions & 15 deletions

File tree

scratchattach/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from .cloud.cloud import ScratchCloud, TwCloud, get_cloud, get_scratch_cloud, get_tw_cloud
1+
from .cloud.cloud import CustomCloud, ScratchCloud, TwCloud, get_cloud, get_scratch_cloud, get_tw_cloud
22
from .cloud._base import BaseCloud, AnyCloud
33

44
from .eventhandlers.cloud_server import init_cloud_server

scratchattach/cloud/_base.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from __future__ import annotations
2+
import traceback
23

34
import json
45
import ssl
@@ -9,6 +10,8 @@
910
from collections.abc import Iterator
1011
import warnings
1112

13+
from scratchattach.cloud import cloud as cloud_module
14+
1215
if TYPE_CHECKING:
1316
from _typeshed import SupportsRead
1417
else:
@@ -116,7 +119,12 @@ class WebSocketEventStream(EventStream):
116119
reading: Lock
117120
def __init__(self, cloud: BaseCloud):
118121
super().__init__()
119-
self.source_cloud = type(cloud)(project_id=cloud.project_id)
122+
# NOTE: maybe consider using copy.copy here (copy.deepcopy doesn't work as you cannot deepcopy a Thread)
123+
cloud_type = type(cloud)
124+
if cloud_type is cloud_module.CustomCloud:
125+
self.source_cloud = cloud_type(project_id=cloud.project_id, cloud_host=cloud.cloud_host)
126+
else:
127+
self.source_cloud = cloud_type(project_id=cloud.project_id)
120128
self.source_cloud._session = cloud._session
121129
self.source_cloud.cookie = cloud.cookie
122130
self.source_cloud.header = cloud.header
@@ -160,7 +168,6 @@ def read(self, amount: int = -1) -> Iterator[dict[str, Any]]:
160168
done = False
161169
# print("Getting data...")
162170
with self.reading:
163-
# print("Getting data...", end_time is None, end_time > time.time(), end_time is None or end_time > time.time())
164171
while not done:
165172
# print("Getting data...")
166173
try:
@@ -171,11 +178,18 @@ def read(self, amount: int = -1) -> Iterator[dict[str, Any]]:
171178
self.receive_new(timeout = timeout_end - time.time() if has_timeout else None)
172179
if not self.packets_left:
173180
continue
174-
yield json.loads(self.packets_left.pop(0))
175181
i += 1
182+
yield json.loads(self.packets_left.pop(0))
176183
done = True
184+
except json.JSONDecodeError as e:
185+
# this could happen e.g. when the scratchattach server sends the message
186+
# "This server uses @TimMcCool's scratchattach 2.0.0"
187+
warnings.warn(f"Invalid JSON sent from server: {e}")
177188
except Exception:
178-
# traceback.print_exc()
189+
# NOTE: at the very least for `except Exception`, let's print the traceback
190+
# ideally we would never even use `except Exception`. Maybe this is technical debt.
191+
# TODO: investigate what the exception we actually want to catch here
192+
traceback.print_exc()
179193
self.source_cloud.reconnect()
180194

181195
def __del__(self):

scratchattach/eventhandlers/cloud_events.py

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
"""CloudEvents class"""
2+
23
from __future__ import annotations
34

45
from scratchattach.cloud import _base
56
from ._base import BaseEventHandler
67
from scratchattach.site import cloud_activity
78
import time
8-
import json
9+
import traceback
910
from collections.abc import Iterator
1011

12+
1113
class CloudEvents(BaseEventHandler):
1214
"""
1315
Class that calls events when on cloud updates that are received through a websocket connection.
1416
"""
17+
1518
def __init__(self, cloud: _base.AnyCloud):
1619
super().__init__()
1720
self.cloud = cloud
@@ -37,34 +40,48 @@ def _updater(self):
3740
self.source_stream.timeout = 1
3841
for data in self.source_stream.read():
3942
try:
40-
_a = cloud_activity.CloudActivity(timestamp=time.time()*1000, _session=self._session, cloud=self.cloud)
41-
if _a.timestamp < self.startup_time + 500: # catch the on_connect message sent by TurboWarp's (and sometimes Scratch's) cloud server
43+
_a = cloud_activity.CloudActivity(
44+
timestamp=time.time() * 1000,
45+
_session=self._session,
46+
cloud=self.cloud,
47+
)
48+
if (
49+
_a.timestamp < self.startup_time + 500
50+
): # catch the on_connect message sent by TurboWarp's (and sometimes Scratch's) cloud server
51+
# print(f"Skipped as {_a.timestamp} < {self.startup_time + 500}")
4252
continue
4353
data["variable_name"] = data["name"]
4454
data["name"] = data["variable_name"].replace("☁ ", "")
4555
_a._update_from_dict(data)
46-
self.call_event("on_"+_a.type, [_a])
56+
# print(f"sending event {_a}")
57+
self.call_event("on_" + _a.type, [_a])
4758
except Exception as e:
4859
pass
4960
except Exception:
5061
# print("CloudEvents: Disconnected. Reconnecting ...", time.time())
51-
time.sleep(0.1) # cooldown
62+
traceback.print_exc() # always print blanketed exceptions!!
63+
self.subsequent_reconnects += 1
64+
time.sleep(0.1) # cooldown
5265

5366
# print("CloudEvents: Reconnected.", time.time())
5467
self.call_event("on_reconnect", [])
5568

69+
5670
class ManualCloudLogEvents:
5771
"""
5872
Class that calls events on cloud updates that are received from a clouddata log.
5973
"""
74+
6075
def __init__(self, cloud: _base.LogCloud):
6176
if not isinstance(cloud, _base.LogCloud):
62-
raise ValueError("Cloud log events can't be used with a cloud that has no logs available")
77+
raise ValueError(
78+
"Cloud log events can't be used with a cloud that has no logs available"
79+
)
6380
self.cloud = cloud
6481
self.source_cloud = cloud
6582
self._session = cloud._session
6683
self.last_timestamp = 0
67-
84+
6885
def update(self) -> Iterator[tuple[str, list[cloud_activity.CloudActivity]]]:
6986
"""
7087
Update once and yield all packets
@@ -75,19 +92,22 @@ def update(self) -> Iterator[tuple[str, list[cloud_activity.CloudActivity]]]:
7592
if _a.timestamp <= self.last_timestamp:
7693
continue
7794
self.last_timestamp = _a.timestamp
78-
yield ("on_"+_a.type, [_a])
95+
yield ("on_" + _a.type, [_a])
7996
except Exception:
8097
pass
81-
98+
8299

83100
class CloudLogEvents(BaseEventHandler):
84101
"""
85102
Class that calls events on cloud updates that are received from a clouddata log.
86103
"""
104+
87105
def __init__(self, cloud: _base.LogCloud, *, update_interval=0.1):
88106
super().__init__()
89107
if not isinstance(cloud, _base.LogCloud):
90-
raise ValueError("Cloud log events can't be used with a cloud that has no logs available")
108+
raise ValueError(
109+
"Cloud log events can't be used with a cloud that has no logs available"
110+
)
91111
self.cloud = cloud
92112
self.source_cloud = cloud
93113
self.update_interval = update_interval

0 commit comments

Comments
 (0)