remoteRF-server 1.1.0.dev46__py3-none-any.whl → 1.1.0.dev48__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.
@@ -50,6 +50,41 @@ from .global_onboarding import RELAYED, hostname, is_offsite
50
50
 
51
51
  logger = logging.getLogger("remoteRF_server.direct")
52
52
 
53
+
54
+ async def close_ice(ice) -> None:
55
+ """Close an aioice Connection without the library's after-close noise.
56
+
57
+ aioice leaves STUN retry timers armed and connectivity-check tasks waiting
58
+ on futures nobody resolves; the timers then fire on closed sockets and the
59
+ tasks die "pending" when the loop stops. Fail those transactions first so
60
+ everything finishes cleanly, then close."""
61
+ from aioice import stun
62
+
63
+ for protocol in list(getattr(ice, "_protocols", [])):
64
+ for transaction in list(getattr(protocol, "transactions", {}).values()):
65
+ handle = getattr(transaction, "_Transaction__timeout_handle", None)
66
+ if handle is not None:
67
+ handle.cancel()
68
+ future = getattr(transaction, "_Transaction__future", None)
69
+ if future is not None and not future.done():
70
+ future.set_exception(stun.TransactionTimeout())
71
+ await ice.close()
72
+ await asyncio.sleep(0) # let the check tasks see their failures and exit
73
+
74
+
75
+ def quiet_aioice_errors(loop) -> None:
76
+ """A STUN retry on an already-closed socket is not an error worth a
77
+ traceback in someone's shell; everything else keeps the default handler."""
78
+ default = loop.default_exception_handler
79
+
80
+ def handler(loop, context):
81
+ exc = context.get("exception")
82
+ if isinstance(exc, (AttributeError, OSError)) and "Transaction" in repr(context.get("handle", "")):
83
+ return
84
+ default(context)
85
+
86
+ loop.set_exception_handler(handler)
87
+
53
88
  ALPN = "remoterf-direct/1"
54
89
  MAX_FRAME = 100 * 1024 * 1024 # the gRPC message ceiling
55
90
  IDLE_SECONDS = 600
@@ -307,7 +342,7 @@ class _Path:
307
342
  task.cancel()
308
343
  with contextlib.suppress(asyncio.CancelledError):
309
344
  await task
310
- await self.ice.close()
345
+ await close_ice(self.ice)
311
346
 
312
347
 
313
348
  async def _offer(key: str, user: str, remote: dict) -> dict:
@@ -327,7 +362,7 @@ async def _offer(key: str, user: str, remote: dict) -> dict:
327
362
  await ice.add_remote_candidate(Candidate.from_sdp(str(sdp)))
328
363
  await ice.add_remote_candidate(None)
329
364
  except Exception:
330
- await ice.close()
365
+ await close_ice(ice)
331
366
  raise
332
367
  path = _paths[key] = _Path(key, user, ice)
333
368
  path.task = asyncio.ensure_future(path.run())
@@ -343,6 +378,7 @@ def _get_loop() -> asyncio.AbstractEventLoop:
343
378
  with _lock:
344
379
  if _loop is None:
345
380
  _loop = asyncio.new_event_loop()
381
+ quiet_aioice_errors(_loop)
346
382
  threading.Thread(
347
383
  target=_loop.run_forever, name="remoterf-direct", daemon=True
348
384
  ).start()
@@ -27,6 +27,19 @@ from .global_onboarding import invite, lan_endpoint
27
27
  from .user_group_handler import user_group_handler
28
28
 
29
29
 
