opensignalbox-common 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- opensignalbox/common/__init__.py +1 -0
- opensignalbox/common/config.py +72 -0
- opensignalbox/common/libs/pyre/__init__.py +6 -0
- opensignalbox/common/libs/pyre/pyre.py +338 -0
- opensignalbox/common/libs/pyre/pyre_event.py +62 -0
- opensignalbox/common/libs/pyre/pyre_group.py +39 -0
- opensignalbox/common/libs/pyre/pyre_node.py +567 -0
- opensignalbox/common/libs/pyre/pyre_peer.py +226 -0
- opensignalbox/common/libs/pyre/zactor.py +150 -0
- opensignalbox/common/libs/pyre/zbeacon.py +320 -0
- opensignalbox/common/libs/pyre/zhelper.py +592 -0
- opensignalbox/common/libs/pyre/zre_msg.py +506 -0
- opensignalbox/common/libs/pyre/zsocket.py +67 -0
- opensignalbox/common/log_config.py +9 -0
- opensignalbox/common/log_config.yaml +70 -0
- opensignalbox/common/messaging.py +171 -0
- opensignalbox/common/models.py +76 -0
- opensignalbox/common/modules.py +440 -0
- opensignalbox/common/routes.py +143 -0
- opensignalbox/common/sv_registry.py +114 -0
- opensignalbox/common/sv_schema_routes.py +23 -0
- opensignalbox/common/sv_wire.py +39 -0
- opensignalbox/common/utils.py +32 -0
- opensignalbox/common/variables.py +435 -0
- opensignalbox/common/version.py +1 -0
- opensignalbox_common-0.1.0.dist-info/METADATA +29 -0
- opensignalbox_common-0.1.0.dist-info/RECORD +29 -0
- opensignalbox_common-0.1.0.dist-info/WHEEL +4 -0
- opensignalbox_common-0.1.0.dist-info/licenses/LICENSE +619 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__doc__ = "openSignalBox signalling simulator."
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Here the configuration management is defined.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import tomllib
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from appdirs import user_config_dir, user_data_dir
|
|
10
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
11
|
+
|
|
12
|
+
from .version import __version__
|
|
13
|
+
|
|
14
|
+
__APPNAME__ = "openSignalBox"
|
|
15
|
+
__AUTHOR__ = "openSignalBox"
|
|
16
|
+
__USER_CONFIG_DIR__ = user_config_dir(__APPNAME__, __AUTHOR__, __version__)
|
|
17
|
+
__USER_DATA_DIR__ = user_data_dir(__APPNAME__, __AUTHOR__, __version__)
|
|
18
|
+
__DOTENV_NAME__ = "osb.env"
|
|
19
|
+
__DOTENV_PATH__ = os.path.join(__USER_CONFIG_DIR__, __DOTENV_NAME__)
|
|
20
|
+
__SETTINGS_NAME__ = "settings.toml"
|
|
21
|
+
__BOXES_DIRNAME__ = "boxes"
|
|
22
|
+
__MODULES_DIRNAME__ = "modules"
|
|
23
|
+
__CONFIG_DIRNAME__ = "config"
|
|
24
|
+
__BOX_TOMLNAME__ = "box.toml"
|
|
25
|
+
__MODULE_TOMLNAME__ = "module.toml"
|
|
26
|
+
__ROUTER_NAME__ = "router"
|
|
27
|
+
__STATES_DIRNAME__ = "states"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class EnvironmentVariables(BaseSettings):
|
|
31
|
+
workspace_path: Path = Path(__USER_DATA_DIR__) / "workspace"
|
|
32
|
+
model_config = SettingsConfigDict(env_file=__DOTENV_PATH__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class OsbSettings(BaseSettings):
|
|
36
|
+
default_box: str = ""
|
|
37
|
+
default_state: str = ""
|
|
38
|
+
autostart: bool = False
|
|
39
|
+
|
|
40
|
+
def load(self) -> None:
|
|
41
|
+
try:
|
|
42
|
+
with open(
|
|
43
|
+
os.path.join(__WORKSPACE_PATH__, __SETTINGS_NAME__), "rb"
|
|
44
|
+
) as settings_file:
|
|
45
|
+
settings_dict = tomllib.load(settings_file)
|
|
46
|
+
for setting in settings_dict:
|
|
47
|
+
setattr(self, setting.lower(), settings_dict[setting])
|
|
48
|
+
except FileNotFoundError:
|
|
49
|
+
raise
|
|
50
|
+
|
|
51
|
+
def save(self) -> None:
|
|
52
|
+
with open(
|
|
53
|
+
os.path.join(__WORKSPACE_PATH__, __SETTINGS_NAME__), "w"
|
|
54
|
+
) as settings_file:
|
|
55
|
+
settings_file.write('DEFAULT_BOX="' + self.default_box + '"\n')
|
|
56
|
+
settings_file.write('DEFAULT_STATE="' + self.default_state + '"\n')
|
|
57
|
+
settings_file.write("AUTOSTART=" + str(self.autostart).lower() + "\n")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class SessionID:
|
|
61
|
+
value: str = ""
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def write_dotenv():
|
|
65
|
+
# TODO Implement writing here for setting workspace in UI. Use python-dotenv to only change the workspace variable in case users use more variables here.
|
|
66
|
+
raise NotImplementedError
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
__ENV_VARS__ = EnvironmentVariables()
|
|
70
|
+
__WORKSPACE_PATH__ = __ENV_VARS__.workspace_path.absolute()
|
|
71
|
+
osb_settings = OsbSettings()
|
|
72
|
+
session_id_store = SessionID()
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import uuid
|
|
3
|
+
from typing import Any
|
|
4
|
+
from uuid import UUID
|
|
5
|
+
|
|
6
|
+
from . import zhelper
|
|
7
|
+
import zmq
|
|
8
|
+
|
|
9
|
+
# local modules
|
|
10
|
+
from . import __version_info__
|
|
11
|
+
from .pyre_event import PyreEvent
|
|
12
|
+
from .pyre_node import PyreNode
|
|
13
|
+
from .zactor import ZActor
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
raw_input # Python 2
|
|
19
|
+
except NameError:
|
|
20
|
+
raw_input = input # Python 3
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Pyre(object):
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
name: str | None = None,
|
|
27
|
+
ctx: zmq.Context[zmq.Socket[bytes]] | None = None,
|
|
28
|
+
*args: Any,
|
|
29
|
+
**kwargs: Any,
|
|
30
|
+
):
|
|
31
|
+
"""Constructor, creates a new Zyre node. Note that until you start the
|
|
32
|
+
node it is silent and invisible to other nodes on the network.
|
|
33
|
+
The node name is provided to other nodes during discovery. If you
|
|
34
|
+
specify NULL, Zyre generates a randomized node name from the UUID.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
name (str): The name of the node
|
|
38
|
+
|
|
39
|
+
Kwargs:
|
|
40
|
+
ctx: PyZMQ Context, if not specified a new context will be created
|
|
41
|
+
"""
|
|
42
|
+
super(Pyre, self).__init__(*args, **kwargs)
|
|
43
|
+
self._ctx: zmq.Context[zmq.socket.Socket[bytes]] = ctx or zmq.Context()
|
|
44
|
+
self._uuid = None
|
|
45
|
+
self._name = name
|
|
46
|
+
self.verbose = False
|
|
47
|
+
self.inbox, self._outbox = zhelper.zcreate_pipe(self._ctx)
|
|
48
|
+
|
|
49
|
+
# Start node engine and wait for it to be ready
|
|
50
|
+
self.actor = ZActor(self._ctx, PyreNode, self._outbox)
|
|
51
|
+
# Send name, if any, to node backend
|
|
52
|
+
if self._name:
|
|
53
|
+
self.actor.send_unicode("SET NAME", zmq.SNDMORE)
|
|
54
|
+
self.actor.send_unicode(self._name)
|
|
55
|
+
|
|
56
|
+
# def __del__(self):
|
|
57
|
+
# We need to explicitly destroy the actor
|
|
58
|
+
# to make sure our node thread is stopped
|
|
59
|
+
# self.actor.destroy()
|
|
60
|
+
|
|
61
|
+
def __bool__(self):
|
|
62
|
+
"Determine whether the object is valid by converting to boolean" # Python 3
|
|
63
|
+
return True # TODO
|
|
64
|
+
|
|
65
|
+
def __nonzero__(self):
|
|
66
|
+
"Determine whether the object is valid by converting to boolean" # Python 2
|
|
67
|
+
return True # TODO
|
|
68
|
+
|
|
69
|
+
def uuid(self):
|
|
70
|
+
"""Return our node UUID string, after successful initialization"""
|
|
71
|
+
if not self._uuid:
|
|
72
|
+
self.actor.send_unicode("UUID")
|
|
73
|
+
self._uuid = uuid.UUID(bytes=self.actor.recv())
|
|
74
|
+
return self._uuid
|
|
75
|
+
|
|
76
|
+
# Return our node name, after successful initialization
|
|
77
|
+
def name(self):
|
|
78
|
+
"""Return our node name, after successful initialization"""
|
|
79
|
+
if not self._name:
|
|
80
|
+
self.actor.send_unicode("NAME")
|
|
81
|
+
self._name = self.actor.recv().decode("utf-8")
|
|
82
|
+
return self._name
|
|
83
|
+
|
|
84
|
+
# Not in Zyre api
|
|
85
|
+
def set_name(self, name):
|
|
86
|
+
logger.warning(
|
|
87
|
+
"DEPRECATED: set name in constructor, this method will be removed!"
|
|
88
|
+
)
|
|
89
|
+
self.actor.send_unicode("SET NAME", zmq.SNDMORE)
|
|
90
|
+
self.actor.send_unicode(name)
|
|
91
|
+
|
|
92
|
+
def set_header(self, key, value):
|
|
93
|
+
"""Set node header; these are provided to other nodes during discovery
|
|
94
|
+
and come in each ENTER message."""
|
|
95
|
+
self.actor.send_unicode("SET HEADER", flags=zmq.SNDMORE)
|
|
96
|
+
self.actor.send_unicode(key, flags=zmq.SNDMORE)
|
|
97
|
+
self.actor.send_unicode(value)
|
|
98
|
+
|
|
99
|
+
def set_verbose(self):
|
|
100
|
+
"""Set verbose mode; this tells the node to log all traffic as well as
|
|
101
|
+
all major events."""
|
|
102
|
+
self.actor.send_unicode("SET VERBOSE")
|
|
103
|
+
|
|
104
|
+
def set_port(self, port_nbr):
|
|
105
|
+
"""Set UDP beacon discovery port; defaults to 5670, this call overrides
|
|
106
|
+
that so you can create independent clusters on the same network, for
|
|
107
|
+
e.g. development vs. production. Has no effect after zyre_start()."""
|
|
108
|
+
self.actor.send_unicode("SET PORT", zmq.SNDMORE)
|
|
109
|
+
self.actor.send(port_nbr)
|
|
110
|
+
|
|
111
|
+
def set_interval(self, interval):
|
|
112
|
+
"""Set UDP beacon discovery interval, in milliseconds. Default is instant
|
|
113
|
+
beacon exploration followed by pinging every 1,000 msecs."""
|
|
114
|
+
self.actor.send_unicode("SET INTERVAL", zmq.SNDMORE)
|
|
115
|
+
self.actor.send_unicode(interval)
|
|
116
|
+
|
|
117
|
+
def set_interface(self, value):
|
|
118
|
+
"""Set network interface for UDP beacons. If you do not set this, CZMQ will
|
|
119
|
+
choose an interface for you. On boxes with several interfaces you should
|
|
120
|
+
specify which one you want to use, or strange things can happen."""
|
|
121
|
+
logging.debug("set_interface not implemented") # TODO
|
|
122
|
+
|
|
123
|
+
# TODO: check args from zyre
|
|
124
|
+
def set_endpoint(self, format, *args):
|
|
125
|
+
"""By default, Zyre binds to an ephemeral TCP port and broadcasts the local
|
|
126
|
+
host name using UDP beaconing. When you call this method, Zyre will use
|
|
127
|
+
gossip discovery instead of UDP beaconing. You MUST set-up the gossip
|
|
128
|
+
service separately using zyre_gossip_bind() and _connect(). Note that the
|
|
129
|
+
endpoint MUST be valid for both bind and connect operations. You can use
|
|
130
|
+
inproc://, ipc://, or tcp:// transports (for tcp://, use an IP address
|
|
131
|
+
that is meaningful to remote as well as local nodes). Returns 0 if
|
|
132
|
+
the bind was successful, else -1."""
|
|
133
|
+
self.actor.send_unicode("SET ENDPOINT", zmq.SNDMORE)
|
|
134
|
+
self.actor.send_unicode(format)
|
|
135
|
+
|
|
136
|
+
# TODO: We haven't implemented gossiping yet
|
|
137
|
+
# def gossip_bind(self, format, *args):
|
|
138
|
+
# def gossip_connect(self, format, *args):
|
|
139
|
+
|
|
140
|
+
def start(self):
|
|
141
|
+
"""Start node, after setting header values. When you start a node it
|
|
142
|
+
begins discovery and connection. Returns 0 if OK, -1 if it wasn't
|
|
143
|
+
possible to start the node."""
|
|
144
|
+
self.actor.send_unicode("START")
|
|
145
|
+
# the backend will signal back
|
|
146
|
+
self.actor.resolve().wait()
|
|
147
|
+
|
|
148
|
+
def stop(self):
|
|
149
|
+
"""Stop node; this signals to other peers that this node will go away.
|
|
150
|
+
This is polite; however you can also just destroy the node without
|
|
151
|
+
stopping it."""
|
|
152
|
+
self.actor.send_unicode("STOP", flags=zmq.DONTWAIT)
|
|
153
|
+
# the backend will signal back
|
|
154
|
+
self.actor.resolve().wait()
|
|
155
|
+
self.actor.destroy()
|
|
156
|
+
|
|
157
|
+
# Receive next message from node
|
|
158
|
+
def recv(self):
|
|
159
|
+
"""Receive next message from network; the message may be a control
|
|
160
|
+
message (ENTER, EXIT, JOIN, LEAVE) or data (WHISPER, SHOUT).
|
|
161
|
+
"""
|
|
162
|
+
return self.inbox.recv_multipart()
|
|
163
|
+
|
|
164
|
+
def join(self, group):
|
|
165
|
+
"""Join a named group; after joining a group you can send messages to
|
|
166
|
+
the group and all Zyre nodes in that group will receive them."""
|
|
167
|
+
self.actor.send_unicode("JOIN", flags=zmq.SNDMORE)
|
|
168
|
+
self.actor.send_unicode(group)
|
|
169
|
+
|
|
170
|
+
def leave(self, group):
|
|
171
|
+
"""Leave a group"""
|
|
172
|
+
self.actor.send_unicode("LEAVE", flags=zmq.SNDMORE)
|
|
173
|
+
self.actor.send_unicode(group)
|
|
174
|
+
|
|
175
|
+
# Send message to single peer; peer ID is first frame in message
|
|
176
|
+
def whisper(self, peer, msg_p):
|
|
177
|
+
"""Send message to single peer, specified as a UUID string
|
|
178
|
+
Destroys message after sending"""
|
|
179
|
+
self.actor.send_unicode("WHISPER", flags=zmq.SNDMORE)
|
|
180
|
+
self.actor.send(peer.bytes, flags=zmq.SNDMORE)
|
|
181
|
+
if isinstance(msg_p, list):
|
|
182
|
+
self.actor.send_multipart(msg_p)
|
|
183
|
+
else:
|
|
184
|
+
self.actor.send(msg_p)
|
|
185
|
+
|
|
186
|
+
def shout(self, group, msg_p):
|
|
187
|
+
"""Send message to a named group
|
|
188
|
+
Destroys message after sending"""
|
|
189
|
+
self.actor.send_unicode("SHOUT", flags=zmq.SNDMORE)
|
|
190
|
+
self.actor.send_unicode(group, flags=zmq.SNDMORE)
|
|
191
|
+
if isinstance(msg_p, list):
|
|
192
|
+
self.actor.send_multipart(msg_p)
|
|
193
|
+
else:
|
|
194
|
+
self.actor.send(msg_p)
|
|
195
|
+
|
|
196
|
+
# TODO: checks args from zyre
|
|
197
|
+
def whispers(self, peer: UUID, format: str, *args: Any):
|
|
198
|
+
"""Send formatted string to a single peer specified as UUID string"""
|
|
199
|
+
self.actor.send_unicode("WHISPER", flags=zmq.SNDMORE)
|
|
200
|
+
self.actor.send(peer.bytes, flags=zmq.SNDMORE)
|
|
201
|
+
self.actor.send_unicode(format)
|
|
202
|
+
|
|
203
|
+
def shouts(self, group, format, *args):
|
|
204
|
+
"""Send formatted string to a named group"""
|
|
205
|
+
self.actor.send_unicode("SHOUT", flags=zmq.SNDMORE)
|
|
206
|
+
self.actor.send_unicode(group, flags=zmq.SNDMORE)
|
|
207
|
+
self.actor.send_unicode(format)
|
|
208
|
+
|
|
209
|
+
def peers(self):
|
|
210
|
+
"""Return list of current peer ids."""
|
|
211
|
+
self.actor.send_unicode("PEERS")
|
|
212
|
+
peers = self.actor.recv_pyobj()
|
|
213
|
+
return peers
|
|
214
|
+
|
|
215
|
+
def peers_by_group(self, group):
|
|
216
|
+
"""Return list of current peer ids."""
|
|
217
|
+
self.actor.send_unicode("PEERS BY GROUP", flags=zmq.SNDMORE)
|
|
218
|
+
self.actor.send_unicode(group)
|
|
219
|
+
peers_by_group = self.actor.recv_pyobj()
|
|
220
|
+
return peers_by_group
|
|
221
|
+
|
|
222
|
+
def endpoint(self):
|
|
223
|
+
"""Return own endpoint"""
|
|
224
|
+
self.actor.send_unicode("ENDPOINT")
|
|
225
|
+
endpoint = self.actor.recv_unicode()
|
|
226
|
+
return endpoint
|
|
227
|
+
|
|
228
|
+
def recent_events(self):
|
|
229
|
+
"""Iterator that yields recent `PyreEvent`s"""
|
|
230
|
+
while self.socket().get(zmq.EVENTS) & zmq.POLLIN:
|
|
231
|
+
yield PyreEvent(self)
|
|
232
|
+
|
|
233
|
+
def events(self):
|
|
234
|
+
"""Iterator that yields `PyreEvent`s indefinitely"""
|
|
235
|
+
while True:
|
|
236
|
+
yield PyreEvent(self)
|
|
237
|
+
|
|
238
|
+
# --------------------------------------------------------------------------
|
|
239
|
+
# Return the name of a connected peer. Caller owns the
|
|
240
|
+
# string.
|
|
241
|
+
# DEPRECATED: This is dropped in Zyre api. You receive names through events
|
|
242
|
+
def get_peer_name(self, peer):
|
|
243
|
+
logger.warning("get_peer_name() is deprecated, will be removed")
|
|
244
|
+
self.actor.send_unicode("PEER NAME", zmq.SNDMORE)
|
|
245
|
+
self.actor.send(peer.bytes)
|
|
246
|
+
name = self.actor.recv_unicode()
|
|
247
|
+
return name
|
|
248
|
+
|
|
249
|
+
def peer_address(self, peer):
|
|
250
|
+
"""Return the endpoint of a connected peer."""
|
|
251
|
+
self.actor.send_unicode("PEER ENDPOINT", zmq.SNDMORE)
|
|
252
|
+
self.actor.send(peer.bytes)
|
|
253
|
+
adr = self.actor.recv_unicode()
|
|
254
|
+
return adr
|
|
255
|
+
|
|
256
|
+
def peer_header_value(self, peer, name):
|
|
257
|
+
"""Return the value of a header of a conected peer.
|
|
258
|
+
Returns null if peer or key doesn't exist."""
|
|
259
|
+
self.actor.send_unicode("PEER HEADER", zmq.SNDMORE)
|
|
260
|
+
self.actor.send(peer.bytes, zmq.SNDMORE)
|
|
261
|
+
self.actor.send_unicode(name)
|
|
262
|
+
value = self.actor.recv_unicode()
|
|
263
|
+
return value
|
|
264
|
+
|
|
265
|
+
def peer_headers(self, peer):
|
|
266
|
+
"""Return the value of a header of a conected peer.
|
|
267
|
+
Returns null if peer or key doesn't exist."""
|
|
268
|
+
self.actor.send_unicode("PEER HEADERS", zmq.SNDMORE)
|
|
269
|
+
self.actor.send(peer.bytes)
|
|
270
|
+
headers = self.actor.recv_pyobj()
|
|
271
|
+
return headers
|
|
272
|
+
|
|
273
|
+
def own_groups(self):
|
|
274
|
+
"""Return list of currently joined groups."""
|
|
275
|
+
self.actor.send_unicode("OWN GROUPS")
|
|
276
|
+
groups = self.actor.recv_pyobj()
|
|
277
|
+
return groups
|
|
278
|
+
|
|
279
|
+
def peer_groups(self):
|
|
280
|
+
"""Return list of groups known through connected peers."""
|
|
281
|
+
self.actor.send_unicode("PEER GROUPS")
|
|
282
|
+
groups = self.actor.recv_pyobj()
|
|
283
|
+
return groups
|
|
284
|
+
|
|
285
|
+
# Return node socket, for direct polling of socket
|
|
286
|
+
def socket(self):
|
|
287
|
+
"""Return socket for talking to the Zyre node, for polling"""
|
|
288
|
+
return self.inbox
|
|
289
|
+
|
|
290
|
+
@staticmethod
|
|
291
|
+
def version():
|
|
292
|
+
return __version_info__
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def chat_task(ctx, pipe):
|
|
296
|
+
n = Pyre(ctx=ctx)
|
|
297
|
+
n.join("CHAT")
|
|
298
|
+
n.start()
|
|
299
|
+
|
|
300
|
+
poller = zmq.Poller()
|
|
301
|
+
poller.register(pipe, zmq.POLLIN)
|
|
302
|
+
poller.register(n.socket(), zmq.POLLIN)
|
|
303
|
+
while True:
|
|
304
|
+
items = dict(poller.poll())
|
|
305
|
+
if pipe in items:
|
|
306
|
+
message = pipe.recv()
|
|
307
|
+
if message == "$TERM":
|
|
308
|
+
break
|
|
309
|
+
logger.debug("CHAT_TASK: {0}".format(message))
|
|
310
|
+
n.shout("CHAT", message)
|
|
311
|
+
|
|
312
|
+
if n.socket() in items:
|
|
313
|
+
event = PyreEvent(n)
|
|
314
|
+
|
|
315
|
+
logger.debug("NODE_MSG TYPE: {0}".format(event.type))
|
|
316
|
+
logger.debug("NODE_MSG PEER: {0}".format(event.peer_uuid))
|
|
317
|
+
|
|
318
|
+
if event.type == "SHOUT":
|
|
319
|
+
logger.debug("NODE_MSG GROUP: {0}".format(event.group))
|
|
320
|
+
|
|
321
|
+
logger.debug("NODE_MSG CONT: {0}".format(event.msg))
|
|
322
|
+
n.stop()
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
if __name__ == "__main__":
|
|
326
|
+
logging.basicConfig()
|
|
327
|
+
logging.getLogger("__main__").setLevel(logging.DEBUG)
|
|
328
|
+
|
|
329
|
+
ctx = zmq.Context()
|
|
330
|
+
chat_pipe = zhelper.zthread_fork(ctx, chat_task)
|
|
331
|
+
while True:
|
|
332
|
+
try:
|
|
333
|
+
msg = raw_input()
|
|
334
|
+
chat_pipe.send_string(msg)
|
|
335
|
+
except (KeyboardInterrupt, SystemExit):
|
|
336
|
+
chat_pipe.send_string("$TERM")
|
|
337
|
+
break
|
|
338
|
+
logger.debug("Exiting")
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import uuid
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class PyreEvent(object):
|
|
6
|
+
"""Parsing Pyre messages
|
|
7
|
+
|
|
8
|
+
This class provides a higher-level API to the Pyre.recv() call, by doing
|
|
9
|
+
work that you will want to do in many cases, such as unpacking the peer
|
|
10
|
+
headers for each ENTER event received.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, incoming: list[bytes]):
|
|
14
|
+
"""Constructor, creates a new Pyre event. Receive an event from the Pyre node, wraps Pyre.recv.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
node (Pyre): Pyre node
|
|
18
|
+
"""
|
|
19
|
+
super(PyreEvent, self).__init__()
|
|
20
|
+
|
|
21
|
+
self.type: str = incoming.pop(0).decode("utf-8")
|
|
22
|
+
self.peer_uuid_bytes = incoming.pop(0)
|
|
23
|
+
self.peer_name = incoming.pop(0).decode("utf-8")
|
|
24
|
+
self.headers = ""
|
|
25
|
+
self.peer_addr = ""
|
|
26
|
+
self.group = ""
|
|
27
|
+
self.msg: list[bytes] = []
|
|
28
|
+
if self.type == "ENTER":
|
|
29
|
+
self.headers = json.loads(incoming.pop(0).decode("utf-8"))
|
|
30
|
+
self.peer_addr = incoming.pop(0).decode("utf-8")
|
|
31
|
+
elif self.type == "JOIN" or self.type == "LEAVE":
|
|
32
|
+
self.group = incoming.pop(0).decode("utf-8")
|
|
33
|
+
elif self.type == "WHISPER":
|
|
34
|
+
self.msg = incoming
|
|
35
|
+
elif self.type == "SHOUT":
|
|
36
|
+
self.group = incoming.pop(0).decode("utf-8")
|
|
37
|
+
self.msg = incoming
|
|
38
|
+
|
|
39
|
+
def header(self, name: str):
|
|
40
|
+
"""Getter for single header values
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
name (str): Header name
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
str: Header value
|
|
47
|
+
"""
|
|
48
|
+
if self.headers and name in self.headers:
|
|
49
|
+
return self.headers[name]
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def peer_uuid(self):
|
|
54
|
+
"""Creates uuid.UUID object
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
TYPE: uuid.UUID
|
|
58
|
+
"""
|
|
59
|
+
return uuid.UUID(bytes=self.peer_uuid_bytes)
|
|
60
|
+
|
|
61
|
+
def __str__(self):
|
|
62
|
+
return "<%s %s from %s>" % (__name__, self.type, self.peer_uuid.hex)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
logger = logging.getLogger(__name__)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PyreGroup(object):
|
|
7
|
+
def __init__(self, name, peers={}):
|
|
8
|
+
self.name = name
|
|
9
|
+
# TODO perhaps warn if peers is not a set type
|
|
10
|
+
self.peers = peers
|
|
11
|
+
|
|
12
|
+
# def __del__(self):
|
|
13
|
+
|
|
14
|
+
def __repr__(self):
|
|
15
|
+
ret = "GROUPNAME={0}:\n".format(self.name)
|
|
16
|
+
for key, val in self.peers.items():
|
|
17
|
+
ret += "\t{0} {1}\n".format(key, val.name)
|
|
18
|
+
return ret
|
|
19
|
+
|
|
20
|
+
# Add peer to group
|
|
21
|
+
def join(self, peer):
|
|
22
|
+
self.peers[peer.get_identity()] = peer
|
|
23
|
+
peer.set_status(peer.get_status() + 1)
|
|
24
|
+
|
|
25
|
+
# Remove peer from group
|
|
26
|
+
def leave(self, peer):
|
|
27
|
+
peer_identity = peer.get_identity()
|
|
28
|
+
if peer_identity in self.peers:
|
|
29
|
+
self.peers.pop(peer.get_identity())
|
|
30
|
+
|
|
31
|
+
else:
|
|
32
|
+
logger.debug("Peer {0} is not in group {1}.".format(peer, self.name))
|
|
33
|
+
|
|
34
|
+
peer.set_status(peer.get_status() + 1)
|
|
35
|
+
|
|
36
|
+
# Send message to all peers in group
|
|
37
|
+
def send(self, msg):
|
|
38
|
+
for p in self.peers.values():
|
|
39
|
+
p.send(msg)
|