30
+ def _print_device_choices() -> None:
31
+ """The ids a whitelist can name, so nobody has to guess."""
32
+ from . import device_manager
33
+
34
+ devices = device_manager.get_all_devices_str()
35
+ if not devices:
36
+ print("No devices are configured yet (devices.yml).")
37
+ return
38
+ print("Devices on this server:")
39
+ for gid in sorted(devices):
40
+ print(f" {gid:>4} {devices[gid]}")
41
+
42
+
30
43
  def _invites(rows: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
31
44
  """Add the pasteable `invite` form to code rows (bare for LAN-only groups)."""
32
45
  lan_only = {
@@ -103,7 +116,8 @@ def make_user_group_local(session) -> None:
103
116
  return None
104
117
 
105
118
  if not s or s.lower() in {"all", "*"}:
106
- return [] # unrestricted: every device, including ones added later
119
+ print("List the device ids this group may use, e.g. '0,1,2'.")
120
+ continue
107
121
 
108
122
  # Allow both commas and whitespace
109
123
  parts = [p for p in s.replace(",", " ").split() if p]
@@ -136,7 +150,8 @@ def make_user_group_local(session) -> None:
136
150
  return
137
151
 
138
152
  # ---- Parameters ----
139
- devices_whitelist = _prompt_int_list("Devices whitelist (e.g. 0,1,2) [all] (c to cancel): ")
153
+ _print_device_choices()
154
+ devices_whitelist = _prompt_int_list("Devices whitelist (e.g. 0,1,2) (c to cancel): ")
140
155
  if devices_whitelist is None:
141
156
  return
142
157
 
@@ -157,7 +172,7 @@ def make_user_group_local(session) -> None:
157
172
  lan_only = not _prompt_yes_no("Reachable from outside your network (via Global)? (Y/n): ", default_no=False)
158
173
 
159
174
  # ---- Final confirmation ----
160
- wl_str = ",".join(map(str, devices_whitelist)) or "all"
175
+ wl_str = ",".join(map(str, devices_whitelist))
161
176
  print("\nAbout to create user group with:")
162
177
  print(f" group_name : {group_name}")
163
178
  print(f" devices_whitelist : {wl_str}")
@@ -270,7 +285,8 @@ def edit_user_group_local(session) -> None:
270
285
  if s == "":
271
286
  return "" # unchanged
272
287
  if s.lower() in {"all", "*"}:
273
- return [] # unrestricted: every device
288
+ print("List the device ids this group may use, e.g. '0,1,2'.")
289
+ return _prompt_devices_optional(msg)
274
290
  parts = [p for p in s.replace(",", " ").split() if p]
275
291
  try:
276
292
  vals = [int(p) for p in parts]
@@ -313,8 +329,9 @@ def edit_user_group_local(session) -> None:
313
329
  if group_name == "":
314
330
  group_name = None
315
331
 
332
+ _print_device_choices()
316
333
  devices_whitelist = _prompt_devices_optional(
317
- "New devices whitelist (e.g. 0,1,2 | all | Enter unchanged, c cancel): "
334
+ "New devices whitelist (e.g. 0,1,2 | Enter unchanged, c cancel): "
318
335
  )
319
336
  if devices_whitelist is None:
320
337
  return
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoteRF-server
3
- Version: 1.1.0.dev46
3
+ Version: 1.1.0.dev48
4
4
  Summary: RemoteRF server-side control package
5
5
  License: GPL-3.0-or-later
6
6
  Classifier: Programming Language :: Python :: 3
@@ -56,14 +56,14 @@ remoteRF_server/server/commands.py,sha256=ccb_Ab1mbIkosBsFGJnDSccrjWtR_Lymvvn3Iq
56
56
  remoteRF_server/server/console.py,sha256=J91LfwPVmBgielOxfE740NOYduekggyZkqi-47y_A4w,4472
57
57
  remoteRF_server/server/control.py,sha256=Uq1Fk0zIoHHX_EXfz3OuNle8urvO_UhAiB5Lk5cs2e4,5177
58
58
  remoteRF_server/server/device_manager.py,sha256=PPuCwlPSmjfVpCqy_hCaOr9WvFgE8mqtLMhWOAgXNDA,39281
59
- remoteRF_server/server/direct_path.py,sha256=CQs0uPTANJq6Bn-TzsV0APmD3hYKH8xYvczXZZk1OXk,15229
59
+ remoteRF_server/server/direct_path.py,sha256=ry4wRbqMQdSSh-HVFlbbMdTCNM0XksTl-h9MORJ0PLM,16781
60
60
  remoteRF_server/server/dynamic_v2.py,sha256=AMhQEFhNAvCNcF-PdfCCQQLmeoaR7s1Os8et1KFGn_8,70814
61
61
  remoteRF_server/server/global_onboarding.py,sha256=NcKG8TYhCJEiaCtdXL1jEgVm-R_xWpOIj5KI4bWfkr0,39942
62
62
  remoteRF_server/server/grpc_server.py,sha256=91FpJubzN_Wfuk6fW_owIEzNCukd333sAGW68FUU8pk,46787
63
63
  remoteRF_server/server/host_dynamic_v2.py,sha256=1rJQwP4knSd0EXkByrSn_ryjLx81f6sp6SNYr_aEass,8105
64
64
  remoteRF_server/server/reservation.py,sha256=BQJLGNVAdKMZu2Lg4DGO9UhuJ0b1GSF8K1DgIOFrYUg,67816
65
65
  remoteRF_server/server/rpc_manager.py,sha256=BDvsTSVRxwNF2ujuMbIb5TYNAUI6AH4xXBuXrb8lltM,17223
66
- remoteRF_server/server/user_group_cli.py,sha256=8W3BV-_Wcy3EKapjwNWow8y6xlZYP-y6Ckb2BA1sdR4,25735
66
+ remoteRF_server/server/user_group_cli.py,sha256=0QbsGfeIqkyU6E1naGwR-i7BxptPMMTQCi1m7yM03JM,26259
67
67
  remoteRF_server/server/user_group_handler.py,sha256=1ax73W_P3H6GDxQS13evwA7G7skIElFVLuS0MFqsFyU,41780
68
68
  remoteRF_server/server/v2_errors.py,sha256=IS3hIhf5tDe7jaOrDAVQPjGBpqAjN7442BlJYgfJo7g,3331
69
69
  remoteRF_server/server/deployment/__init__.py,sha256=YV86uafd8XiGD7sLaqNfO5xxmt5te2R6DUhD-w1lZuY,77
@@ -90,9 +90,9 @@ remoteRF_server/tools/gen_certs.py,sha256=Vbyu8JjRu__UYPVmyCRXt5SOw0Za1HV7g1md3G
90
90
  remoteRF_server/tools/gist_status.py,sha256=SFv2iQgQrIKT9gxKrU-_Dr7S0p0rE4wQM6J3OC6FcoM,4649
91
91
  remoteRF_server/tools/gist_status_testing.py,sha256=IYw7UkPjAUpKDTtIlnYGb5g6SJkwz4TRGUfJJ6f4LhQ,2690
92
92
  remoteRF_server/tools/introspect_uhd.py,sha256=vUpPb0rM9tsHPxWadS3EfMDE-gMd-TvNV0TjrZOQqBY,4531
93
- remoterf_server-1.1.0.dev46.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
94
- remoterf_server-1.1.0.dev46.dist-info/METADATA,sha256=OJ9-jeWed-pV98PI7BGqFv7mQBw5cw7yWCIuXvTG2Qg,42069
95
- remoterf_server-1.1.0.dev46.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
96
- remoterf_server-1.1.0.dev46.dist-info/entry_points.txt,sha256=_bbKk65PfYi-n6zUjqOiOwcalf_Ws1TCGATIe1TukxE,136
97
- remoterf_server-1.1.0.dev46.dist-info/top_level.txt,sha256=NuYWe8dSx3qdbxshWBwOgx68EHmJ9I8Zvxam4ARsQ_I,30
98
- remoterf_server-1.1.0.dev46.dist-info/RECORD,,
93
+ remoterf_server-1.1.0.dev48.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
94
+ remoterf_server-1.1.0.dev48.dist-info/METADATA,sha256=ZhdOC9w67CZe59Nn38BVHXVFsnyGESpLxDE5izAzi5s,42069
95
+ remoterf_server-1.1.0.dev48.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
96
+ remoterf_server-1.1.0.dev48.dist-info/entry_points.txt,sha256=_bbKk65PfYi-n6zUjqOiOwcalf_Ws1TCGATIe1TukxE,136
97
+ remoterf_server-1.1.0.dev48.dist-info/top_level.txt,sha256=NuYWe8dSx3qdbxshWBwOgx68EHmJ9I8Zvxam4ARsQ_I,30
98
+ remoterf_server-1.1.0.dev48.dist-info/RECORD,,